mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-12 05:07:46 +02:00
refactor: download
- download task manager - fix installation summary update
This commit is contained in:
@ -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 {
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.download.enums
|
||||
package me.rhunk.snapenhance.download.data
|
||||
|
||||
import android.net.Uri
|
||||
|
@ -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(
|
||||
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.download.enums
|
||||
package me.rhunk.snapenhance.download.data
|
||||
|
||||
enum class DownloadStage(
|
||||
val isFinalStage: Boolean = false,
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user