mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-28 22:27:14 +02:00
Update:Book downloads will go in author/title subfolders #194
This commit is contained in:
@@ -222,14 +222,16 @@ class FolderScanner(var ctx: Context) {
|
|||||||
// Scan item after download and create local library item
|
// Scan item after download and create local library item
|
||||||
fun scanDownloadItem(downloadItem: DownloadItem):DownloadItemScanResult? {
|
fun scanDownloadItem(downloadItem: DownloadItem):DownloadItemScanResult? {
|
||||||
val folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl))
|
val folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl))
|
||||||
val foldersFound = folderDf?.search(false, DocumentFileType.FOLDER) ?: mutableListOf()
|
val foldersFound = folderDf?.search(true, DocumentFileType.FOLDER) ?: mutableListOf()
|
||||||
|
|
||||||
var itemFolderId = ""
|
var itemFolderId = ""
|
||||||
var itemFolderUrl = ""
|
var itemFolderUrl = ""
|
||||||
var itemFolderBasePath = ""
|
var itemFolderBasePath = ""
|
||||||
var itemFolderAbsolutePath = ""
|
var itemFolderAbsolutePath = ""
|
||||||
foldersFound.forEach {
|
foldersFound.forEach {
|
||||||
if (it.name == downloadItem.itemTitle) {
|
// e.g. absolute path is "storage/emulated/0/Audiobooks/Orson Scott Card/Enders Game"
|
||||||
|
// and itemSubfolder is "Orson Scott Card/Enders Game"
|
||||||
|
if (it.getAbsolutePath(ctx).endsWith(downloadItem.itemSubfolder)) {
|
||||||
itemFolderId = it.id
|
itemFolderId = it.id
|
||||||
itemFolderUrl = it.uri.toString()
|
itemFolderUrl = it.uri.toString()
|
||||||
itemFolderBasePath = it.getBasePath(ctx)
|
itemFolderBasePath = it.getBasePath(ctx)
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner
|
|||||||
Failed
|
Failed
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadItemQueue: MutableList<DownloadItem> = mutableListOf()
|
var downloadItemQueue: MutableList<DownloadItem> = mutableListOf() // All pending and downloading items
|
||||||
var currentDownloadItemParts: MutableList<DownloadItemPart> = mutableListOf()
|
var currentDownloadItemParts: MutableList<DownloadItemPart> = mutableListOf() // Item parts currently being downloaded
|
||||||
|
|
||||||
interface DownloadEventEmitter {
|
interface DownloadEventEmitter {
|
||||||
fun onDownloadItem(downloadItem:DownloadItem)
|
fun onDownloadItem(downloadItem:DownloadItem)
|
||||||
@@ -175,11 +175,13 @@ class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner
|
|||||||
override fun onCompleted(result:Any) {
|
override fun onCompleted(result:Any) {
|
||||||
Log.d(tag, "DOWNLOAD: FILE MOVE COMPLETED")
|
Log.d(tag, "DOWNLOAD: FILE MOVE COMPLETED")
|
||||||
val resultDocFile = result as DocumentFile
|
val resultDocFile = result as DocumentFile
|
||||||
Log.d(tag, "DOWNLOAD: COMPLETED FILE INFO ${resultDocFile.getAbsolutePath(mainActivity)}")
|
Log.d(tag, "DOWNLOAD: COMPLETED FILE INFO (name=${resultDocFile.name}) ${resultDocFile.getAbsolutePath(mainActivity)}")
|
||||||
|
|
||||||
// Rename to fix appended .mp4 on m4b files
|
// Rename to fix appended .mp3 on m4b/m4a files
|
||||||
// REF: https://github.com/anggrayudi/SimpleStorage/issues/94
|
// REF: https://github.com/anggrayudi/SimpleStorage/issues/94
|
||||||
resultDocFile.renameTo(downloadItemPart.filename)
|
if (resultDocFile.name?.endsWith(".m4b.mp3") == true || resultDocFile.name?.endsWith(".m4a.mp3") == true) {
|
||||||
|
resultDocFile.renameTo(downloadItemPart.filename)
|
||||||
|
}
|
||||||
|
|
||||||
downloadItemPart.moved = true
|
downloadItemPart.moved = true
|
||||||
downloadItemPart.isMoving = false
|
downloadItemPart.isMoving = false
|
||||||
@@ -198,7 +200,7 @@ class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner
|
|||||||
} else {
|
} else {
|
||||||
downloadItemPart.isMoving = true
|
downloadItemPart.isMoving = true
|
||||||
val mimetype = if (downloadItemPart.audioTrack != null) MimeType.AUDIO else MimeType.IMAGE
|
val mimetype = if (downloadItemPart.audioTrack != null) MimeType.AUDIO else MimeType.IMAGE
|
||||||
val fileDescription = FileDescription(downloadItemPart.filename, downloadItemPart.itemTitle, mimetype)
|
val fileDescription = FileDescription(downloadItemPart.filename, downloadItemPart.finalDestinationSubfolder, mimetype)
|
||||||
file?.moveFileTo(mainActivity, localFolderFile, fileDescription, fcb)
|
file?.moveFileTo(mainActivity, localFolderFile, fileDescription, fcb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ data class DownloadItem(
|
|||||||
val itemFolderPath:String,
|
val itemFolderPath:String,
|
||||||
val localFolder: LocalFolder,
|
val localFolder: LocalFolder,
|
||||||
val itemTitle: String,
|
val itemTitle: String,
|
||||||
|
val itemSubfolder: String,
|
||||||
val media: MediaType,
|
val media: MediaType,
|
||||||
val downloadItemParts: MutableList<DownloadItemPart>
|
val downloadItemParts: MutableList<DownloadItemPart>
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ data class DownloadItemPart(
|
|||||||
val downloadItemId: String,
|
val downloadItemId: String,
|
||||||
val filename: String,
|
val filename: String,
|
||||||
val finalDestinationPath:String,
|
val finalDestinationPath:String,
|
||||||
val itemTitle: String,
|
|
||||||
val serverPath: String,
|
val serverPath: String,
|
||||||
val localFolderName: String,
|
val localFolderName: String,
|
||||||
val localFolderUrl: String,
|
val localFolderUrl: String,
|
||||||
@@ -30,11 +29,12 @@ data class DownloadItemPart(
|
|||||||
@JsonIgnore val uri: Uri,
|
@JsonIgnore val uri: Uri,
|
||||||
@JsonIgnore val destinationUri: Uri,
|
@JsonIgnore val destinationUri: Uri,
|
||||||
@JsonIgnore val finalDestinationUri: Uri,
|
@JsonIgnore val finalDestinationUri: Uri,
|
||||||
|
val finalDestinationSubfolder: String,
|
||||||
var downloadId: Long?,
|
var downloadId: Long?,
|
||||||
var progress: Long
|
var progress: Long
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun make(downloadItemId:String, filename:String, destinationFile: File, finalDestinationFile: File, itemTitle:String, serverPath:String, localFolder: LocalFolder, audioTrack: AudioTrack?, episode: PodcastEpisode?) :DownloadItemPart {
|
fun make(downloadItemId:String, filename:String, destinationFile: File, finalDestinationFile: File, subfolder:String, serverPath:String, localFolder: LocalFolder, audioTrack: AudioTrack?, episode: PodcastEpisode?) :DownloadItemPart {
|
||||||
val destinationUri = Uri.fromFile(destinationFile)
|
val destinationUri = Uri.fromFile(destinationFile)
|
||||||
val finalDestinationUri = Uri.fromFile(finalDestinationFile)
|
val finalDestinationUri = Uri.fromFile(finalDestinationFile)
|
||||||
|
|
||||||
@@ -47,7 +47,6 @@ data class DownloadItemPart(
|
|||||||
downloadItemId,
|
downloadItemId,
|
||||||
filename = filename,
|
filename = filename,
|
||||||
finalDestinationPath = finalDestinationFile.absolutePath,
|
finalDestinationPath = finalDestinationFile.absolutePath,
|
||||||
itemTitle = itemTitle,
|
|
||||||
serverPath = serverPath,
|
serverPath = serverPath,
|
||||||
localFolderName = localFolder.name,
|
localFolderName = localFolder.name,
|
||||||
localFolderUrl = localFolder.contentUrl,
|
localFolderUrl = localFolder.contentUrl,
|
||||||
@@ -61,6 +60,7 @@ data class DownloadItemPart(
|
|||||||
uri = downloadUri,
|
uri = downloadUri,
|
||||||
destinationUri = destinationUri,
|
destinationUri = destinationUri,
|
||||||
finalDestinationUri = finalDestinationUri,
|
finalDestinationUri = finalDestinationUri,
|
||||||
|
finalDestinationSubfolder = subfolder,
|
||||||
downloadId = null,
|
downloadId = null,
|
||||||
progress = 0
|
progress = 0
|
||||||
)
|
)
|
||||||
@@ -74,7 +74,7 @@ data class DownloadItemPart(
|
|||||||
fun getDownloadRequest(): DownloadManager.Request {
|
fun getDownloadRequest(): DownloadManager.Request {
|
||||||
val dlRequest = DownloadManager.Request(uri)
|
val dlRequest = DownloadManager.Request(uri)
|
||||||
dlRequest.setTitle(filename)
|
dlRequest.setTitle(filename)
|
||||||
dlRequest.setDescription("Downloading to $localFolderName for book $itemTitle")
|
dlRequest.setDescription("Downloading to $localFolderName with filename $filename")
|
||||||
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
|
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
|
||||||
dlRequest.setDestinationUri(destinationUri)
|
dlRequest.setDestinationUri(destinationUri)
|
||||||
return dlRequest
|
return dlRequest
|
||||||
|
|||||||
@@ -2,15 +2,9 @@ package com.audiobookshelf.app.plugins
|
|||||||
|
|
||||||
import android.app.DownloadManager
|
import android.app.DownloadManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import com.anggrayudi.storage.callback.FileCallback
|
|
||||||
import com.anggrayudi.storage.file.*
|
|
||||||
import com.anggrayudi.storage.media.FileDescription
|
|
||||||
import com.anggrayudi.storage.media.MediaStoreCompat
|
|
||||||
import com.audiobookshelf.app.MainActivity
|
import com.audiobookshelf.app.MainActivity
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
@@ -26,10 +20,6 @@ import com.getcapacitor.Plugin
|
|||||||
import com.getcapacitor.PluginCall
|
import com.getcapacitor.PluginCall
|
||||||
import com.getcapacitor.PluginMethod
|
import com.getcapacitor.PluginMethod
|
||||||
import com.getcapacitor.annotation.CapacitorPlugin
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@CapacitorPlugin(name = "AbsDownloader")
|
@CapacitorPlugin(name = "AbsDownloader")
|
||||||
@@ -43,9 +33,7 @@ class AbsDownloader : Plugin() {
|
|||||||
lateinit var folderScanner: FolderScanner
|
lateinit var folderScanner: FolderScanner
|
||||||
lateinit var downloadItemManager: DownloadItemManager
|
lateinit var downloadItemManager: DownloadItemManager
|
||||||
|
|
||||||
private var downloadQueue: MutableList<DownloadItem> = mutableListOf()
|
private val clientEventEmitter = (object : DownloadItemManager.DownloadEventEmitter {
|
||||||
|
|
||||||
val clientEventEmitter = (object : DownloadItemManager.DownloadEventEmitter {
|
|
||||||
override fun onDownloadItem(downloadItem:DownloadItem) {
|
override fun onDownloadItem(downloadItem:DownloadItem) {
|
||||||
notifyListeners("onDownloadItem", JSObject(jacksonMapper.writeValueAsString(downloadItem)))
|
notifyListeners("onDownloadItem", JSObject(jacksonMapper.writeValueAsString(downloadItem)))
|
||||||
}
|
}
|
||||||
@@ -75,7 +63,7 @@ class AbsDownloader : Plugin() {
|
|||||||
Log.d(tag, "Download library item $libraryItemId to folder $localFolderId / episode: $episodeId")
|
Log.d(tag, "Download library item $libraryItemId to folder $localFolderId / episode: $episodeId")
|
||||||
|
|
||||||
val downloadId = if (episodeId.isEmpty()) libraryItemId else "$libraryItemId-$episodeId"
|
val downloadId = if (episodeId.isEmpty()) libraryItemId else "$libraryItemId-$episodeId"
|
||||||
if (downloadQueue.find { it.id == downloadId } != null) {
|
if (downloadItemManager.downloadItemQueue.find { it.id == downloadId } != null) {
|
||||||
Log.d(tag, "Download already started for this media entity $downloadId")
|
Log.d(tag, "Download already started for this media entity $downloadId")
|
||||||
return call.resolve(JSObject("{\"error\":\"Download already started for this media entity\"}"))
|
return call.resolve(JSObject("{\"error\":\"Download already started for this media entity\"}"))
|
||||||
}
|
}
|
||||||
@@ -142,17 +130,18 @@ class AbsDownloader : Plugin() {
|
|||||||
|
|
||||||
private fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder, episode:PodcastEpisode?) {
|
private fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder, episode:PodcastEpisode?) {
|
||||||
val tempFolderPath = mainActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
|
val tempFolderPath = mainActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
|
||||||
// val tempFolderPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
|
|
||||||
Log.d(tag, "downloadCacheDirectory=$tempFolderPath")
|
Log.d(tag, "downloadCacheDirectory=$tempFolderPath")
|
||||||
|
|
||||||
if (libraryItem.mediaType == "book") {
|
if (libraryItem.mediaType == "book") {
|
||||||
val bookTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
|
val bookTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
|
||||||
|
val bookAuthor = cleanStringForFileSystem(libraryItem.media.metadata.getAuthorDisplayName())
|
||||||
|
|
||||||
val tracks = libraryItem.media.getAudioTracks()
|
val tracks = libraryItem.media.getAudioTracks()
|
||||||
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
|
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
|
||||||
val itemFolderPath = "${localFolder.absolutePath}/$bookTitle"
|
val itemSubfolder = "$bookAuthor/$bookTitle"
|
||||||
val downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null, libraryItem.userMediaProgress,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
|
val itemFolderPath = "${localFolder.absolutePath}/$itemSubfolder"
|
||||||
|
val downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null, libraryItem.userMediaProgress,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, itemSubfolder, libraryItem.media, mutableListOf())
|
||||||
|
|
||||||
// Create download item part for each audio track
|
// Create download item part for each audio track
|
||||||
tracks.forEach { audioTrack ->
|
tracks.forEach { audioTrack ->
|
||||||
@@ -173,12 +162,8 @@ class AbsDownloader : Plugin() {
|
|||||||
finalDestinationFile.delete()
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,audioTrack,null)
|
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,itemSubfolder,serverPath,localFolder,audioTrack,null)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
// val dlRequest = downloadItemPart.getDownloadRequest()
|
|
||||||
// val downloadId = downloadManager.enqueue(dlRequest)
|
|
||||||
// downloadItemPart.downloadId = downloadId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.downloadItemParts.isNotEmpty()) {
|
if (downloadItem.downloadItemParts.isNotEmpty()) {
|
||||||
@@ -199,17 +184,10 @@ class AbsDownloader : Plugin() {
|
|||||||
finalDestinationFile.delete()
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,null,null)
|
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,itemSubfolder,serverPath,localFolder,null,null)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
// val dlRequest = downloadItemPart.getDownloadRequest()
|
|
||||||
// val downloadId = downloadManager.enqueue(dlRequest)
|
|
||||||
// downloadItemPart.downloadId = downloadId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadQueue.add(downloadItem)
|
|
||||||
// startWatchingDownloads(downloadItem)
|
|
||||||
// DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
|
||||||
downloadItemManager.addDownloadItem(downloadItem)
|
downloadItemManager.addDownloadItem(downloadItem)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -220,7 +198,7 @@ class AbsDownloader : Plugin() {
|
|||||||
Log.d(tag, "Starting podcast episode download")
|
Log.d(tag, "Starting podcast episode download")
|
||||||
val itemFolderPath = localFolder.absolutePath + "/" + podcastTitle
|
val itemFolderPath = localFolder.absolutePath + "/" + podcastTitle
|
||||||
val downloadItemId = "${libraryItem.id}-${episode?.id}"
|
val downloadItemId = "${libraryItem.id}-${episode?.id}"
|
||||||
val downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, libraryItem.userMediaProgress, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, podcastTitle, libraryItem.media, mutableListOf())
|
val downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, libraryItem.userMediaProgress, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, podcastTitle, podcastTitle, libraryItem.media, mutableListOf())
|
||||||
|
|
||||||
var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack?.relPath ?: "")}"
|
var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack?.relPath ?: "")}"
|
||||||
var destinationFilename = getFilenameFromRelPath(audioTrack?.relPath ?: "")
|
var destinationFilename = getFilenameFromRelPath(audioTrack?.relPath ?: "")
|
||||||
@@ -236,10 +214,6 @@ class AbsDownloader : Plugin() {
|
|||||||
var downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,audioTrack,episode)
|
var downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,audioTrack,episode)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
var dlRequest = downloadItemPart.getDownloadRequest()
|
|
||||||
var downloadId = downloadManager.enqueue(dlRequest)
|
|
||||||
downloadItemPart.downloadId = downloadId
|
|
||||||
|
|
||||||
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
||||||
serverPath = "/api/items/${libraryItem.id}/cover"
|
serverPath = "/api/items/${libraryItem.id}/cover"
|
||||||
destinationFilename = "cover.jpg"
|
destinationFilename = "cover.jpg"
|
||||||
@@ -252,132 +226,10 @@ class AbsDownloader : Plugin() {
|
|||||||
} else {
|
} else {
|
||||||
downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,null,null)
|
downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,null,null)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
dlRequest = downloadItemPart.getDownloadRequest()
|
|
||||||
downloadId = downloadManager.enqueue(dlRequest)
|
|
||||||
downloadItemPart.downloadId = downloadId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadQueue.add(downloadItem)
|
downloadItemManager.addDownloadItem(downloadItem)
|
||||||
startWatchingDownloads(downloadItem)
|
|
||||||
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startWatchingDownloads(downloadItem: DownloadItem) {
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
|
||||||
while (downloadItem.downloadItemParts.find { !it.moved && !it.failed } != null) { // While some item is not completed
|
|
||||||
val numPartsBefore = downloadItem.downloadItemParts.size
|
|
||||||
checkDownloads(downloadItem)
|
|
||||||
|
|
||||||
// Keep database updated as item parts finish downloading
|
|
||||||
if (downloadItem.downloadItemParts.size > 0 && downloadItem.downloadItemParts.size != numPartsBefore) {
|
|
||||||
Log.d(tag, "Save download item on num parts changed from $numPartsBefore to ${downloadItem.downloadItemParts.size}")
|
|
||||||
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyListeners("onItemDownloadUpdate", JSObject(jacksonMapper.writeValueAsString(downloadItem)))
|
|
||||||
delay(500)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove download notifications
|
|
||||||
downloadItem.downloadItemParts.forEach { downloadItemPart ->
|
|
||||||
downloadItemPart.downloadId?.let {
|
|
||||||
downloadManager.remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloadItemScanResult = folderScanner.scanDownloadItem(downloadItem)
|
|
||||||
DeviceManager.dbManager.removeDownloadItem(downloadItem.id)
|
|
||||||
downloadQueue.remove(downloadItem)
|
|
||||||
|
|
||||||
Log.d(tag, "Item download complete ${downloadItem.itemTitle} | local library item id: ${downloadItemScanResult?.localLibraryItem?.id} | Items remaining in Queue ${downloadQueue.size}")
|
|
||||||
|
|
||||||
val jsobj = JSObject()
|
|
||||||
jsobj.put("libraryItemId", downloadItem.id)
|
|
||||||
jsobj.put("localFolderId", downloadItem.localFolder.id)
|
|
||||||
|
|
||||||
downloadItemScanResult?.localLibraryItem?.let { localLibraryItem ->
|
|
||||||
jsobj.put("localLibraryItem", JSObject(jacksonMapper.writeValueAsString(localLibraryItem)))
|
|
||||||
}
|
|
||||||
downloadItemScanResult?.localMediaProgress?.let { localMediaProgress ->
|
|
||||||
jsobj.put("localMediaProgress", JSObject(jacksonMapper.writeValueAsString(localMediaProgress)))
|
|
||||||
}
|
|
||||||
notifyListeners("onItemDownloadComplete", jsobj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkDownloads(downloadItem: DownloadItem) {
|
|
||||||
val itemParts = downloadItem.downloadItemParts.map { it }
|
|
||||||
for (downloadItemPart in itemParts) {
|
|
||||||
if (downloadItemPart.downloadId != null) {
|
|
||||||
val dlid = downloadItemPart.downloadId!!
|
|
||||||
val query = DownloadManager.Query().setFilterById(dlid)
|
|
||||||
downloadManager.query(query).use {
|
|
||||||
if (it.moveToFirst()) {
|
|
||||||
val bytesColumnIndex = it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
|
|
||||||
val statusColumnIndex = it.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
|
||||||
val bytesDownloadedColumnIndex = it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
|
|
||||||
|
|
||||||
val totalBytes = if (bytesColumnIndex >= 0) it.getInt(bytesColumnIndex) else 0
|
|
||||||
val downloadStatus = if (statusColumnIndex >= 0) it.getInt(statusColumnIndex) else 0
|
|
||||||
val bytesDownloadedSoFar = if (bytesDownloadedColumnIndex >= 0) it.getInt(bytesDownloadedColumnIndex) else 0
|
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} bytes $totalBytes | bytes dled $bytesDownloadedSoFar | downloadStatus $downloadStatus")
|
|
||||||
|
|
||||||
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
|
||||||
// Once file download is complete move the file to the final destination
|
|
||||||
if (!downloadItemPart.completed) {
|
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Done")
|
|
||||||
downloadItemPart.completed = true
|
|
||||||
|
|
||||||
val file = DocumentFileCompat.fromUri(mainActivity, downloadItemPart.destinationUri)
|
|
||||||
Log.d(tag, "DOWNLOAD: Attempt move for file at destination ${downloadItemPart.destinationUri} | ${file?.getBasePath(mainActivity)}")
|
|
||||||
|
|
||||||
val fcb = object : FileCallback() {
|
|
||||||
override fun onPrepare() {
|
|
||||||
Log.d(tag, "DOWNLOAD: PREPARING MOVE FILE")
|
|
||||||
}
|
|
||||||
override fun onFailed(errorCode:ErrorCode) {
|
|
||||||
Log.e(tag, "DOWNLOAD: FAILED TO MOVE FILE $errorCode")
|
|
||||||
downloadItemPart.failed = true
|
|
||||||
file?.delete()
|
|
||||||
}
|
|
||||||
override fun onCompleted(result:Any) {
|
|
||||||
Log.d(tag, "DOWNLOAD: FILE MOVE COMPLETED")
|
|
||||||
val resultDocFile = result as DocumentFile
|
|
||||||
Log.d(tag, "DOWNLOAD: COMPLETED FILE INFO ${resultDocFile.getAbsolutePath(mainActivity)}")
|
|
||||||
downloadItemPart.moved = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(tag, "DOWNLOAD: Move file to final destination path: ${downloadItemPart.finalDestinationPath}")
|
|
||||||
val localFolderFile = DocumentFileCompat.fromUri(mainActivity,Uri.parse(downloadItemPart.localFolderUrl))
|
|
||||||
if (localFolderFile == null) {
|
|
||||||
Log.e(tag, "Local Folder File from uri is null")
|
|
||||||
} else {
|
|
||||||
val mimetype = if (downloadItemPart.audioTrack != null) MimeType.AUDIO else MimeType.IMAGE
|
|
||||||
val fileDescription = FileDescription(downloadItemPart.filename, downloadItemPart.itemTitle, mimetype)
|
|
||||||
file?.moveFileTo(mainActivity,localFolderFile,fileDescription,fcb)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Why is kotlin requiring an else here..
|
|
||||||
}
|
|
||||||
} else if (downloadStatus == DownloadManager.STATUS_FAILED) {
|
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Failed")
|
|
||||||
downloadItem.downloadItemParts.remove(downloadItemPart)
|
|
||||||
} else {
|
|
||||||
//update progress
|
|
||||||
val percentProgress = if (totalBytes > 0) ((bytesDownloadedSoFar * 100L) / totalBytes) else 0
|
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Progress = $percentProgress%")
|
|
||||||
downloadItemPart.progress = percentProgress
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.d(tag, "Download ${downloadItemPart.filename} not found in dlmanager")
|
|
||||||
downloadItem.downloadItemParts.remove(downloadItemPart)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ export default {
|
|||||||
async asyncData({ params, store, redirect, app, query }) {
|
async asyncData({ params, store, redirect, app, query }) {
|
||||||
const mediaItemHistory = await app.$db.getMediaItemHistory(params.id)
|
const mediaItemHistory = await app.$db.getMediaItemHistory(params.id)
|
||||||
|
|
||||||
if (!mediaItemHistory) {
|
|
||||||
return redirect('/?error=Media Item Not Found')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: query.title || 'Unknown',
|
title: query.title || 'Unknown',
|
||||||
mediaItemHistory
|
mediaItemHistory
|
||||||
|
|||||||
Reference in New Issue
Block a user