From e20e3dcfd3ce450b0efe61d64db4a34413652c72 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Sat, 19 Aug 2023 04:46:47 +0200 Subject: [PATCH] fixed some bugs caused by new download update --- app/build.gradle.kts | 2 +- .../utils/DownloadFileWorkManager.kt | 17 +- .../utils/VideoDownloadManager.kt | 271 ++++++++++-------- 3 files changed, 165 insertions(+), 125 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 71015e31..708a2083 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -52,7 +52,7 @@ android { targetSdk = 33 versionCode = 59 - versionName = "4.1.4" + versionName = "4.1.5" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "") diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt index c1eb649b..aa424c08 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt @@ -7,6 +7,7 @@ import androidx.work.ForegroundInfo import androidx.work.WorkerParameters import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.VideoDownloadManager.WORK_KEY_INFO @@ -25,15 +26,16 @@ class DownloadFileWorkManager(val context: Context, private val workerParams: Wo override suspend fun doWork(): Result { val key = workerParams.inputData.getString("key") try { - println("KEY $key") if (key == DOWNLOAD_CHECK) { - downloadCheck(applicationContext, ::handleNotification)?.let { - awaitDownload(it) - } + downloadCheck(applicationContext, ::handleNotification) } else if (key != null) { - val info = applicationContext.getKey(WORK_KEY_INFO, key) + val info = + applicationContext.getKey(WORK_KEY_INFO, key) val pkg = - applicationContext.getKey(WORK_KEY_PACKAGE, key) + applicationContext.getKey( + WORK_KEY_PACKAGE, + key + ) if (info != null) { downloadEpisode( applicationContext, @@ -43,10 +45,8 @@ class DownloadFileWorkManager(val context: Context, private val workerParams: Wo info.links, ::handleNotification ) - awaitDownload(info.ep.id) } else if (pkg != null) { downloadFromResume(applicationContext, pkg, ::handleNotification) - awaitDownload(pkg.item.ep.id) } removeKeys(key) } @@ -73,6 +73,7 @@ class DownloadFileWorkManager(val context: Context, private val workerParams: Wo VideoDownloadManager.DownloadType.IsDone, VideoDownloadManager.DownloadType.IsFailed, VideoDownloadManager.DownloadType.IsStopped -> { isDone = true } + else -> Unit } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 0334103f..ef0d9d8a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -26,6 +26,7 @@ import com.hippo.unifile.UniFile import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.TvType @@ -723,7 +724,7 @@ object VideoDownloadManager { var hlsTotal: Int? = null, // this is how many segments that has been written to the file // will always be <= hlsProgress as we may keep some in a buffer - var hlsWrittenProgress: Long = 0, + var hlsWrittenProgress: Int = 0, // this is used for copy with metadata on how much we have downloaded for setKey private var downloadFileInfoTemplate: DownloadedFileInfo? = null @@ -798,6 +799,15 @@ object VideoDownloadManager { notify() } + fun onDelete() { + bytesDownloaded = 0 + hlsWrittenProgress = 0 + hlsProgress = 0 + + //internalType = DownloadType.IsStopped + notify() + } + companion object { const val UPDATE_RATE_MS: Long = 1000L } @@ -842,6 +852,9 @@ object VideoDownloadManager { } } catch (t: Throwable) { logError(t) + if (BuildConfig.DEBUG) { + throw t + } } } @@ -866,7 +879,7 @@ object VideoDownloadManager { } fun setWrittenSegment(segmentIndex: Int) { - hlsWrittenProgress = segmentIndex.toLong() + 1L + hlsWrittenProgress = segmentIndex + 1 } } @@ -916,16 +929,18 @@ object VideoDownloadManager { // set up a connection val request = app.get( link.url.replace(" ", "%20"), - headers = link.headers + mapOf( - "Accept-Encoding" to "identity", - "accept" to "*/*", - "user-agent" to USER_AGENT, - "sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"", - "sec-fetch-mode" to "navigate", - "sec-fetch-dest" to "video", - "sec-fetch-user" to "?1", - "sec-ch-ua-mobile" to "?0", - ) + if (resumeAt > 0) mapOf("Range" to "bytes=${resumeAt}-") else emptyMap(), + headers = link.headers.appendAndDontOverride( + mapOf( + "Accept-Encoding" to "identity", + "accept" to "*/*", + "user-agent" to USER_AGENT, + "sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"", + "sec-fetch-mode" to "navigate", + "sec-fetch-dest" to "video", + "sec-fetch-user" to "?1", + "sec-ch-ua-mobile" to "?0", + ) + if (resumeAt > 0) mapOf("Range" to "bytes=${resumeAt}-") else emptyMap() + ), referer = link.referer, verify = false ) @@ -964,14 +979,14 @@ object VideoDownloadManager { } if (metadata.type == DownloadType.IsStopped) { - return@withContext delete( - context, - name, - relativePath, - extension, - parentId, - baseFile - ) + // we need to close before delete + fileStream.closeQuietly() + metadata.onDelete() + if (deleteFile(context, baseFile, relativePath ?: "", displayName)) { + return@withContext SUCCESS_STOPPED + } else { + return@withContext ERROR_DELETING_FILE + } } metadata.type = DownloadType.IsDone @@ -995,6 +1010,18 @@ object VideoDownloadManager { } } + /** Helper function to make sure duplicate attributes don't get overriden or inserted without lowercase cmp + * example: map("a" to 1) appendAndDontOverride map("A" to 2, "a" to 3, "c" to 4) = map("a" to 1, "c" to 4) + * */ + private fun Map.appendAndDontOverride(rhs: Map): Map { + val out = this.toMutableMap() + val current = this.keys.map { it.lowercase() } + for ((key, value) in rhs) { + if (current.contains(key.lowercase())) continue + out[key] = value + } + return out + } @Throws private suspend fun downloadHLS( @@ -1047,11 +1074,13 @@ object VideoDownloadManager { // do the initial get request to fetch the segments val m3u8 = M3u8Helper.M3u8Stream( - link.url, link.quality, mapOf( - "Accept-Encoding" to "identity", - "accept" to "*/*", - "user-agent" to USER_AGENT, - ) + if (link.referer.isNotBlank()) mapOf("referer" to link.referer) else emptyMap() + link.url, link.quality, link.headers.appendAndDontOverride( + mapOf( + "Accept-Encoding" to "identity", + "accept" to "*/*", + "user-agent" to USER_AGENT, + ) + if (link.referer.isNotBlank()) mapOf("referer" to link.referer) else emptyMap() + ) ) val items = M3u8Helper2.hslLazy(listOf(m3u8)) @@ -1081,14 +1110,14 @@ object VideoDownloadManager { } if (metadata.type == DownloadType.IsStopped) { - return@withContext delete( - context, - name, - relativePath, - extension, - parentId, - baseFile - ) + // we need to close before delete + fileStream.closeQuietly() + metadata.onDelete() + if (deleteFile(context, baseFile, relativePath ?: "", displayName)) { + return@withContext SUCCESS_STOPPED + } else { + return@withContext ERROR_DELETING_FILE + } } metadata.type = DownloadType.IsDone @@ -1194,7 +1223,7 @@ object VideoDownloadManager { return (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace( '/', File.separatorChar - ) + ).replace("${File.separatorChar}${File.separatorChar}", File.separatorChar.toString()) } /** @@ -1224,7 +1253,7 @@ object VideoDownloadManager { return this != null && this.filePath == getDownloadDir()?.filePath } - private fun delete( + /*private fun delete( context: Context, name: String, folder: String?, @@ -1235,7 +1264,7 @@ object VideoDownloadManager { val displayName = getDisplayName(name, extension) // delete all subtitle files - if (extension == "mp4") { + if (extension != "vtt" && extension != "srt") { try { delete(context, name, folder, "vtt", parentId, basePath) delete(context, name, folder, "srt", parentId, basePath) @@ -1248,9 +1277,9 @@ object VideoDownloadManager { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && basePath.isDownloadDir()) { val relativePath = getRelativePath(folder) val lastContent = - context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName) - if (lastContent != null) { - context.contentResolver.delete(lastContent, null, null) + context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName) ?: return ERROR_DELETING_FILE + if(context.contentResolver.delete(lastContent, null, null) <= 0) { + return ERROR_DELETING_FILE } } else { val dir = basePath?.gotoDir(folder) @@ -1260,13 +1289,12 @@ object VideoDownloadManager { // Cleans up empty directory if (dir.listFiles()?.isEmpty() == true) dir.delete() } -// } parentId?.let { downloadDeleteEvent.invoke(parentId) } } return SUCCESS_STOPPED - } + }*/ fun getFileName(context: Context, metadata: DownloadEpisodeMetadata): String { @@ -1377,71 +1405,71 @@ object VideoDownloadManager { suspend fun downloadCheck( context: Context, notificationCallback: (Int, Notification) -> Unit, - ): Int? { - if (currentDownloads.size < maxConcurrentDownloads && downloadQueue.size > 0) { - val pkg = downloadQueue.removeFirst() - val item = pkg.item - val id = item.ep.id - if (currentDownloads.contains(id)) { // IF IT IS ALREADY DOWNLOADING, RESUME IT - downloadEvent.invoke(Pair(id, DownloadActionType.Resume)) - /** ID needs to be returned to the work-manager to properly await notification */ - return id - } + ) { + if (!(currentDownloads.size < maxConcurrentDownloads && downloadQueue.size > 0)) return - currentDownloads.add(id) - - try { - for (index in (pkg.linkIndex ?: 0) until item.links.size) { - val link = item.links[index] - val resume = pkg.linkIndex == index - - setKey( - KEY_RESUME_PACKAGES, - id.toString(), - DownloadResumePackage(item, index) - ) - - var connectionResult = - downloadSingleEpisode( - context, - item.source, - item.folder, - item.ep, - link, - notificationCallback, - resume - ) - //.also { println("Single episode finished with return code: $it") } - - // retry every link at least once - if (connectionResult <= 0) { - connectionResult = downloadSingleEpisode( - context, - item.source, - item.folder, - item.ep, - link, - notificationCallback, - true - ) - } - - if (connectionResult > 0) { // SUCCESS - removeKey(KEY_RESUME_PACKAGES, id.toString()) - break - } else if (index == item.links.lastIndex) { - downloadStatusEvent.invoke(Pair(id, DownloadType.IsFailed)) - } - } - } catch (e: Exception) { - logError(e) - } finally { - currentDownloads.remove(id) - // Because otherwise notifications will not get caught by the work manager - downloadCheckUsingWorker(context) - } + val pkg = downloadQueue.removeFirst() + val item = pkg.item + val id = item.ep.id + if (currentDownloads.contains(id)) { // IF IT IS ALREADY DOWNLOADING, RESUME IT + downloadEvent.invoke(Pair(id, DownloadActionType.Resume)) + /** ID needs to be returned to the work-manager to properly await notification */ + // return id } - return null + + currentDownloads.add(id) + try { + for (index in (pkg.linkIndex ?: 0) until item.links.size) { + val link = item.links[index] + val resume = pkg.linkIndex == index + + setKey( + KEY_RESUME_PACKAGES, + id.toString(), + DownloadResumePackage(item, index) + ) + + var connectionResult = + downloadSingleEpisode( + context, + item.source, + item.folder, + item.ep, + link, + notificationCallback, + resume + ) + //.also { println("Single episode finished with return code: $it") } + + // retry every link at least once + if (connectionResult <= 0) { + connectionResult = downloadSingleEpisode( + context, + item.source, + item.folder, + item.ep, + link, + notificationCallback, + true + ) + } + + if (connectionResult > 0) { // SUCCESS + removeKey(KEY_RESUME_PACKAGES, id.toString()) + break + } else if (index == item.links.lastIndex) { + downloadStatusEvent.invoke(Pair(id, DownloadType.IsFailed)) + } + } + } catch (e: Exception) { + logError(e) + } finally { + currentDownloads.remove(id) + // Because otherwise notifications will not get caught by the work manager + downloadCheckUsingWorker(context) + } + + // return id } fun getDownloadFileInfoAndUpdateSettings(context: Context, id: Int): DownloadedFileInfoResult? { @@ -1500,23 +1528,21 @@ object VideoDownloadManager { return success } - private fun deleteFile(context: Context, id: Int): Boolean { - val info = - context.getKey(KEY_DOWNLOAD_INFO, id.toString()) ?: return false - downloadEvent.invoke(Pair(id, DownloadActionType.Stop)) - downloadProgressEvent.invoke(Triple(id, 0, 0)) - downloadStatusEvent.invoke(Pair(id, DownloadType.IsStopped)) - downloadDeleteEvent.invoke(id) - val base = basePathToFile(context, info.basePath) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && base.isDownloadDir()) { + private fun deleteFile( + context: Context, + folder: UniFile?, + relativePath: String, + displayName: String + ): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && folder.isDownloadDir()) { val cr = context.contentResolver ?: return false val fileUri = - cr.getExistingDownloadUriOrNullQ(info.relativePath, info.displayName) + cr.getExistingDownloadUriOrNullQ(relativePath, displayName) ?: return true // FILE NOT FOUND, ALREADY DELETED return cr.delete(fileUri, null, null) > 0 // IF DELETED ROWS IS OVER 0 } else { - val file = base?.gotoDir(info.relativePath)?.findFile(info.displayName) + val file = folder?.gotoDir(relativePath)?.findFile(displayName) // val normalPath = context.getNormalPath(getFile(info.relativePath), info.displayName) // val dFile = File(normalPath) if (file?.exists() != true) return true @@ -1530,6 +1556,17 @@ object VideoDownloadManager { } } + private fun deleteFile(context: Context, id: Int): Boolean { + val info = + context.getKey(KEY_DOWNLOAD_INFO, id.toString()) ?: return false + downloadEvent.invoke(Pair(id, DownloadActionType.Stop)) + downloadProgressEvent.invoke(Triple(id, 0, 0)) + downloadStatusEvent.invoke(id to DownloadType.IsStopped) + downloadDeleteEvent.invoke(id) + val base = basePathToFile(context, info.basePath) + return deleteFile(context, base, info.relativePath, info.displayName) + } + fun getDownloadResumePackage(context: Context, id: Int): DownloadResumePackage? { return context.getKey(KEY_RESUME_PACKAGES, id.toString()) } @@ -1544,10 +1581,12 @@ object VideoDownloadManager { downloadQueue.addLast(pkg) downloadCheck(context, notificationCallback) if (setKey) saveQueue() + //ret } else { - downloadEvent.invoke( + downloadEvent( Pair(pkg.item.ep.id, DownloadActionType.Resume) ) + //null } }