refactor: download

- download task manager
- fix installation summary update
This commit is contained in:
rhunk
2023-08-04 12:17:19 +02:00
parent 2ff8a69403
commit 3df11aadb8
22 changed files with 152 additions and 180 deletions

View File

@ -18,51 +18,6 @@ object SharedContext {
lateinit var downloadTaskManager: DownloadTaskManager
lateinit var translation: LocaleWrapper
private fun askForStoragePermission(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.addCategory("android.intent.category.DEFAULT")
intent.data = android.net.Uri.parse("package:${context.packageName}")
if (context !is Activity) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
exitProcess(0)
}
if (context !is Activity) {
Logger.log("Storage permission not granted, exiting")
exitProcess(0)
}
context.requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE), 0)
}
private fun askForPermissions(context: Context) {
//ask for storage permission
val hasStoragePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Environment.isExternalStorageManager()
} else {
context.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == android.content.pm.PackageManager.PERMISSION_GRANTED
}
if (hasStoragePermission) return
if (context !is Activity) {
askForStoragePermission(context)
return
}
AlertDialog.Builder(context)
.setTitle("Storage permission")
.setMessage("App needs storage permission to download files and save them to your device. Please allow it in the next screen.")
.setPositiveButton("Grant") { _, _ ->
askForStoragePermission(context)
}
.setNegativeButton("Cancel") { _, _ ->
exitProcess(0)
}
.show()
}
fun ensureInitialized(context: Context) {
if (!this::downloadTaskManager.isInitialized) {
downloadTaskManager = DownloadTaskManager().apply {

View File

@ -16,9 +16,11 @@ class DownloaderConfig : ConfigContainer() {
"append_date_time",
"append_type",
"append_username"
)
).apply { set(mutableListOf("append_hash", "append_date_time", "append_type", "append_username")) }
val allowDuplicate = boolean("allow_duplicate")
val mergeOverlays = boolean("merge_overlays")
val chatDownloadContextMenu = boolean("chat_download_context_menu")
val logging = multiple("logging", "started", "success", "progress", "failure")
val logging = multiple("logging", "started", "success", "progress", "failure").apply {
set(mutableListOf("started", "success"))
}
}

View File

@ -9,7 +9,7 @@ import me.rhunk.snapenhance.download.data.DownloadMetadata
import me.rhunk.snapenhance.download.data.DownloadRequest
import me.rhunk.snapenhance.download.data.InputMedia
import me.rhunk.snapenhance.download.data.MediaEncryptionKeyPair
import me.rhunk.snapenhance.download.enums.DownloadMediaType
import me.rhunk.snapenhance.download.data.DownloadMediaType
class DownloadManagerClient (
private val context: ModContext,

View File

@ -5,7 +5,7 @@ import android.content.Context
import android.database.sqlite.SQLiteDatabase
import me.rhunk.snapenhance.download.data.DownloadMetadata
import me.rhunk.snapenhance.download.data.PendingDownload
import me.rhunk.snapenhance.download.enums.DownloadStage
import me.rhunk.snapenhance.download.data.DownloadStage
import me.rhunk.snapenhance.ui.download.MediaFilter
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
@ -158,6 +158,7 @@ class DownloadTaskManager {
iconUrl = cursor.getString(cursor.getColumnIndex("iconUrl"))
)
).apply {
downloadTaskManager = this@DownloadTaskManager
downloadStage = DownloadStage.valueOf(cursor.getString(cursor.getColumnIndex("downloadStage")))
//if downloadStage is not saved, it means the app was killed before the download was finished
if (downloadStage != DownloadStage.SAVED) {

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.download.enums
package me.rhunk.snapenhance.download.data
import android.net.Uri

View File

@ -1,7 +1,5 @@
package me.rhunk.snapenhance.download.data
import me.rhunk.snapenhance.download.enums.DownloadMediaType
data class DashOptions(val offsetTime: Long, val duration: Long?)
data class InputMedia(

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.download.enums
package me.rhunk.snapenhance.download.data
enum class DownloadStage(
val isFinalStage: Boolean = false,

View File

@ -2,15 +2,16 @@ package me.rhunk.snapenhance.download.data
import kotlinx.coroutines.Job
import me.rhunk.snapenhance.SharedContext
import me.rhunk.snapenhance.download.enums.DownloadStage
import me.rhunk.snapenhance.download.DownloadTaskManager
data class PendingDownload(
var downloadId: Int = 0,
var outputFile: String? = null,
var job: Job? = null,
val metadata : DownloadMetadata
) {
lateinit var downloadTaskManager: DownloadTaskManager
var job: Job? = null
var changeListener = { _: DownloadStage, _: DownloadStage -> }
private var _stage: DownloadStage = DownloadStage.PENDING
var downloadStage: DownloadStage
@ -20,15 +21,13 @@ data class PendingDownload(
set(value) = synchronized(this) {
changeListener(_stage, value)
_stage = value
SharedContext.downloadTaskManager.updateTask(this)
downloadTaskManager.updateTask(this)
}
fun isJobActive(): Boolean {
return job?.isActive ?: false
}
fun isJobActive() = job?.isActive == true
fun cancel() {
job?.cancel()
downloadStage = DownloadStage.CANCELLED
job?.cancel()
}
}

View File

@ -5,7 +5,6 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.widget.ImageView
import com.arthenica.ffmpegkit.FFmpegKit
import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.Constants.ARROYO_URL_KEY_PROTO_PATH
import me.rhunk.snapenhance.Logger
@ -20,11 +19,11 @@ import me.rhunk.snapenhance.data.wrapper.impl.media.opera.Layer
import me.rhunk.snapenhance.data.wrapper.impl.media.opera.ParamMap
import me.rhunk.snapenhance.database.objects.FriendInfo
import me.rhunk.snapenhance.download.DownloadManagerClient
import me.rhunk.snapenhance.download.data.DownloadMediaType
import me.rhunk.snapenhance.download.data.DownloadMetadata
import me.rhunk.snapenhance.download.data.InputMedia
import me.rhunk.snapenhance.download.data.SplitMediaAssetType
import me.rhunk.snapenhance.download.data.toKeyPair
import me.rhunk.snapenhance.download.enums.DownloadMediaType
import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.features.impl.Messaging
@ -52,11 +51,8 @@ import kotlin.io.encoding.ExperimentalEncodingApi
class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
private var lastSeenMediaInfoMap: MutableMap<SplitMediaAssetType, MediaInfo>? = null
private var lastSeenMapParams: ParamMap? = null
private val isFFmpegPresent by lazy {
runCatching { FFmpegKit.execute("-version") }.isSuccess
}
private fun provideClientDownloadManager(
private fun provideDownloadManagerClient(
pathSuffix: String,
mediaIdentifier: String,
mediaDisplaySource: String? = null,
@ -115,10 +111,6 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
)
}
private fun canMergeOverlay(): Boolean {
if (!context.config.downloader.autoDownloadOptions.get().contains("merge_overlay")) return false
return isFFmpegPresent
}
//TODO: implement subfolder argument
private fun createNewFilePath(hexHash: String, mediaDisplayType: String?, pathPrefix: String): String {
@ -246,7 +238,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
val author = context.database.getFriendInfo(senderId) ?: return
val authorUsername = author.usernameForSorting!!
downloadOperaMedia(provideClientDownloadManager(
downloadOperaMedia(provideDownloadManagerClient(
pathSuffix = authorUsername,
mediaIdentifier = "$conversationId$senderId${conversationMessage.server_message_id}",
mediaDisplaySource = authorUsername,
@ -286,7 +278,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
) ?: throw Exception("Friend not found in database")
val authorName = author.usernameForSorting!!
downloadOperaMedia(provideClientDownloadManager(
downloadOperaMedia(provideDownloadManagerClient(
pathSuffix = authorName,
mediaIdentifier = paramMap["MEDIA_ID"].toString(),
mediaDisplaySource = authorName,
@ -305,7 +297,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
"[^\\x00-\\x7F]".toRegex(),
"")
downloadOperaMedia(provideClientDownloadManager(
downloadOperaMedia(provideDownloadManagerClient(
pathSuffix = "Public-Stories/$userDisplayName",
mediaIdentifier = paramMap["SNAP_ID"].toString(),
mediaDisplayType = userDisplayName,
@ -316,7 +308,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
//spotlight
if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || canAutoDownload("spotlight"))) {
downloadOperaMedia(provideClientDownloadManager(
downloadOperaMedia(provideDownloadManagerClient(
pathSuffix = "Spotlight",
mediaIdentifier = paramMap["SNAP_ID"].toString(),
mediaDisplayType = MediaFilter.SPOTLIGHT.mediaDisplayType,
@ -328,11 +320,6 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
//stories with mpeg dash media
//TODO: option to download multiple chapters
if (paramMap.containsKey("LONGFORM_VIDEO_PLAYLIST_ITEM") && forceDownload) {
if (!isFFmpegPresent) {
context.shortToast("Can't download media. ffmpeg was not found")
return
}
val storyName = paramMap["STORY_NAME"].toString().replace(
"[^\\x00-\\x7F]".toRegex(),
"")
@ -361,7 +348,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
}
}
provideClientDownloadManager(
provideDownloadManagerClient(
pathSuffix = "Pro-Stories/${storyName}",
mediaIdentifier = "${paramMap["STORY_ID"]}-${snapItem.snapId}",
mediaDisplaySource = storyName,
@ -396,10 +383,12 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
val mediaInfoMap = mutableMapOf<SplitMediaAssetType, MediaInfo>()
val isVideo = mediaParamMap.containsKey("video_media_info_list")
val canMergeOverlay = context.config.downloader.autoDownloadOptions.get().contains("merge_overlay")
mediaInfoMap[SplitMediaAssetType.ORIGINAL] = MediaInfo(
(if (isVideo) mediaParamMap["video_media_info_list"] else mediaParamMap["image_media_info"])!!
)
if (canMergeOverlay() && mediaParamMap.containsKey("overlay_image_media_info")) {
if (canMergeOverlay && mediaParamMap.containsKey("overlay_image_media_info")) {
mediaInfoMap[SplitMediaAssetType.OVERLAY] =
MediaInfo(mediaParamMap["overlay_image_media_info"]!!)
}
@ -483,7 +472,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
runCatching {
if (!isPreview) {
val encryptionKeys = EncryptionHelper.getEncryptionKeys(contentType, messageReader, isArroyo = isArroyoMessage)
provideClientDownloadManager(
provideDownloadManagerClient(
pathSuffix = authorName,
mediaIdentifier = "${message.client_conversation_id}${message.sender_id}${message.server_message_id}",
mediaDisplaySource = authorName,

View File

@ -27,7 +27,7 @@ import me.rhunk.snapenhance.SharedContext
import me.rhunk.snapenhance.core.R
import me.rhunk.snapenhance.data.FileType
import me.rhunk.snapenhance.download.data.PendingDownload
import me.rhunk.snapenhance.download.enums.DownloadStage
import me.rhunk.snapenhance.download.data.DownloadStage
import me.rhunk.snapenhance.util.snap.PreviewUtils
import java.io.File
import java.io.FileInputStream