mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
changes to downloader for stable resume
This commit is contained in:
parent
4e28e5f8cc
commit
afcbdeecc8
4 changed files with 191 additions and 380 deletions
|
@ -520,10 +520,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
if (uri == null) return@normalSafeApiCall
|
if (uri == null) return@normalSafeApiCall
|
||||||
val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall
|
val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall
|
||||||
// RW perms for the path
|
// RW perms for the path
|
||||||
val flags =
|
ctx.contentResolver.takePersistableUriPermission(
|
||||||
|
uri,
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
)
|
||||||
ctx.contentResolver.takePersistableUriPermission(uri, flags)
|
|
||||||
|
|
||||||
val file = UniFile.fromUri(ctx, uri)
|
val file = UniFile.fromUri(ctx, uri)
|
||||||
println("Loaded subtitle file. Selected URI path: $uri - Name: ${file.name}")
|
println("Loaded subtitle file. Selected URI path: $uri - Name: ${file.name}")
|
||||||
|
|
|
@ -116,13 +116,14 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
||||||
null,
|
null,
|
||||||
"txt",
|
"txt",
|
||||||
false
|
false
|
||||||
).fileStream
|
).openNew()
|
||||||
fileStream?.writer()?.write(text)
|
fileStream.writer().write(text)
|
||||||
} catch (e: Exception) {
|
dialog.dismissSafe(activity)
|
||||||
logError(e)
|
} catch (t: Throwable) {
|
||||||
|
logError(t)
|
||||||
|
showToast(t.message)
|
||||||
} finally {
|
} finally {
|
||||||
fileStream?.closeQuietly()
|
fileStream?.closeQuietly()
|
||||||
dialog.dismissSafe(activity)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.closeBtt.setOnClickListener {
|
binding.closeBtt.setOnClickListener {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadCheck
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadEpisode
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadEpisode
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadFromResume
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadFromResume
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadStatusEvent
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadStatusEvent
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadResumePackage
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
const val DOWNLOAD_CHECK = "DownloadCheck"
|
const val DOWNLOAD_CHECK = "DownloadCheck"
|
||||||
|
@ -36,15 +37,20 @@ class DownloadFileWorkManager(val context: Context, private val workerParams: Wo
|
||||||
WORK_KEY_PACKAGE,
|
WORK_KEY_PACKAGE,
|
||||||
key
|
key
|
||||||
)
|
)
|
||||||
|
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
downloadEpisode(
|
getDownloadResumePackage(applicationContext, info.ep.id)?.let { dpkg ->
|
||||||
applicationContext,
|
downloadFromResume(applicationContext, dpkg, ::handleNotification)
|
||||||
info.source,
|
} ?: run {
|
||||||
info.folder,
|
downloadEpisode(
|
||||||
info.ep,
|
applicationContext,
|
||||||
info.links,
|
info.source,
|
||||||
::handleNotification
|
info.folder,
|
||||||
)
|
info.ep,
|
||||||
|
info.links,
|
||||||
|
::handleNotification
|
||||||
|
)
|
||||||
|
}
|
||||||
} else if (pkg != null) {
|
} else if (pkg != null) {
|
||||||
downloadFromResume(applicationContext, pkg, ::handleNotification)
|
downloadFromResume(applicationContext, pkg, ::handleNotification)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,7 @@ import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
@ -32,6 +30,7 @@ import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||||
import com.lagradost.cloudstream3.services.VideoDownloadService
|
import com.lagradost.cloudstream3.services.VideoDownloadService
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
@ -44,6 +43,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -301,6 +301,8 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
||||||
builder.setProgress((total / 1000).toInt(), (progress / 1000).toInt(), false)
|
builder.setProgress((total / 1000).toInt(), (progress / 1000).toInt(), false)
|
||||||
|
} else if (state == DownloadType.IsPending) {
|
||||||
|
builder.setProgress(0,0,true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val rowTwoExtra = if (ep.name != null) " - ${ep.name}\n" else ""
|
val rowTwoExtra = if (ep.name != null) " - ${ep.name}\n" else ""
|
||||||
|
@ -352,6 +354,10 @@ object VideoDownloadManager {
|
||||||
(if (linkName == null) "" else "$linkName\n") + "$rowTwo\n$progressPercentage % ($progressMbString/$totalMbString)$suffix$mbPerSecondString"
|
(if (linkName == null) "" else "$linkName\n") + "$rowTwo\n$progressPercentage % ($progressMbString/$totalMbString)$suffix$mbPerSecondString"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DownloadType.IsPending -> {
|
||||||
|
(if (linkName == null) "" else "$linkName\n") + rowTwo
|
||||||
|
}
|
||||||
|
|
||||||
DownloadType.IsFailed -> {
|
DownloadType.IsFailed -> {
|
||||||
downloadFormat.format(
|
downloadFormat.format(
|
||||||
context.getString(R.string.download_failed),
|
context.getString(R.string.download_failed),
|
||||||
|
@ -363,7 +369,7 @@ object VideoDownloadManager {
|
||||||
downloadFormat.format(context.getString(R.string.download_done), rowTwo)
|
downloadFormat.format(context.getString(R.string.download_done), rowTwo)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
DownloadType.IsStopped -> {
|
||||||
downloadFormat.format(
|
downloadFormat.format(
|
||||||
context.getString(R.string.download_canceled),
|
context.getString(R.string.download_canceled),
|
||||||
rowTwo
|
rowTwo
|
||||||
|
@ -377,7 +383,7 @@ object VideoDownloadManager {
|
||||||
} else {
|
} else {
|
||||||
val txt =
|
val txt =
|
||||||
when (state) {
|
when (state) {
|
||||||
DownloadType.IsDownloading, DownloadType.IsPaused -> {
|
DownloadType.IsDownloading, DownloadType.IsPaused, DownloadType.IsPending -> {
|
||||||
rowTwo
|
rowTwo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,7 +398,7 @@ object VideoDownloadManager {
|
||||||
downloadFormat.format(context.getString(R.string.download_done), rowTwo)
|
downloadFormat.format(context.getString(R.string.download_done), rowTwo)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
DownloadType.IsStopped -> {
|
||||||
downloadFormat.format(
|
downloadFormat.format(
|
||||||
context.getString(R.string.download_canceled),
|
context.getString(R.string.download_canceled),
|
||||||
rowTwo
|
rowTwo
|
||||||
|
@ -480,54 +486,6 @@ object VideoDownloadManager {
|
||||||
return tempName.replace(" ", " ").trim(' ')
|
return tempName.replace(" ", " ").trim(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
|
||||||
private fun ContentResolver.getExistingFolderStartName(relativePath: String): List<Pair<String, Uri>>? {
|
|
||||||
try {
|
|
||||||
val projection = arrayOf(
|
|
||||||
MediaStore.MediaColumns._ID,
|
|
||||||
MediaStore.MediaColumns.DISPLAY_NAME, // unused (for verification use only)
|
|
||||||
//MediaStore.MediaColumns.RELATIVE_PATH, // unused (for verification use only)
|
|
||||||
)
|
|
||||||
|
|
||||||
val selection =
|
|
||||||
"${MediaStore.MediaColumns.RELATIVE_PATH}='$relativePath'"
|
|
||||||
|
|
||||||
val result = this.query(
|
|
||||||
MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
|
|
||||||
projection, selection, null, null
|
|
||||||
)
|
|
||||||
val list = ArrayList<Pair<String, Uri>>()
|
|
||||||
|
|
||||||
result.use { c ->
|
|
||||||
if (c != null && c.count >= 1) {
|
|
||||||
c.moveToFirst()
|
|
||||||
while (true) {
|
|
||||||
val id = c.getLong(c.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
|
|
||||||
val name =
|
|
||||||
c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME))
|
|
||||||
val uri = ContentUris.withAppendedId(
|
|
||||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI, id
|
|
||||||
)
|
|
||||||
list.add(Pair(name, uri))
|
|
||||||
if (c.isLast) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c.moveToNext()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
val cDisplayName = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME))
|
|
||||||
val cRelativePath = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH))*/
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logError(e)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for getting video player subs.
|
* Used for getting video player subs.
|
||||||
* @return List of pairs for the files in this format: <Name, Uri>
|
* @return List of pairs for the files in this format: <Name, Uri>
|
||||||
|
@ -538,76 +496,12 @@ object VideoDownloadManager {
|
||||||
basePath: String?
|
basePath: String?
|
||||||
): List<Pair<String, Uri>>? {
|
): List<Pair<String, Uri>>? {
|
||||||
val base = basePathToFile(context, basePath)
|
val base = basePathToFile(context, basePath)
|
||||||
val folder = base?.gotoDir(relativePath, false)
|
val folder = base?.gotoDir(relativePath, false) ?: return null
|
||||||
|
if (!folder.isDirectory) return null
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && base.isDownloadDir()) {
|
return folder.listFiles()?.map { (it.name ?: "") to it.uri }
|
||||||
return context.contentResolver?.getExistingFolderStartName(relativePath)
|
|
||||||
} else {
|
|
||||||
// val normalPath =
|
|
||||||
// "${Environment.getExternalStorageDirectory()}${File.separatorChar}${relativePath}".replace(
|
|
||||||
// '/',
|
|
||||||
// File.separatorChar
|
|
||||||
// )
|
|
||||||
// val folder = File(normalPath)
|
|
||||||
if (folder?.isDirectory == true) {
|
|
||||||
return folder.listFiles()?.map { Pair(it.name ?: "", it.uri) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
|
||||||
private fun ContentResolver.getExistingDownloadUriOrNullQ(
|
|
||||||
relativePath: String,
|
|
||||||
displayName: String
|
|
||||||
): Uri? {
|
|
||||||
try {
|
|
||||||
val projection = arrayOf(
|
|
||||||
MediaStore.MediaColumns._ID,
|
|
||||||
//MediaStore.MediaColumns.DISPLAY_NAME, // unused (for verification use only)
|
|
||||||
//MediaStore.MediaColumns.RELATIVE_PATH, // unused (for verification use only)
|
|
||||||
)
|
|
||||||
|
|
||||||
val selection =
|
|
||||||
"${MediaStore.MediaColumns.RELATIVE_PATH}='$relativePath' AND " + "${MediaStore.MediaColumns.DISPLAY_NAME}='$displayName'"
|
|
||||||
|
|
||||||
val result = this.query(
|
|
||||||
MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
|
|
||||||
projection, selection, null, null
|
|
||||||
)
|
|
||||||
|
|
||||||
result.use { c ->
|
|
||||||
if (c != null && c.count >= 1) {
|
|
||||||
c.moveToFirst().let {
|
|
||||||
val id = c.getLong(c.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
|
|
||||||
/*
|
|
||||||
val cDisplayName = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME))
|
|
||||||
val cRelativePath = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH))*/
|
|
||||||
|
|
||||||
return ContentUris.withAppendedId(
|
|
||||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI, id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logError(e)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
|
||||||
fun ContentResolver.getFileLength(fileUri: Uri): Long? {
|
|
||||||
return try {
|
|
||||||
this.openFileDescriptor(fileUri, "r")
|
|
||||||
.use { it?.statSize ?: 0 }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logError(e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class CreateNotificationMetadata(
|
data class CreateNotificationMetadata(
|
||||||
val type: DownloadType,
|
val type: DownloadType,
|
||||||
|
@ -619,16 +513,39 @@ object VideoDownloadManager {
|
||||||
)
|
)
|
||||||
|
|
||||||
data class StreamData(
|
data class StreamData(
|
||||||
val errorCode: Int,
|
private val fileLength: Long,
|
||||||
val resume: Boolean? = null,
|
val file: UniFile,
|
||||||
val fileLength: Long? = null,
|
//val fileStream: OutputStream,
|
||||||
val fileStream: OutputStream? = null,
|
) {
|
||||||
)
|
fun open() : OutputStream {
|
||||||
|
return file.openOutputStream(resume)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openNew() : OutputStream {
|
||||||
|
return file.openOutputStream(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val resume: Boolean get() = fileLength > 0L
|
||||||
|
val startAt: Long get() = if (resume) fileLength else 0L
|
||||||
|
val exists: Boolean get() = file.exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//class ADownloadException(val id: Int) : RuntimeException(message = "Download error $id")
|
||||||
|
|
||||||
|
fun UniFile.createFileOrThrow(displayName: String): UniFile {
|
||||||
|
return this.createFile(displayName) ?: throw IOException("Could not create file")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun UniFile.deleteOrThrow() {
|
||||||
|
if (!this.delete()) throw IOException("Could not delete file")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the appropriate file and creates a data stream from the file.
|
* Sets up the appropriate file and creates a data stream from the file.
|
||||||
* Used for initializing downloads.
|
* Used for initializing downloads.
|
||||||
* */
|
* */
|
||||||
|
@Throws(IOException::class)
|
||||||
fun setupStream(
|
fun setupStream(
|
||||||
context: Context,
|
context: Context,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -637,88 +554,24 @@ object VideoDownloadManager {
|
||||||
tryResume: Boolean,
|
tryResume: Boolean,
|
||||||
): StreamData {
|
): StreamData {
|
||||||
val displayName = getDisplayName(name, extension)
|
val displayName = getDisplayName(name, extension)
|
||||||
val fileStream: OutputStream
|
|
||||||
val fileLength: Long
|
|
||||||
var resume = tryResume
|
|
||||||
val baseFile = context.getBasePath()
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && baseFile.first?.isDownloadDir() == true) {
|
val (baseFile, _) = context.getBasePath()
|
||||||
val cr = context.contentResolver ?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND)
|
|
||||||
|
|
||||||
val currentExistingFile =
|
val subDir = baseFile?.gotoDir(folder) ?: throw IOException()
|
||||||
cr.getExistingDownloadUriOrNullQ(
|
val foundFile = subDir.findFile(displayName)
|
||||||
folder ?: "",
|
|
||||||
displayName
|
|
||||||
) // CURRENT FILE WITH THE SAME PATH
|
|
||||||
|
|
||||||
fileLength =
|
val (file, fileLength) = if (foundFile == null || !foundFile.exists()) {
|
||||||
if (currentExistingFile == null || !resume) 0 else (cr.getFileLength(
|
subDir.createFileOrThrow(displayName) to 0L
|
||||||
currentExistingFile
|
|
||||||
)
|
|
||||||
?: 0)// IF NOT RESUME THEN 0, OTHERWISE THE CURRENT FILE SIZE
|
|
||||||
|
|
||||||
if (!resume && currentExistingFile != null) { // DELETE FILE IF FILE EXITS AND NOT RESUME
|
|
||||||
val rowsDeleted = context.contentResolver.delete(currentExistingFile, null, null)
|
|
||||||
if (rowsDeleted < 1) {
|
|
||||||
println("ERROR DELETING FILE!!!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var appendFile = false
|
|
||||||
val newFileUri = if (resume && currentExistingFile != null) {
|
|
||||||
appendFile = true
|
|
||||||
currentExistingFile
|
|
||||||
} else {
|
|
||||||
val contentUri =
|
|
||||||
MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
|
||||||
//val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
|
||||||
val currentMimeType = when (extension) {
|
|
||||||
|
|
||||||
// Absolutely ridiculous, if text/vtt is used as mimetype scoped storage prevents
|
|
||||||
// downloading to /Downloads yet it works with null
|
|
||||||
|
|
||||||
"vtt" -> null // "text/vtt"
|
|
||||||
"mp4" -> "video/mp4"
|
|
||||||
"srt" -> null // "application/x-subrip"//"text/plain"
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
val newFile = ContentValues().apply {
|
|
||||||
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
|
|
||||||
put(MediaStore.MediaColumns.TITLE, name)
|
|
||||||
if (currentMimeType != null)
|
|
||||||
put(MediaStore.MediaColumns.MIME_TYPE, currentMimeType)
|
|
||||||
put(MediaStore.MediaColumns.RELATIVE_PATH, folder)
|
|
||||||
}
|
|
||||||
|
|
||||||
cr.insert(
|
|
||||||
contentUri,
|
|
||||||
newFile
|
|
||||||
) ?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileStream = cr.openOutputStream(newFileUri, "w" + (if (appendFile) "a" else ""))
|
|
||||||
?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND)
|
|
||||||
} else {
|
} else {
|
||||||
val subDir = baseFile.first?.gotoDir(folder)
|
if (tryResume) {
|
||||||
val rFile = subDir?.findFile(displayName)
|
foundFile to foundFile.size()
|
||||||
if (rFile?.exists() != true) {
|
|
||||||
fileLength = 0
|
|
||||||
if (subDir?.createFile(displayName) == null) return StreamData(ERROR_CREATE_FILE)
|
|
||||||
} else {
|
} else {
|
||||||
if (resume) {
|
foundFile.deleteOrThrow()
|
||||||
fileLength = rFile.size()
|
subDir.createFileOrThrow(displayName) to 0L
|
||||||
} else {
|
|
||||||
fileLength = 0
|
|
||||||
if (!rFile.delete()) return StreamData(ERROR_DELETING_FILE)
|
|
||||||
if (subDir.createFile(displayName) == null) return StreamData(ERROR_CREATE_FILE)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fileStream = (subDir.findFile(displayName)
|
|
||||||
?: subDir.createFile(displayName))!!.openOutputStream()
|
|
||||||
// fileStream = FileOutputStream(rFile, false)
|
|
||||||
if (fileLength == 0L) resume = false
|
|
||||||
}
|
}
|
||||||
return StreamData(SUCCESS_STREAM, resume, fileLength, fileStream)
|
|
||||||
|
return StreamData(fileLength, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This class handles the notifications, as well as the relevant key */
|
/** This class handles the notifications, as well as the relevant key */
|
||||||
|
@ -938,6 +791,8 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
fun setWrittenSegment(segmentIndex: Int) {
|
fun setWrittenSegment(segmentIndex: Int) {
|
||||||
hlsWrittenProgress = segmentIndex + 1
|
hlsWrittenProgress = segmentIndex + 1
|
||||||
|
// in case of abort we need to save every written progress
|
||||||
|
updateFileInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1185,18 +1040,16 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
// set up the download file
|
// set up the download file
|
||||||
val stream = setupStream(context, name, relativePath, extension, tryResume)
|
val stream = setupStream(context, name, relativePath, extension, tryResume)
|
||||||
if (stream.errorCode != SUCCESS_STREAM) return@withContext stream.errorCode
|
|
||||||
fileStream = stream.fileStream ?: return@withContext ERROR_UNKNOWN
|
fileStream = stream.open()
|
||||||
val resume = stream.resume ?: return@withContext ERROR_UNKNOWN
|
|
||||||
val fileLength = stream.fileLength ?: return@withContext ERROR_UNKNOWN
|
metadata.setResumeLength(stream.startAt)
|
||||||
val resumeAt = (if (resume) fileLength else 0)
|
|
||||||
metadata.setResumeLength(resumeAt)
|
|
||||||
metadata.type = DownloadType.IsPending
|
metadata.type = DownloadType.IsPending
|
||||||
|
|
||||||
val items = streamLazy(
|
val items = streamLazy(
|
||||||
url = link.url.replace(" ", "%20"),
|
url = link.url.replace(" ", "%20"),
|
||||||
referer = link.referer,
|
referer = link.referer,
|
||||||
startByte = resumeAt,
|
startByte = stream.startAt,
|
||||||
headers = link.headers.appendAndDontOverride(
|
headers = link.headers.appendAndDontOverride(
|
||||||
mapOf(
|
mapOf(
|
||||||
"Accept-Encoding" to "identity",
|
"Accept-Encoding" to "identity",
|
||||||
|
@ -1230,6 +1083,19 @@ object VideoDownloadManager {
|
||||||
val pendingData: HashMap<Long, LazyStreamDownloadResponse> =
|
val pendingData: HashMap<Long, LazyStreamDownloadResponse> =
|
||||||
hashMapOf()
|
hashMapOf()
|
||||||
|
|
||||||
|
val fileChecker = launch(Dispatchers.IO) {
|
||||||
|
while (isActive) {
|
||||||
|
if (stream.exists) {
|
||||||
|
delay(5000)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fileMutex.withLock {
|
||||||
|
metadata.type = DownloadType.IsStopped
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val jobs = (0 until parallelConnections).map {
|
val jobs = (0 until parallelConnections).map {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
|
|
||||||
|
@ -1329,9 +1195,11 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
jobs.join()
|
jobs.join()
|
||||||
|
fileChecker.cancel()
|
||||||
|
|
||||||
// jobs are finished so we don't want to stop them anymore
|
// jobs are finished so we don't want to stop them anymore
|
||||||
metadata.removeStopListener()
|
metadata.removeStopListener()
|
||||||
|
if (!stream.exists) metadata.type = DownloadType.IsStopped
|
||||||
|
|
||||||
if (metadata.type == DownloadType.IsFailed) {
|
if (metadata.type == DownloadType.IsFailed) {
|
||||||
return@withContext ERROR_CONNECTION_ERROR
|
return@withContext ERROR_CONNECTION_ERROR
|
||||||
|
@ -1341,11 +1209,8 @@ object VideoDownloadManager {
|
||||||
// we need to close before delete
|
// we need to close before delete
|
||||||
fileStream.closeQuietly()
|
fileStream.closeQuietly()
|
||||||
metadata.onDelete()
|
metadata.onDelete()
|
||||||
if (deleteFile(context, baseFile, relativePath ?: "", displayName)) {
|
deleteFile(context, baseFile, relativePath ?: "", displayName)
|
||||||
return@withContext SUCCESS_STOPPED
|
return@withContext SUCCESS_STOPPED
|
||||||
} else {
|
|
||||||
return@withContext ERROR_DELETING_FILE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.type = DownloadType.IsDone
|
metadata.type = DownloadType.IsDone
|
||||||
|
@ -1400,13 +1265,13 @@ object VideoDownloadManager {
|
||||||
folder
|
folder
|
||||||
) else folder
|
) else folder
|
||||||
val displayName = getDisplayName(name, extension)
|
val displayName = getDisplayName(name, extension)
|
||||||
val stream = setupStream(context, name, relativePath, extension, startAt > 0)
|
val stream =
|
||||||
if (stream.errorCode != SUCCESS_STREAM) return@withContext stream.errorCode
|
setupStream(context, name, relativePath, extension, startAt > 0)
|
||||||
if (stream.resume != true) startAt = 0
|
if (!stream.resume) startAt = 0
|
||||||
fileStream = stream.fileStream ?: return@withContext ERROR_UNKNOWN
|
fileStream = stream.open()
|
||||||
|
|
||||||
// push the metadata
|
// push the metadata
|
||||||
metadata.setResumeLength(stream.fileLength ?: 0)
|
metadata.setResumeLength(stream.startAt)
|
||||||
metadata.hlsProgress = startAt
|
metadata.hlsProgress = startAt
|
||||||
metadata.type = DownloadType.IsPending
|
metadata.type = DownloadType.IsPending
|
||||||
metadata.setDownloadFileInfoTemplate(
|
metadata.setDownloadFileInfoTemplate(
|
||||||
|
@ -1433,13 +1298,25 @@ object VideoDownloadManager {
|
||||||
metadata.hlsTotal = items.size
|
metadata.hlsTotal = items.size
|
||||||
metadata.type = DownloadType.IsDownloading
|
metadata.type = DownloadType.IsDownloading
|
||||||
|
|
||||||
|
|
||||||
val currentMutex = Mutex()
|
val currentMutex = Mutex()
|
||||||
val current = (0 until items.size).iterator()
|
val current = (startAt until items.size).iterator()
|
||||||
|
|
||||||
val fileMutex = Mutex()
|
val fileMutex = Mutex()
|
||||||
val pendingData: HashMap<Int, ByteArray> = hashMapOf()
|
val pendingData: HashMap<Int, ByteArray> = hashMapOf()
|
||||||
|
|
||||||
|
val fileChecker = launch(Dispatchers.IO) {
|
||||||
|
while (isActive) {
|
||||||
|
if (stream.exists) {
|
||||||
|
delay(5000)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fileMutex.withLock {
|
||||||
|
metadata.type = DownloadType.IsStopped
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// see @downloadexplanation for explanation of this download strategy,
|
// see @downloadexplanation for explanation of this download strategy,
|
||||||
// this keeps all jobs working at all times,
|
// this keeps all jobs working at all times,
|
||||||
// does several connections in parallel instead of a regular for loop to improve
|
// does several connections in parallel instead of a regular for loop to improve
|
||||||
|
@ -1476,7 +1353,7 @@ object VideoDownloadManager {
|
||||||
// user pause
|
// user pause
|
||||||
while (metadata.type == DownloadType.IsPaused) delay(100)
|
while (metadata.type == DownloadType.IsPaused) delay(100)
|
||||||
// if stopped then break to delete
|
// if stopped then break to delete
|
||||||
if (metadata.type == DownloadType.IsStopped || !isActive) return@launch
|
if (metadata.type == DownloadType.IsStopped || metadata.type == DownloadType.IsFailed || !isActive) return@launch
|
||||||
|
|
||||||
val segmentLength = bytes.size.toLong()
|
val segmentLength = bytes.size.toLong()
|
||||||
// send notification, no matter the actual write order
|
// send notification, no matter the actual write order
|
||||||
|
@ -1499,11 +1376,13 @@ object VideoDownloadManager {
|
||||||
val cacheLength = cache.size.toLong()
|
val cacheLength = cache.size.toLong()
|
||||||
|
|
||||||
fileStream.write(cache)
|
fileStream.write(cache)
|
||||||
|
|
||||||
metadata.addBytesWritten(cacheLength)
|
metadata.addBytesWritten(cacheLength)
|
||||||
metadata.setWrittenSegment(metadata.hlsWrittenProgress)
|
metadata.setWrittenSegment(metadata.hlsWrittenProgress)
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
// this is in case of write fail
|
// this is in case of write fail
|
||||||
|
logError(t)
|
||||||
if (metadata.type != DownloadType.IsStopped) {
|
if (metadata.type != DownloadType.IsStopped) {
|
||||||
metadata.type = DownloadType.IsFailed
|
metadata.type = DownloadType.IsFailed
|
||||||
}
|
}
|
||||||
|
@ -1520,9 +1399,12 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
jobs.join()
|
jobs.join()
|
||||||
|
fileChecker.cancel()
|
||||||
|
|
||||||
metadata.removeStopListener()
|
metadata.removeStopListener()
|
||||||
|
|
||||||
|
if (!stream.exists) metadata.type = DownloadType.IsStopped
|
||||||
|
|
||||||
if (metadata.type == DownloadType.IsFailed) {
|
if (metadata.type == DownloadType.IsFailed) {
|
||||||
return@withContext ERROR_CONNECTION_ERROR
|
return@withContext ERROR_CONNECTION_ERROR
|
||||||
}
|
}
|
||||||
|
@ -1531,11 +1413,8 @@ object VideoDownloadManager {
|
||||||
// we need to close before delete
|
// we need to close before delete
|
||||||
fileStream.closeQuietly()
|
fileStream.closeQuietly()
|
||||||
metadata.onDelete()
|
metadata.onDelete()
|
||||||
if (deleteFile(context, baseFile, relativePath ?: "", displayName)) {
|
deleteFile(context, baseFile, relativePath ?: "", displayName)
|
||||||
return@withContext SUCCESS_STOPPED
|
return@withContext SUCCESS_STOPPED
|
||||||
} else {
|
|
||||||
return@withContext ERROR_DELETING_FILE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.type = DownloadType.IsDone
|
metadata.type = DownloadType.IsDone
|
||||||
|
@ -1564,6 +1443,11 @@ object VideoDownloadManager {
|
||||||
directoryName: String?,
|
directoryName: String?,
|
||||||
createMissingDirectories: Boolean = true
|
createMissingDirectories: Boolean = true
|
||||||
): UniFile? {
|
): UniFile? {
|
||||||
|
if(directoryName == null) return this
|
||||||
|
|
||||||
|
return directoryName.split(File.separatorChar).filter { it.isNotBlank() }.fold(this) { file: UniFile?, directory ->
|
||||||
|
file?.createDirectory(directory)
|
||||||
|
}
|
||||||
|
|
||||||
// May give this error on scoped storage.
|
// May give this error on scoped storage.
|
||||||
// W/DocumentsContract: Failed to create document
|
// W/DocumentsContract: Failed to create document
|
||||||
|
@ -1571,7 +1455,7 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
// Not present in latest testing.
|
// Not present in latest testing.
|
||||||
|
|
||||||
// println("Going to dir $directoryName from ${this.uri} ---- ${this.filePath}")
|
println("Going to dir $directoryName from ${this.uri} ---- ${this.filePath}")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Creates itself from parent if doesn't exist.
|
// Creates itself from parent if doesn't exist.
|
||||||
|
@ -1671,49 +1555,6 @@ object VideoDownloadManager {
|
||||||
return this != null && this.filePath == getDownloadDir()?.filePath
|
return this != null && this.filePath == getDownloadDir()?.filePath
|
||||||
}
|
}
|
||||||
|
|
||||||
/*private fun delete(
|
|
||||||
context: Context,
|
|
||||||
name: String,
|
|
||||||
folder: String?,
|
|
||||||
extension: String,
|
|
||||||
parentId: Int?,
|
|
||||||
basePath: UniFile?
|
|
||||||
): Int {
|
|
||||||
val displayName = getDisplayName(name, extension)
|
|
||||||
|
|
||||||
// delete all subtitle files
|
|
||||||
if (extension != "vtt" && extension != "srt") {
|
|
||||||
try {
|
|
||||||
delete(context, name, folder, "vtt", parentId, basePath)
|
|
||||||
delete(context, name, folder, "srt", parentId, basePath)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If scoped storage and using download dir (not accessible with UniFile)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && basePath.isDownloadDir()) {
|
|
||||||
val relativePath = getRelativePath(folder)
|
|
||||||
val lastContent =
|
|
||||||
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)
|
|
||||||
val file = dir?.findFile(displayName)
|
|
||||||
val success = file?.delete()
|
|
||||||
if (success != true) return ERROR_DELETING_FILE else {
|
|
||||||
// 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 {
|
fun getFileName(context: Context, metadata: DownloadEpisodeMetadata): String {
|
||||||
return getFileName(context, metadata.name, metadata.episode, metadata.season)
|
return getFileName(context, metadata.name, metadata.episode, metadata.season)
|
||||||
|
@ -1765,70 +1606,60 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (link.isM3u8 || URL(link.url).path.endsWith(".m3u8")) {
|
val callback: (CreateNotificationMetadata) -> Unit = { meta ->
|
||||||
val startIndex = if (tryResume) {
|
main {
|
||||||
context.getKey<DownloadedFileInfo>(
|
createNotification(
|
||||||
KEY_DOWNLOAD_INFO,
|
context,
|
||||||
ep.id.toString(),
|
source,
|
||||||
null
|
link.name,
|
||||||
)?.extraInfo?.toIntOrNull()
|
ep,
|
||||||
} else null
|
meta.type,
|
||||||
return suspendSafeApiCall {
|
meta.bytesDownloaded,
|
||||||
downloadHLS(
|
meta.bytesTotal,
|
||||||
|
notificationCallback,
|
||||||
|
meta.hlsProgress,
|
||||||
|
meta.hlsTotal,
|
||||||
|
meta.bytesPerSecond
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (link.isM3u8 || normalSafeApiCall { URL(link.url).path.endsWith(".m3u8") } == true) {
|
||||||
|
val startIndex = if (tryResume) {
|
||||||
|
context.getKey<DownloadedFileInfo>(
|
||||||
|
KEY_DOWNLOAD_INFO,
|
||||||
|
ep.id.toString(),
|
||||||
|
null
|
||||||
|
)?.extraInfo?.toIntOrNull()
|
||||||
|
} else null
|
||||||
|
|
||||||
|
return downloadHLS(
|
||||||
context,
|
context,
|
||||||
link,
|
link,
|
||||||
name,
|
name,
|
||||||
folder,
|
folder,
|
||||||
ep.id,
|
ep.id,
|
||||||
startIndex,
|
startIndex,
|
||||||
createNotificationCallback = { meta ->
|
callback
|
||||||
main {
|
|
||||||
createNotification(
|
|
||||||
context,
|
|
||||||
source,
|
|
||||||
link.name,
|
|
||||||
ep,
|
|
||||||
meta.type,
|
|
||||||
meta.bytesDownloaded,
|
|
||||||
meta.bytesTotal,
|
|
||||||
notificationCallback,
|
|
||||||
meta.hlsProgress,
|
|
||||||
meta.hlsTotal,
|
|
||||||
meta.bytesPerSecond
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}.also {
|
} else {
|
||||||
extractorJob.cancel()
|
return downloadThing(
|
||||||
} ?: ERROR_UNKNOWN
|
context,
|
||||||
|
link,
|
||||||
|
name,
|
||||||
|
folder,
|
||||||
|
"mp4",
|
||||||
|
tryResume,
|
||||||
|
ep.id,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
return ERROR_UNKNOWN
|
||||||
|
} finally {
|
||||||
|
extractorJob.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
return suspendSafeApiCall {
|
|
||||||
downloadThing(
|
|
||||||
context,
|
|
||||||
link,
|
|
||||||
name,
|
|
||||||
folder,
|
|
||||||
"mp4",
|
|
||||||
tryResume,
|
|
||||||
ep.id,
|
|
||||||
createNotificationCallback = { meta ->
|
|
||||||
main {
|
|
||||||
createNotification(
|
|
||||||
context,
|
|
||||||
source,
|
|
||||||
link.name,
|
|
||||||
ep,
|
|
||||||
meta.type,
|
|
||||||
meta.bytesDownloaded,
|
|
||||||
meta.bytesTotal,
|
|
||||||
notificationCallback,
|
|
||||||
bytesPerSecond = meta.bytesPerSecond
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}.also { extractorJob.cancel() } ?: ERROR_UNKNOWN
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadCheck(
|
suspend fun downloadCheck(
|
||||||
|
@ -1911,26 +1742,10 @@ object VideoDownloadManager {
|
||||||
val info =
|
val info =
|
||||||
context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return null
|
context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return null
|
||||||
val base = basePathToFile(context, info.basePath)
|
val base = basePathToFile(context, info.basePath)
|
||||||
|
val file = base?.gotoDir(info.relativePath, false)?.findFile(info.displayName)
|
||||||
|
if (file?.exists() != true) return null
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && base.isDownloadDir()) {
|
return DownloadedFileInfoResult(file.size(), info.totalBytes, file.uri)
|
||||||
val cr = context.contentResolver ?: return null
|
|
||||||
val fileUri =
|
|
||||||
cr.getExistingDownloadUriOrNullQ(info.relativePath, info.displayName)
|
|
||||||
?: return null
|
|
||||||
val fileLength = cr.getFileLength(fileUri) ?: return null
|
|
||||||
if (fileLength == 0L) return null
|
|
||||||
return DownloadedFileInfoResult(fileLength, info.totalBytes, fileUri)
|
|
||||||
} else {
|
|
||||||
|
|
||||||
val file = base?.gotoDir(info.relativePath, false)?.findFile(info.displayName)
|
|
||||||
|
|
||||||
// val normalPath = context.getNormalPath(getFile(info.relativePath), info.displayName)
|
|
||||||
// val dFile = File(normalPath)
|
|
||||||
|
|
||||||
if (file?.exists() != true) return null
|
|
||||||
|
|
||||||
return DownloadedFileInfoResult(file.size(), info.totalBytes, file.uri)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
return null
|
return null
|
||||||
|
@ -1943,6 +1758,7 @@ object VideoDownloadManager {
|
||||||
fun UniFile.size(): Long {
|
fun UniFile.size(): Long {
|
||||||
val len = length()
|
val len = length()
|
||||||
return if (len <= 1) {
|
return if (len <= 1) {
|
||||||
|
println("LEN:::::::>>>>>>>>>>>>>>>>>>>>>>>$len")
|
||||||
val inputStream = this.openInputStream()
|
val inputStream = this.openInputStream()
|
||||||
return inputStream.available().toLong().also { inputStream.closeQuietly() }
|
return inputStream.available().toLong().also { inputStream.closeQuietly() }
|
||||||
} else {
|
} else {
|
||||||
|
@ -1962,32 +1778,20 @@ object VideoDownloadManager {
|
||||||
relativePath: String,
|
relativePath: String,
|
||||||
displayName: String
|
displayName: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && folder.isDownloadDir()) {
|
val file = folder?.gotoDir(relativePath)?.findFile(displayName) ?: return false
|
||||||
val cr = context.contentResolver ?: return false
|
if (!file.exists()) return true
|
||||||
val fileUri =
|
return try {
|
||||||
cr.getExistingDownloadUriOrNullQ(relativePath, displayName)
|
file.delete()
|
||||||
?: return true // FILE NOT FOUND, ALREADY DELETED
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
return cr.delete(fileUri, null, null) > 0 // IF DELETED ROWS IS OVER 0
|
(context.contentResolver?.delete(file.uri, null, null) ?: return false) > 0
|
||||||
} else {
|
|
||||||
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
|
|
||||||
return try {
|
|
||||||
file.delete()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logError(e)
|
|
||||||
val cr = context.contentResolver
|
|
||||||
cr.delete(file.uri, null, null) > 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteFile(context: Context, id: Int): Boolean {
|
private fun deleteFile(context: Context, id: Int): Boolean {
|
||||||
val info =
|
val info =
|
||||||
context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return false
|
context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return false
|
||||||
downloadEvent.invoke(Pair(id, DownloadActionType.Stop))
|
downloadEvent.invoke(id to DownloadActionType.Stop)
|
||||||
downloadProgressEvent.invoke(Triple(id, 0, 0))
|
downloadProgressEvent.invoke(Triple(id, 0, 0))
|
||||||
downloadStatusEvent.invoke(id to DownloadType.IsStopped)
|
downloadStatusEvent.invoke(id to DownloadType.IsStopped)
|
||||||
downloadDeleteEvent.invoke(id)
|
downloadDeleteEvent.invoke(id)
|
||||||
|
|
Loading…
Reference in a new issue