diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/DbManager.kt b/android/app/src/main/java/com/audiobookshelf/app/data/DbManager.kt index 07ce32ac..b41f5d0c 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/data/DbManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/data/DbManager.kt @@ -15,11 +15,11 @@ class DbManager { Paper.book("device").write("data", deviceData) } - fun getLocalLibraryItems():MutableList { + fun getLocalLibraryItems(mediaType:String? = null):MutableList { var localLibraryItems:MutableList = mutableListOf() Paper.book("localLibraryItems").allKeys.forEach { var localLibraryItem:LocalLibraryItem? = Paper.book("localLibraryItems").read(it) - if (localLibraryItem != null) { + if (localLibraryItem != null && (mediaType.isNullOrEmpty() || mediaType == localLibraryItem?.mediaType)) { // TODO: Check to make sure all file paths exist // if (localMediaItem.coverContentUrl != null) { // var file = DocumentFile.fromSingleUri(ctx) @@ -44,6 +44,10 @@ class DbManager { } } + fun getLocalLibraryItemByLLId(libraryItemId:String):LocalLibraryItem? { + return getLocalLibraryItems().find { it.libraryItemId == libraryItemId } + } + fun getLocalLibraryItem(localLibraryItemId:String):LocalLibraryItem? { return Paper.book("localLibraryItems").read(localLibraryItemId) } diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt index afa75558..5bbb7192 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt @@ -50,6 +50,7 @@ class AbsDatabase : Plugin() { @PluginMethod fun getLocalLibraryItem(call:PluginCall) { var id = call.getString("id", "").toString() + GlobalScope.launch(Dispatchers.IO) { var localLibraryItem = DeviceManager.dbManager.getLocalLibraryItem(id) if (localLibraryItem == null) { @@ -61,9 +62,24 @@ class AbsDatabase : Plugin() { } @PluginMethod - fun getLocalLibraryItems(call:PluginCall) { + fun getLocalLibraryItemByLLId(call:PluginCall) { + var libraryItemId = call.getString("libraryItemId", "").toString() GlobalScope.launch(Dispatchers.IO) { - var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItems() + var localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLLId(libraryItemId) + if (localLibraryItem == null) { + call.resolve() + } else { + call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(localLibraryItem))) + } + } + } + + @PluginMethod + fun getLocalLibraryItems(call:PluginCall) { + var mediaType = call.getString("mediaType", "").toString() + + GlobalScope.launch(Dispatchers.IO) { + var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItems(mediaType) var jsobj = JSObject() jsobj.put("localLibraryItems", jacksonObjectMapper().writeValueAsString(localLibraryItems)) call.resolve(jsobj) diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt index 70f9521e..8470420f 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt @@ -144,13 +144,18 @@ class AbsDownloader : Plugin() { var itemFolderPath = localFolder.absolutePath + "/" + bookTitle var downloadItem = DownloadItem(libraryItem.id, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf()) - // Create download item part for each audio track tracks.forEach { audioTrack -> var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack.relPath)}" var destinationFilename = getFilenameFromRelPath(audioTrack.relPath) Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}") var destinationFile = File("$itemFolderPath/$destinationFilename") + + if (destinationFile.exists()) { + Log.d(tag, "Audio file already exists, removing it from ${destinationFile.absolutePath}") + destinationFile.delete() + } + var destinationUri = Uri.fromFile(destinationFile) var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}") Log.d(tag, "Audio File Destination Uri $destinationUri | Download URI $downloadUri") @@ -169,6 +174,12 @@ class AbsDownloader : Plugin() { var serverPath = "/api/items/${libraryItem.id}/cover?format=jpeg" var destinationFilename = "cover.jpg" var destinationFile = File("$itemFolderPath/$destinationFilename") + + if (destinationFile.exists()) { + Log.d(tag, "Cover already exists, removing it from ${destinationFile.absolutePath}") + destinationFile.delete() + } + var destinationUri = Uri.fromFile(destinationFile) var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}&token=${DeviceManager.token}") var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, bookTitle, serverPath, localFolder.name, localFolder.id, null, false, downloadUri, destinationUri, null, 0) @@ -237,20 +248,20 @@ class AbsDownloader : Plugin() { val totalBytes = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) val downloadStatus = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_STATUS)) val bytesDownloadedSoFar = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) - Log.d(tag, "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) { - Log.d(tag, "Download ${downloadItemPart.filename} Done") + Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Done") // downloadItem.downloadItemParts.remove(downloadItemPart) downloadItemPart.completed = true } else if (downloadStatus == DownloadManager.STATUS_FAILED) { - Log.d(tag, "Download ${downloadItemPart.filename} Failed") + Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Failed") downloadItem.downloadItemParts.remove(downloadItemPart) // downloadItemPart.completed = true } else { //update progress val percentProgress = if (totalBytes > 0) ((bytesDownloadedSoFar * 100L) / totalBytes) else 0 - Log.d(tag, "${downloadItemPart.filename} Progress = $percentProgress%") + Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Progress = $percentProgress%") downloadItemPart.progress = percentProgress } } else { diff --git a/components/app/Appbar.vue b/components/app/Appbar.vue index 9d8d24be..22b2ab2d 100644 --- a/components/app/Appbar.vue +++ b/components/app/Appbar.vue @@ -7,7 +7,7 @@ arrow_back -
+
@@ -17,6 +17,8 @@
+ + search diff --git a/components/bookshelf/LazyBookshelf.vue b/components/bookshelf/LazyBookshelf.vue index bcf9caf4..1c5d63fa 100644 --- a/components/bookshelf/LazyBookshelf.vue +++ b/components/bookshelf/LazyBookshelf.vue @@ -42,7 +42,8 @@ export default { entityIndexesMounted: [], pagesLoaded: {}, isFirstInit: false, - pendingReset: false + pendingReset: false, + localLibraryItems: [] } }, computed: { @@ -101,6 +102,9 @@ export default { currentLibraryId() { return this.$store.state.libraries.currentLibraryId }, + currentLibraryMediaType() { + return this.$store.getters['libraries/getCurrentLibraryMediaType'] + }, shelfHeight() { return this.entityHeight + 40 }, @@ -164,6 +168,13 @@ export default { this.entities[index] = payload.results[i] if (this.entityComponentRefs[index]) { this.entityComponentRefs[index].setEntity(this.entities[index]) + + if (this.isBookEntity) { + var localLibraryItem = this.localLibraryItems.find((lli) => lli.libraryItemId == this.entities[index].id) + if (localLibraryItem) { + this.entityComponentRefs[index].setLocalLibraryItem(localLibraryItem) + } + } } } } @@ -204,6 +215,7 @@ export default { this.loadPage(lastBookPage) } + // Remove entities out of view this.entityIndexesMounted = this.entityIndexesMounted.filter((_index) => { if (_index < firstBookIndex || _index >= lastBookIndex) { var el = document.getElementById(`book-card-${_index}`) @@ -295,6 +307,10 @@ export default { }, async init() { if (this.isFirstInit) return + + this.localLibraryItems = await this.$db.getLocalLibraryItems(this.currentLibraryMediaType) + console.log('Local library items loaded for lazy bookshelf', this.localLibraryItems.length) + this.isFirstInit = true this.initSizeData() await this.loadPage(0) @@ -360,6 +376,13 @@ export default { this.entities[indexOf] = libraryItem if (this.entityComponentRefs[indexOf]) { this.entityComponentRefs[indexOf].setEntity(libraryItem) + + if (this.isBookEntity) { + var localLibraryItem = this.localLibraryItems.find((lli) => lli.libraryItemId == libraryItem.id) + if (localLibraryItem) { + this.entityComponentRefs[indexOf].setLocalLibraryItem(localLibraryItem) + } + } } } } diff --git a/components/cards/LazyBookCard.vue b/components/cards/LazyBookCard.vue index a0581928..986cc856 100644 --- a/components/cards/LazyBookCard.vue +++ b/components/cards/LazyBookCard.vue @@ -37,6 +37,10 @@
+
+ {{ isLocalOnly ? 'task' : 'download_done' }} +
+
@@ -85,7 +89,8 @@ export default { rescanning: false, selected: false, isSelectionMode: false, - showCoverBg: false + showCoverBg: false, + localLibraryItem: null } }, watch: { @@ -105,9 +110,12 @@ export default { return this.libraryItem || {} }, isLocal() { - // Is local library item return !!this._libraryItem.isLocal }, + isLocalOnly() { + // Local item with no server match + return this.isLocal && !this._libraryItem.libraryItemId + }, media() { return this._libraryItem.media || {} }, @@ -119,7 +127,7 @@ export default { }, bookCoverSrc() { if (this.isLocal) { - if (this.media.coverPath) return Capacitor.convertFileSrc(this.media.coverPath) + if (this.libraryItem.coverContentUrl) return Capacitor.convertFileSrc(this.libraryItem.coverContentUrl) return this.placeholderUrl } return this.store.getters['globals/getLibraryItemCoverSrc'](this._libraryItem, this.placeholderUrl) @@ -319,6 +327,10 @@ export default { setEntity(libraryItem) { this.libraryItem = libraryItem }, + setLocalLibraryItem(localLibraryItem) { + // Server books may have a local library item + this.localLibraryItem = localLibraryItem + }, clickCard(e) { if (this.isSelectionMode) { e.stopPropagation() @@ -442,6 +454,10 @@ export default { mounted() { if (this.bookMount) { this.setEntity(this.bookMount) + + if (this.bookMount.localLibraryItem) { + this.setLocalLibraryItem(this.bookMount.localLibraryItem) + } } } } diff --git a/components/covers/BookCover.vue b/components/covers/BookCover.vue index 96ee4a3e..5259e617 100644 --- a/components/covers/BookCover.vue +++ b/components/covers/BookCover.vue @@ -66,6 +66,9 @@ export default { if (!this.libraryItem) return false return this.libraryItem.isLocal }, + localCover() { + return this.libraryItem ? this.libraryItem.coverContentUrl : null + }, squareAspectRatio() { return this.bookCoverAspectRatio === 1 }, @@ -105,7 +108,7 @@ export default { }, fullCoverUrl() { if (this.isLocal) { - if (this.hasCover) return Capacitor.convertFileSrc(this.cover) + if (this.localCover) return Capacitor.convertFileSrc(this.localCover) return this.placeholderUrl } if (this.downloadCover) return this.downloadCover diff --git a/components/widgets/CircleProgress.vue b/components/widgets/CircleProgress.vue new file mode 100644 index 00000000..7cc49a4b --- /dev/null +++ b/components/widgets/CircleProgress.vue @@ -0,0 +1,143 @@ + + + + + \ No newline at end of file diff --git a/components/widgets/DownloadProgressIndicator.vue b/components/widgets/DownloadProgressIndicator.vue new file mode 100644 index 00000000..ae48ae94 --- /dev/null +++ b/components/widgets/DownloadProgressIndicator.vue @@ -0,0 +1,108 @@ + + + \ No newline at end of file diff --git a/layouts/default.vue b/layouts/default.vue index a9e9521c..8c1cb480 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -13,17 +13,25 @@ diff --git a/pages/localMedia/item/_id.vue b/pages/localMedia/item/_id.vue index f7cb919a..30dc7d13 100644 --- a/pages/localMedia/item/_id.vue +++ b/pages/localMedia/item/_id.vue @@ -12,6 +12,9 @@

Folder: {{ folderName }}

{{ mediaMetadata.title }}

+ +

{{ libraryItemId || 'Not linked to server library item' }}

+

Scanning...

@@ -88,6 +91,9 @@ export default { }, mediaType() { return this.localLibraryItem ? this.localLibraryItem.mediaType : null + }, + libraryItemId() { + return this.localLibraryItem ? this.localLibraryItem.libraryItemId : null }, media() { return this.localLibraryItem ? this.localLibraryItem.media : null diff --git a/plugins/db.js b/plugins/db.js index cdadf2fa..ca5deb0e 100644 --- a/plugins/db.js +++ b/plugins/db.js @@ -84,9 +84,9 @@ class DbService { }) } - getLocalLibraryItems() { + getLocalLibraryItems(mediaType = null) { if (isWeb) return [] - return AbsDatabase.getLocalLibraryItems().then((data) => { + return AbsDatabase.getLocalLibraryItems(mediaType).then((data) => { console.log('Loaded all local media items', JSON.stringify(data)) if (data.localLibraryItems && typeof data.localLibraryItems == 'string') { return JSON.parse(data.localLibraryItems) @@ -99,6 +99,11 @@ class DbService { if (isWeb) return null return AbsDatabase.getLocalLibraryItem({ id }) } + + getLocalLibraryItemByLLId(libraryItemId) { + if (isWeb) return null + return AbsDatabase.getLocalLibraryItemByLLId({ libraryItemId }) + } } export default ({ app, store }, inject) => { diff --git a/store/globals.js b/store/globals.js index ff0fdbf0..44967a1c 100644 --- a/store/globals.js +++ b/store/globals.js @@ -1,8 +1,11 @@ export const state = () => ({ - + itemDownloads: [] }) export const getters = { + getDownloadItem: state => libraryItemId => { + return state.itemDownloads.find(i => i.id == libraryItemId) + }, getLibraryItemCoverSrc: (state, getters, rootState, rootGetters) => (libraryItem, placeholder = '/book_placeholder.jpg') => { if (!libraryItem) return placeholder var media = libraryItem.media @@ -28,5 +31,15 @@ export const actions = { } export const mutations = { - + addUpdateItemDownload(state, downloadItem) { + var index = state.itemDownloads.findIndex(i => i.id == downloadItem.id) + if (index >= 0) { + state.itemDownloads.splice(index, 1, downloadItem) + } else { + state.itemDownloads.push(downloadItem) + } + }, + removeItemDownload(state, id) { + state.itemDownloads = state.itemDownloads.filter(i => i.id != id) + } } \ No newline at end of file diff --git a/store/libraries.js b/store/libraries.js index 340e7a0e..d6b43427 100644 --- a/store/libraries.js +++ b/store/libraries.js @@ -17,6 +17,10 @@ export const getters = { getCurrentLibraryName: (state, getters) => { var currLib = getters.getCurrentLibrary return currLib ? currLib.name : null + }, + getCurrentLibraryMediaType: (state, getters) => { + var currLib = getters.getCurrentLibrary + return currLib ? currLib.mediaType : null } }