mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-27 22:04:08 +02:00
Update:More accurate progress percentage using bytes, download 1 audio file at a time & currently downloading page #251 #360 #515 #274
This commit is contained in:
@@ -220,7 +220,7 @@ 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, cb: (DownloadItemScanResult?) -> Unit) {
|
||||||
val folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl))
|
val folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl))
|
||||||
val foldersFound = folderDf?.search(true, DocumentFileType.FOLDER) ?: mutableListOf()
|
val foldersFound = folderDf?.search(true, DocumentFileType.FOLDER) ?: mutableListOf()
|
||||||
|
|
||||||
@@ -241,13 +241,13 @@ class FolderScanner(var ctx: Context) {
|
|||||||
|
|
||||||
if (itemFolderUrl == "") {
|
if (itemFolderUrl == "") {
|
||||||
Log.d(tag, "scanDownloadItem failed to find media folder")
|
Log.d(tag, "scanDownloadItem failed to find media folder")
|
||||||
return null
|
return cb(null)
|
||||||
}
|
}
|
||||||
val df: DocumentFile? = DocumentFileCompat.fromUri(ctx, Uri.parse(itemFolderUrl))
|
val df: DocumentFile? = DocumentFileCompat.fromUri(ctx, Uri.parse(itemFolderUrl))
|
||||||
|
|
||||||
if (df == null) {
|
if (df == null) {
|
||||||
Log.e(tag, "Folder Doc File Invalid ${downloadItem.itemFolderPath}")
|
Log.e(tag, "Folder Doc File Invalid ${downloadItem.itemFolderPath}")
|
||||||
return null
|
return cb(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val localLibraryItemId = getLocalLibraryItemId(itemFolderId)
|
val localLibraryItemId = getLocalLibraryItemId(itemFolderId)
|
||||||
@@ -283,7 +283,7 @@ class FolderScanner(var ctx: Context) {
|
|||||||
}
|
}
|
||||||
} else if (itemPart.audioTrack != null) { // Is audio track
|
} else if (itemPart.audioTrack != null) { // Is audio track
|
||||||
val audioTrackFromServer = itemPart.audioTrack
|
val audioTrackFromServer = itemPart.audioTrack
|
||||||
Log.d(tag, "scanDownloadItem: Audio Track from Server index = ${audioTrackFromServer?.index}")
|
Log.d(tag, "scanDownloadItem: Audio Track from Server index = ${audioTrackFromServer.index}")
|
||||||
|
|
||||||
val localFileId = DeviceManager.getBase64Id(docFile.id)
|
val localFileId = DeviceManager.getBase64Id(docFile.id)
|
||||||
val localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
val localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
||||||
@@ -314,7 +314,7 @@ class FolderScanner(var ctx: Context) {
|
|||||||
|
|
||||||
if (audioTracks.isEmpty()) {
|
if (audioTracks.isEmpty()) {
|
||||||
Log.d(tag, "scanDownloadItem did not find any audio tracks in folder for ${downloadItem.itemFolderPath}")
|
Log.d(tag, "scanDownloadItem did not find any audio tracks in folder for ${downloadItem.itemFolderPath}")
|
||||||
return null
|
return cb(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For books sort audio tracks then set
|
// For books sort audio tracks then set
|
||||||
@@ -364,7 +364,7 @@ class FolderScanner(var ctx: Context) {
|
|||||||
|
|
||||||
DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem)
|
DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem)
|
||||||
|
|
||||||
return downloadItemScanResult
|
cb(downloadItemScanResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scanLocalLibraryItem(localLibraryItem:LocalLibraryItem, forceAudioProbe:Boolean):LocalLibraryItemScanResult? {
|
fun scanLocalLibraryItem(localLibraryItem:LocalLibraryItem, forceAudioProbe:Boolean):LocalLibraryItemScanResult? {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner: FolderScanner, var mainActivity: MainActivity, var clientEventEmitter:DownloadEventEmitter) {
|
class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner: FolderScanner, var mainActivity: MainActivity, var clientEventEmitter:DownloadEventEmitter) {
|
||||||
val tag = "DownloadItemManager"
|
val tag = "DownloadItemManager"
|
||||||
private val maxSimultaneousDownloads = 5
|
private val maxSimultaneousDownloads = 1
|
||||||
private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
enum class DownloadCheckStatus {
|
enum class DownloadCheckStatus {
|
||||||
@@ -98,11 +98,11 @@ class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner
|
|||||||
handleDownloadItemPartCheck(downloadCheckStatus, downloadItemPart)
|
handleDownloadItemPartCheck(downloadCheckStatus, downloadItemPart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delay(500)
|
||||||
|
|
||||||
if (currentDownloadItemParts.size < maxSimultaneousDownloads) {
|
if (currentDownloadItemParts.size < maxSimultaneousDownloads) {
|
||||||
checkUpdateDownloadQueue()
|
checkUpdateDownloadQueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(500)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(tag, "Finished watching downloads")
|
Log.d(tag, "Finished watching downloads")
|
||||||
@@ -122,12 +122,14 @@ class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner
|
|||||||
|
|
||||||
val totalBytes = if (bytesColumnIndex >= 0) it.getInt(bytesColumnIndex) else 0
|
val totalBytes = if (bytesColumnIndex >= 0) it.getInt(bytesColumnIndex) else 0
|
||||||
val downloadStatus = if (statusColumnIndex >= 0) it.getInt(statusColumnIndex) else 0
|
val downloadStatus = if (statusColumnIndex >= 0) it.getInt(statusColumnIndex) else 0
|
||||||
val bytesDownloadedSoFar = if (bytesDownloadedColumnIndex >= 0) it.getInt(bytesDownloadedColumnIndex) else 0
|
val bytesDownloadedSoFar = if (bytesDownloadedColumnIndex >= 0) it.getLong(bytesDownloadedColumnIndex) else 0
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} bytes $totalBytes | bytes dled $bytesDownloadedSoFar | downloadStatus $downloadStatus")
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} bytes $totalBytes | bytes dled $bytesDownloadedSoFar | downloadStatus $downloadStatus")
|
||||||
|
|
||||||
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Successful")
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Successful")
|
||||||
downloadItemPart.completed = true
|
downloadItemPart.completed = true
|
||||||
|
downloadItemPart.progress = 1
|
||||||
|
downloadItemPart.bytesDownloaded = bytesDownloadedSoFar
|
||||||
return DownloadCheckStatus.Successful
|
return DownloadCheckStatus.Successful
|
||||||
} else if (downloadStatus == DownloadManager.STATUS_FAILED) {
|
} else if (downloadStatus == DownloadManager.STATUS_FAILED) {
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Failed")
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Failed")
|
||||||
@@ -139,6 +141,7 @@ class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner
|
|||||||
val percentProgress = if (totalBytes > 0) ((bytesDownloadedSoFar * 100L) / totalBytes) else 0
|
val percentProgress = if (totalBytes > 0) ((bytesDownloadedSoFar * 100L) / totalBytes) else 0
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Progress = $percentProgress%")
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Progress = $percentProgress%")
|
||||||
downloadItemPart.progress = percentProgress
|
downloadItemPart.progress = percentProgress
|
||||||
|
downloadItemPart.bytesDownloaded = bytesDownloadedSoFar
|
||||||
return DownloadCheckStatus.InProgress
|
return DownloadCheckStatus.InProgress
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -214,23 +217,24 @@ class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner
|
|||||||
if (downloadItem.isDownloadFinished) {
|
if (downloadItem.isDownloadFinished) {
|
||||||
Log.i(tag, "Download Item finished ${downloadItem.media.metadata.title}")
|
Log.i(tag, "Download Item finished ${downloadItem.media.metadata.title}")
|
||||||
|
|
||||||
val downloadItemScanResult = folderScanner.scanDownloadItem(downloadItem)
|
folderScanner.scanDownloadItem(downloadItem) { downloadItemScanResult ->
|
||||||
Log.d(tag, "Item download complete ${downloadItem.itemTitle} | local library item id: ${downloadItemScanResult?.localLibraryItem?.id}")
|
Log.d(tag, "Item download complete ${downloadItem.itemTitle} | local library item id: ${downloadItemScanResult?.localLibraryItem?.id}")
|
||||||
|
|
||||||
val jsobj = JSObject()
|
val jsobj = JSObject()
|
||||||
jsobj.put("libraryItemId", downloadItem.id)
|
jsobj.put("libraryItemId", downloadItem.id)
|
||||||
jsobj.put("localFolderId", downloadItem.localFolder.id)
|
jsobj.put("localFolderId", downloadItem.localFolder.id)
|
||||||
|
|
||||||
downloadItemScanResult?.localLibraryItem?.let { localLibraryItem ->
|
downloadItemScanResult?.localLibraryItem?.let { localLibraryItem ->
|
||||||
jsobj.put("localLibraryItem", JSObject(jacksonMapper.writeValueAsString(localLibraryItem)))
|
jsobj.put("localLibraryItem", JSObject(jacksonMapper.writeValueAsString(localLibraryItem)))
|
||||||
|
}
|
||||||
|
downloadItemScanResult?.localMediaProgress?.let { localMediaProgress ->
|
||||||
|
jsobj.put("localMediaProgress", JSObject(jacksonMapper.writeValueAsString(localMediaProgress)))
|
||||||
|
}
|
||||||
|
|
||||||
|
clientEventEmitter.onDownloadItemComplete(jsobj)
|
||||||
|
downloadItemQueue.remove(downloadItem)
|
||||||
|
DeviceManager.dbManager.removeDownloadItem(downloadItem.id)
|
||||||
}
|
}
|
||||||
downloadItemScanResult?.localMediaProgress?.let { localMediaProgress ->
|
|
||||||
jsobj.put("localMediaProgress", JSObject(jacksonMapper.writeValueAsString(localMediaProgress)))
|
|
||||||
}
|
|
||||||
|
|
||||||
clientEventEmitter.onDownloadItemComplete(jsobj)
|
|
||||||
downloadItemQueue.remove(downloadItem)
|
|
||||||
DeviceManager.dbManager.removeDownloadItem(downloadItem.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ data class DownloadItemPart(
|
|||||||
val id: String,
|
val id: String,
|
||||||
val downloadItemId: String,
|
val downloadItemId: String,
|
||||||
val filename: String,
|
val filename: String,
|
||||||
|
val fileSize: Long,
|
||||||
val finalDestinationPath:String,
|
val finalDestinationPath:String,
|
||||||
val serverPath: String,
|
val serverPath: String,
|
||||||
val localFolderName: String,
|
val localFolderName: String,
|
||||||
@@ -31,10 +32,11 @@ data class DownloadItemPart(
|
|||||||
@JsonIgnore val finalDestinationUri: Uri,
|
@JsonIgnore val finalDestinationUri: Uri,
|
||||||
val finalDestinationSubfolder: String,
|
val finalDestinationSubfolder: String,
|
||||||
var downloadId: Long?,
|
var downloadId: Long?,
|
||||||
var progress: Long
|
var progress: Long,
|
||||||
|
var bytesDownloaded: Long
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun make(downloadItemId:String, filename:String, destinationFile: File, finalDestinationFile: File, subfolder:String, serverPath:String, localFolder: LocalFolder, audioTrack: AudioTrack?, episode: PodcastEpisode?) :DownloadItemPart {
|
fun make(downloadItemId:String, filename:String, fileSize: Long, 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)
|
||||||
|
|
||||||
@@ -46,6 +48,7 @@ data class DownloadItemPart(
|
|||||||
id = DeviceManager.getBase64Id(finalDestinationFile.absolutePath),
|
id = DeviceManager.getBase64Id(finalDestinationFile.absolutePath),
|
||||||
downloadItemId,
|
downloadItemId,
|
||||||
filename = filename,
|
filename = filename,
|
||||||
|
fileSize = fileSize,
|
||||||
finalDestinationPath = finalDestinationFile.absolutePath,
|
finalDestinationPath = finalDestinationFile.absolutePath,
|
||||||
serverPath = serverPath,
|
serverPath = serverPath,
|
||||||
localFolderName = localFolder.name,
|
localFolderName = localFolder.name,
|
||||||
@@ -62,14 +65,12 @@ data class DownloadItemPart(
|
|||||||
finalDestinationUri = finalDestinationUri,
|
finalDestinationUri = finalDestinationUri,
|
||||||
finalDestinationSubfolder = subfolder,
|
finalDestinationSubfolder = subfolder,
|
||||||
downloadId = null,
|
downloadId = null,
|
||||||
progress = 0
|
progress = 0,
|
||||||
|
bytesDownloaded = 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:JsonIgnore
|
|
||||||
val fileSize get() = audioTrack?.metadata?.size ?: 0
|
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getDownloadRequest(): DownloadManager.Request {
|
fun getDownloadRequest(): DownloadManager.Request {
|
||||||
val dlRequest = DownloadManager.Request(uri)
|
val dlRequest = DownloadManager.Request(uri)
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ class AbsDownloader : Plugin() {
|
|||||||
|
|
||||||
// Create download item part for each audio track
|
// Create download item part for each audio track
|
||||||
tracks.forEach { audioTrack ->
|
tracks.forEach { audioTrack ->
|
||||||
|
val fileSize = audioTrack.metadata?.size ?: 0
|
||||||
val serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack.relPath)}"
|
val serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack.relPath)}"
|
||||||
val destinationFilename = getFilenameFromRelPath(audioTrack.relPath)
|
val destinationFilename = getFilenameFromRelPath(audioTrack.relPath)
|
||||||
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
||||||
@@ -162,13 +163,16 @@ class AbsDownloader : Plugin() {
|
|||||||
finalDestinationFile.delete()
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,itemSubfolder,serverPath,localFolder,audioTrack,null)
|
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename, fileSize, destinationFile,finalDestinationFile,itemSubfolder,serverPath,localFolder,audioTrack,null)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.downloadItemParts.isNotEmpty()) {
|
if (downloadItem.downloadItemParts.isNotEmpty()) {
|
||||||
// Add cover download item
|
// Add cover download item
|
||||||
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
||||||
|
val coverLibraryFile = libraryItem.libraryFiles?.find { it.metadata.path == libraryItem.media.coverPath }
|
||||||
|
val coverFileSize = coverLibraryFile?.metadata?.size ?: 0
|
||||||
|
|
||||||
val serverPath = "/api/items/${libraryItem.id}/cover"
|
val serverPath = "/api/items/${libraryItem.id}/cover"
|
||||||
val destinationFilename = "cover-${libraryItem.id}.jpg"
|
val destinationFilename = "cover-${libraryItem.id}.jpg"
|
||||||
val destinationFile = File("$tempFolderPath/$destinationFilename")
|
val destinationFile = File("$tempFolderPath/$destinationFilename")
|
||||||
@@ -184,7 +188,7 @@ class AbsDownloader : Plugin() {
|
|||||||
finalDestinationFile.delete()
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,itemSubfolder,serverPath,localFolder,null,null)
|
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename, coverFileSize, destinationFile,finalDestinationFile,itemSubfolder,serverPath,localFolder,null,null)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +199,8 @@ class AbsDownloader : Plugin() {
|
|||||||
val podcastTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
|
val podcastTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
|
||||||
|
|
||||||
val audioTrack = episode?.audioTrack
|
val audioTrack = episode?.audioTrack
|
||||||
|
val fileSize = audioTrack?.metadata?.size ?: 0
|
||||||
|
|
||||||
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}"
|
||||||
@@ -211,10 +217,13 @@ class AbsDownloader : Plugin() {
|
|||||||
finalDestinationFile.delete()
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,audioTrack,episode)
|
var downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,fileSize, destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,audioTrack,episode)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
||||||
|
val coverLibraryFile = libraryItem.libraryFiles?.find { it.metadata.path == libraryItem.media.coverPath }
|
||||||
|
val coverFileSize = coverLibraryFile?.metadata?.size ?: 0
|
||||||
|
|
||||||
serverPath = "/api/items/${libraryItem.id}/cover"
|
serverPath = "/api/items/${libraryItem.id}/cover"
|
||||||
destinationFilename = "cover.jpg"
|
destinationFilename = "cover.jpg"
|
||||||
|
|
||||||
@@ -224,7 +233,7 @@ class AbsDownloader : Plugin() {
|
|||||||
if (finalDestinationFile.exists()) {
|
if (finalDestinationFile.exists()) {
|
||||||
Log.d(tag, "Podcast cover already exists - not downloading cover again")
|
Log.d(tag, "Podcast cover already exists - not downloading cover again")
|
||||||
} else {
|
} else {
|
||||||
downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,null,null)
|
downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,coverFileSize,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,null,null)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ export default {
|
|||||||
computed: {},
|
computed: {},
|
||||||
methods: {
|
methods: {
|
||||||
updateProgress() {
|
updateProgress() {
|
||||||
var progbar = this.$refs.progressbar
|
const progbar = this.$refs.progressbar
|
||||||
var circle = this.$refs.circle
|
const circle = this.$refs.circle
|
||||||
if (!progbar || !circle) return
|
if (!progbar || !circle) return
|
||||||
|
|
||||||
clearTimeout(this.updateTimeout)
|
clearTimeout(this.updateTimeout)
|
||||||
var progress = Math.min(this.value || 0, 1)
|
const progress = Math.min(this.value || 0, 1)
|
||||||
|
|
||||||
progbar.style.setProperty('--progress-percent-before', this.lastProgress)
|
progbar.style.setProperty('--progress-percent-before', this.lastProgress)
|
||||||
progbar.style.setProperty('--progress-percent', progress)
|
progbar.style.setProperty('--progress-percent', progress)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="numPartsRemaining > 0">
|
<div v-if="downloadItemPartsRemaining.length" @click="clickedIt">
|
||||||
<widgets-circle-progress :value="progress" :count="numPartsRemaining" />
|
<widgets-circle-progress :value="progress" :count="downloadItemPartsRemaining.length" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -10,73 +10,38 @@ import { AbsDownloader } from '@/plugins/capacitor'
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
updateListener: null,
|
downloadItemListener: null,
|
||||||
completeListener: null,
|
completeListener: null,
|
||||||
itemDownloadingMap: {}
|
itemPartUpdateListener: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
numItemPartsComplete() {
|
downloadItems() {
|
||||||
var total = 0
|
return this.$store.state.globals.itemDownloads
|
||||||
Object.values(this.itemDownloadingMap).map((item) => (total += item.partsCompleted))
|
|
||||||
return total
|
|
||||||
},
|
},
|
||||||
numPartsRemaining() {
|
downloadItemParts() {
|
||||||
return this.numTotalParts - this.numItemPartsComplete
|
let parts = []
|
||||||
|
this.downloadItems.forEach((di) => parts.push(...di.downloadItemParts))
|
||||||
|
return parts
|
||||||
},
|
},
|
||||||
numTotalParts() {
|
downloadItemPartsRemaining() {
|
||||||
var total = 0
|
return this.downloadItemParts.filter((dip) => !dip.completed)
|
||||||
Object.values(this.itemDownloadingMap).map((item) => (total += item.totalParts))
|
|
||||||
return total
|
|
||||||
},
|
},
|
||||||
progress() {
|
progress() {
|
||||||
var numItems = Object.keys(this.itemDownloadingMap).length
|
let totalBytes = 0
|
||||||
if (!numItems) return 0
|
let totalBytesDownloaded = 0
|
||||||
var totalProg = 0
|
this.downloadItemParts.forEach((dip) => {
|
||||||
Object.values(this.itemDownloadingMap).map((item) => (totalProg += item.itemProgress))
|
totalBytes += dip.fileSize
|
||||||
return totalProg / numItems
|
totalBytesDownloaded += dip.bytesDownloaded
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!totalBytes) return 0
|
||||||
|
return Math.min(1, totalBytesDownloaded / totalBytes)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onItemDownloadUpdate(data) {
|
clickedIt() {
|
||||||
console.log('DownloadProgressIndicator onItemDownloadUpdate', JSON.stringify(data))
|
this.$router.push('/downloading')
|
||||||
if (!data || !data.downloadItemParts) {
|
|
||||||
console.error('Invalid item update payload')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var downloadItemParts = data.downloadItemParts
|
|
||||||
var partsCompleted = 0
|
|
||||||
var totalPartsProgress = 0
|
|
||||||
var partsRemaining = 0
|
|
||||||
downloadItemParts.forEach((dip) => {
|
|
||||||
if (dip.completed) {
|
|
||||||
totalPartsProgress += 1
|
|
||||||
partsCompleted++
|
|
||||||
} else {
|
|
||||||
var progPercent = dip.progress / 100
|
|
||||||
totalPartsProgress += progPercent
|
|
||||||
partsRemaining++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
var itemProgress = totalPartsProgress / downloadItemParts.length
|
|
||||||
|
|
||||||
var update = {
|
|
||||||
id: data.id,
|
|
||||||
libraryItemId: data.libraryItemId,
|
|
||||||
partsRemaining,
|
|
||||||
partsCompleted,
|
|
||||||
totalParts: downloadItemParts.length,
|
|
||||||
itemProgress
|
|
||||||
}
|
|
||||||
data.itemProgress = itemProgress
|
|
||||||
data.episodes = downloadItemParts.filter((dip) => dip.episode).map((dip) => dip.episode)
|
|
||||||
|
|
||||||
console.log('[download] Saving item update download payload', JSON.stringify(update))
|
|
||||||
console.log('[download] Download Progress indicator data', JSON.stringify(data))
|
|
||||||
|
|
||||||
this.$set(this.itemDownloadingMap, update.id, update)
|
|
||||||
|
|
||||||
this.$store.commit('globals/addUpdateItemDownload', data)
|
|
||||||
},
|
},
|
||||||
onItemDownloadComplete(data) {
|
onItemDownloadComplete(data) {
|
||||||
console.log('DownloadProgressIndicator onItemDownloadComplete', JSON.stringify(data))
|
console.log('DownloadProgressIndicator onItemDownloadComplete', JSON.stringify(data))
|
||||||
@@ -85,11 +50,6 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.itemDownloadingMap[data.libraryItemId]) {
|
|
||||||
delete this.itemDownloadingMap[data.libraryItemId]
|
|
||||||
} else {
|
|
||||||
console.warn('Item download complete but not found in item downloading map', data.libraryItemId)
|
|
||||||
}
|
|
||||||
if (!data.localLibraryItem) {
|
if (!data.localLibraryItem) {
|
||||||
this.$toast.error('Item download complete but failed to create library item')
|
this.$toast.error('Item download complete but failed to create library item')
|
||||||
} else {
|
} else {
|
||||||
@@ -103,15 +63,28 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit('globals/removeItemDownload', data.libraryItemId)
|
this.$store.commit('globals/removeItemDownload', data.libraryItemId)
|
||||||
|
},
|
||||||
|
onDownloadItem(downloadItem) {
|
||||||
|
console.log('DownloadProgressIndicator onDownloadItem', JSON.stringify(downloadItem))
|
||||||
|
|
||||||
|
downloadItem.itemProgress = 0
|
||||||
|
downloadItem.episodes = downloadItem.downloadItemParts.filter((dip) => dip.episode).map((dip) => dip.episode)
|
||||||
|
|
||||||
|
this.$store.commit('globals/addUpdateItemDownload', downloadItem)
|
||||||
|
},
|
||||||
|
onDownloadItemPartUpdate(itemPart) {
|
||||||
|
this.$store.commit('globals/updateDownloadItemPart', itemPart)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.updateListener = AbsDownloader.addListener('onItemDownloadUpdate', (data) => this.onItemDownloadUpdate(data))
|
this.downloadItemListener = AbsDownloader.addListener('onDownloadItem', (data) => this.onDownloadItem(data))
|
||||||
|
this.itemPartUpdateListener = AbsDownloader.addListener('onDownloadItemPartUpdate', (data) => this.onDownloadItemPartUpdate(data))
|
||||||
this.completeListener = AbsDownloader.addListener('onItemDownloadComplete', (data) => this.onItemDownloadComplete(data))
|
this.completeListener = AbsDownloader.addListener('onItemDownloadComplete', (data) => this.onItemDownloadComplete(data))
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.updateListener) this.updateListener.remove()
|
if (this.downloadItemListener) this.downloadItemListener.remove()
|
||||||
if (this.completeListener) this.completeListener.remove()
|
if (this.completeListener) this.completeListener.remove()
|
||||||
|
if (this.itemPartUpdateListener) this.itemPartUpdateListener.remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full h-full py-6 px-4 overflow-y-auto">
|
||||||
|
<p class="mb-4 text-base text-white">Downloading Files ({{ downloadItemParts.length }})</p>
|
||||||
|
|
||||||
|
<div v-if="!downloadItemParts.length" class="py-6 text-center text-lg">No download item parts</div>
|
||||||
|
<template v-for="(itemPart, num) in downloadItemParts">
|
||||||
|
<div :key="itemPart.id" class="w-full">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="w-14">
|
||||||
|
<span v-if="itemPart.completed" class="material-icons text-success">check_circle_outline</span>
|
||||||
|
<span v-else class="font-semibold text-gray-200">{{ Math.round(itemPart.progress) }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow px-2">
|
||||||
|
<p class="truncate">{{ itemPart.filename }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="num + 1 < downloadItemParts.length" class="flex border-t border-white border-opacity-10 my-3" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
downloadItems() {
|
||||||
|
return this.$store.state.globals.itemDownloads
|
||||||
|
},
|
||||||
|
downloadItemParts() {
|
||||||
|
let parts = []
|
||||||
|
this.downloadItems.forEach((di) => parts.push(...di.downloadItemParts))
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {},
|
||||||
|
beforeDestroy() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
@@ -79,6 +79,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="downloadItem" class="py-3">
|
||||||
|
<p class="text-center text-lg">Downloading! ({{ Math.round(downloadItem.itemProgress * 100) }}%)</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- metadata -->
|
<!-- metadata -->
|
||||||
<div class="grid gap-2 my-4" style="grid-template-columns: max-content auto">
|
<div class="grid gap-2 my-4" style="grid-template-columns: max-content auto">
|
||||||
<div v-if="narrators && narrators.length" class="text-white text-opacity-60 uppercase text-sm">Narrators</div>
|
<div v-if="narrators && narrators.length" class="text-white text-opacity-60 uppercase text-sm">Narrators</div>
|
||||||
@@ -119,10 +123,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="downloadItem" class="py-3">
|
|
||||||
<p class="text-center text-lg">Downloading! ({{ Math.round(downloadItem.itemProgress * 100) }}%)</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-full py-4">
|
<div class="w-full py-4">
|
||||||
<p class="text-sm">{{ description }}</p>
|
<p class="text-sm">{{ description }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -108,6 +108,31 @@ export const mutations = {
|
|||||||
state.itemDownloads.push(downloadItem)
|
state.itemDownloads.push(downloadItem)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateDownloadItemPart(state, downloadItemPart) {
|
||||||
|
const downloadItem = state.itemDownloads.find(i => i.id == downloadItemPart.downloadItemId)
|
||||||
|
if (!downloadItem) {
|
||||||
|
console.error('updateDownloadItemPart: Download item not found for itemPart', JSON.stringify(downloadItemPart))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalBytes = 0
|
||||||
|
let totalBytesDownloaded = 0
|
||||||
|
downloadItem.downloadItemParts = downloadItem.downloadItemParts.map(dip => {
|
||||||
|
let newDip = dip.id == downloadItemPart.id ? downloadItemPart : dip
|
||||||
|
|
||||||
|
totalBytes += newDip.fileSize
|
||||||
|
totalBytesDownloaded += newDip.bytesDownloaded
|
||||||
|
|
||||||
|
return newDip
|
||||||
|
})
|
||||||
|
|
||||||
|
if (totalBytes > 0) {
|
||||||
|
downloadItem.itemProgress = Math.min(1, totalBytesDownloaded / totalBytes)
|
||||||
|
console.log(`updateDownloadItemPart: filename=${downloadItemPart.filename}, totalBytes=${totalBytes}, downloaded=${totalBytesDownloaded}, itemProgress=${downloadItem.itemProgress}`)
|
||||||
|
} else {
|
||||||
|
downloadItem.itemProgress = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
removeItemDownload(state, id) {
|
removeItemDownload(state, id) {
|
||||||
state.itemDownloads = state.itemDownloads.filter(i => i.id != id)
|
state.itemDownloads = state.itemDownloads.filter(i => i.id != id)
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user