Update capacitor version, kotlin version, android dependencies, refactor some folders

This commit is contained in:
advplyr 2023-01-22 17:26:08 -06:00
parent b2d3edca81
commit a8c66ff808
26 changed files with 1034 additions and 519 deletions

View file

@ -22,6 +22,8 @@ kotlin {
android { android {
namespace 'com.audiobookshelf.app'
buildFeatures { buildFeatures {
viewBinding true viewBinding true
} }
@ -78,6 +80,7 @@ configurations.all {
} }
dependencies { dependencies {
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation project(':capacitor-android') implementation project(':capacitor-android')
@ -121,7 +124,7 @@ dependencies {
implementation 'io.github.pilgr:paperdb:2.7.2' implementation 'io.github.pilgr:paperdb:2.7.2'
// Simple Storage // Simple Storage
implementation "com.anggrayudi:storage:0.14.0" implementation "com.anggrayudi:storage:1.5.4"
// OK HTTP // OK HTTP
implementation 'com.squareup.okhttp3:okhttp:4.9.2' implementation 'com.squareup.okhttp3:okhttp:4.9.2'

View file

@ -2,8 +2,8 @@
android { android {
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_11
} }
} }
@ -13,8 +13,8 @@ dependencies {
implementation project(':capacitor-dialog') implementation project(':capacitor-dialog')
implementation project(':capacitor-haptics') implementation project(':capacitor-haptics')
implementation project(':capacitor-network') implementation project(':capacitor-network')
implementation project(':capacitor-preferences')
implementation project(':capacitor-status-bar') implementation project(':capacitor-status-bar')
implementation project(':capacitor-storage')
} }

View file

@ -2,7 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution" xmlns:dist="http://schemas.android.com/apk/distribution"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.audiobookshelf.app"
android:installLocation="preferExternal" > android:installLocation="preferExternal" >
<!-- Permissions --> <!-- Permissions -->

View file

@ -16,11 +16,11 @@
"classpath": "com.capacitorjs.plugins.network.NetworkPlugin" "classpath": "com.capacitorjs.plugins.network.NetworkPlugin"
}, },
{ {
"pkg": "@capacitor/status-bar", "pkg": "@capacitor/preferences",
"classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin" "classpath": "com.capacitorjs.plugins.preferences.PreferencesPlugin"
}, },
{ {
"pkg": "@capacitor/storage", "pkg": "@capacitor/status-bar",
"classpath": "com.capacitorjs.plugins.storage.StoragePlugin" "classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin"
} }
] ]

View file

@ -13,7 +13,7 @@ import androidx.core.app.ActivityCompat
import com.anggrayudi.storage.SimpleStorage import com.anggrayudi.storage.SimpleStorage
import com.anggrayudi.storage.SimpleStorageHelper import com.anggrayudi.storage.SimpleStorageHelper
import com.audiobookshelf.app.data.AbsDatabase import com.audiobookshelf.app.data.AbsDatabase
import com.audiobookshelf.app.data.DbManager import com.audiobookshelf.app.managers.DbManager
import com.audiobookshelf.app.player.PlayerNotificationService import com.audiobookshelf.app.player.PlayerNotificationService
import com.audiobookshelf.app.plugins.AbsAudioPlayer import com.audiobookshelf.app.plugins.AbsAudioPlayer
import com.audiobookshelf.app.plugins.AbsDownloader import com.audiobookshelf.app.plugins.AbsDownloader
@ -51,11 +51,17 @@ class MainActivity : BridgeActivity() {
// .detectLeakedClosableObjects() // .detectLeakedClosableObjects()
// .penaltyLog() // .penaltyLog()
// .build()) // .build())
DbManager.initialize(applicationContext)
registerPlugin(AbsAudioPlayer::class.java)
registerPlugin(AbsDownloader::class.java)
registerPlugin(AbsFileSystem::class.java)
registerPlugin(AbsDatabase::class.java)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(tag, "onCreate") Log.d(tag, "onCreate")
DbManager.initialize(applicationContext)
val permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) val permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
if (permission != PackageManager.PERMISSION_GRANTED) { if (permission != PackageManager.PERMISSION_GRANTED) {
@ -63,11 +69,6 @@ class MainActivity : BridgeActivity() {
PERMISSIONS_ALL, PERMISSIONS_ALL,
REQUEST_PERMISSIONS) REQUEST_PERMISSIONS)
} }
registerPlugin(AbsAudioPlayer::class.java)
registerPlugin(AbsDownloader::class.java)
registerPlugin(AbsFileSystem::class.java)
registerPlugin(AbsDatabase::class.java)
} }
override fun onDestroy() { override fun onDestroy() {

View file

@ -2,6 +2,7 @@ package com.audiobookshelf.app.device
import android.util.Log import android.util.Log
import com.audiobookshelf.app.data.* import com.audiobookshelf.app.data.*
import com.audiobookshelf.app.managers.DbManager
import com.audiobookshelf.app.player.PlayerNotificationService import com.audiobookshelf.app.player.PlayerNotificationService
interface WidgetEventEmitter { interface WidgetEventEmitter {
@ -11,7 +12,7 @@ interface WidgetEventEmitter {
object DeviceManager { object DeviceManager {
const val tag = "DeviceManager" const val tag = "DeviceManager"
val dbManager:DbManager = DbManager() val dbManager: DbManager = DbManager()
var deviceData:DeviceData = dbManager.getDeviceData() var deviceData:DeviceData = dbManager.getDeviceData()
var serverConnectionConfig: ServerConnectionConfig? = null var serverConnectionConfig: ServerConnectionConfig? = null

View file

@ -9,6 +9,7 @@ import com.arthenica.ffmpegkit.FFmpegKitConfig
import com.arthenica.ffmpegkit.FFprobeKit import com.arthenica.ffmpegkit.FFprobeKit
import com.arthenica.ffmpegkit.Level import com.arthenica.ffmpegkit.Level
import com.audiobookshelf.app.data.* import com.audiobookshelf.app.data.*
import com.audiobookshelf.app.models.DownloadItem
import com.audiobookshelf.app.plugins.AbsDownloader import com.audiobookshelf.app.plugins.AbsDownloader
import com.fasterxml.jackson.core.json.JsonReadFeature import com.fasterxml.jackson.core.json.JsonReadFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
@ -83,7 +84,7 @@ class FolderScanner(var ctx: Context) {
Log.d(tag, "Folder $${localFolder.name} scan Results: $mediaItemsAdded Added | $mediaItemsUpdated Updated | $mediaItemsRemoved Removed | $mediaItemsUpToDate Up-to-date") Log.d(tag, "Folder $${localFolder.name} scan Results: $mediaItemsAdded Added | $mediaItemsUpdated Updated | $mediaItemsRemoved Removed | $mediaItemsUpToDate Up-to-date")
return if (mediaItemsAdded > 0 || mediaItemsUpdated > 0 || mediaItemsRemoved > 0) { return if (mediaItemsAdded > 0 || mediaItemsUpdated > 0 || mediaItemsRemoved > 0) {
var folderLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(localFolder.id) // Get all local media items val folderLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(localFolder.id) // Get all local media items
FolderScanResult(mediaItemsAdded, mediaItemsUpdated, mediaItemsRemoved, mediaItemsUpToDate, localFolder, folderLibraryItems) FolderScanResult(mediaItemsAdded, mediaItemsUpdated, mediaItemsRemoved, mediaItemsUpToDate, localFolder, folderLibraryItems)
} else { } else {
Log.d(tag, "No Media Items to save") Log.d(tag, "No Media Items to save")
@ -91,7 +92,7 @@ class FolderScanner(var ctx: Context) {
} }
} }
fun scanLibraryItemFolder(itemFolder:DocumentFile, localFolder:LocalFolder, existingItem:LocalLibraryItem?, forceAudioProbe:Boolean):ItemScanResult { private fun scanLibraryItemFolder(itemFolder:DocumentFile, localFolder:LocalFolder, existingItem:LocalLibraryItem?, forceAudioProbe:Boolean):ItemScanResult {
val itemFolderName = itemFolder.name ?: "" val itemFolderName = itemFolder.name ?: ""
val itemId = getLocalLibraryItemId(itemFolder.id) val itemId = getLocalLibraryItemId(itemFolder.id)
@ -219,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: AbsDownloader.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(false, DocumentFileType.FOLDER) ?: mutableListOf()
@ -268,49 +269,46 @@ class FolderScanner(var ctx: Context) {
} }
} }
val audioTracks:MutableList<AudioTrack> = mutableListOf() val audioTracks:MutableList<AudioTrack> = mutableListOf()
filesFound.forEach { docFile -> filesFound.forEach { docFile ->
val itemPart = downloadItem.downloadItemParts.find { itemPart -> val itemPart = downloadItem.downloadItemParts.find { itemPart ->
itemPart.filename == docFile.name itemPart.filename == docFile.name
}
if (itemPart == null) {
if (downloadItem.mediaType == "book") { // for books every download item should be a file found
Log.e(tag, "scanDownloadItem: Item part not found for doc file ${docFile.name} | ${docFile.getAbsolutePath(ctx)} | ${docFile.uri}")
}
} else if (itemPart.audioTrack != null) { // Is audio track
val audioTrackFromServer = itemPart.audioTrack
Log.d(tag, "scanDownloadItem: Audio Track from Server index = ${audioTrackFromServer?.index}")
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())
localLibraryItem.localFiles.add(localFile)
// TODO: Make asynchronous
val audioProbeResult = probeAudioFile(localFile.absolutePath)
// Create new audio track
val track = AudioTrack(audioTrackFromServer.index, audioTrackFromServer.startOffset, audioProbeResult?.duration ?: 0.0, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, audioProbeResult, audioTrackFromServer.index)
audioTracks.add(track)
Log.d(tag, "scanDownloadItem: Created Audio Track with index ${track.index} from local file ${localFile.absolutePath}")
// Add podcast episodes to library
itemPart.episode?.let { podcastEpisode ->
val podcast = localLibraryItem.media as Podcast
val newEpisode = podcast.addEpisode(track, podcastEpisode)
localEpisodeId = newEpisode.id
Log.d(tag, "scanDownloadItem: Added episode to podcast ${podcastEpisode.title} ${track.title} | Track index: ${podcastEpisode.audioTrack?.index}")
}
} else { // Cover image
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())
localLibraryItem.coverAbsolutePath = localFile.absolutePath
localLibraryItem.coverContentUrl = localFile.contentUrl
localLibraryItem.localFiles.add(localFile)
}
} }
if (itemPart == null) {
if (downloadItem.mediaType == "book") { // for books every download item should be a file found
Log.e(tag, "scanDownloadItem: Item part not found for doc file ${docFile.name} | ${docFile.getAbsolutePath(ctx)} | ${docFile.uri}")
}
} else if (itemPart.audioTrack != null) { // Is audio track
val audioTrackFromServer = itemPart.audioTrack
Log.d(tag, "scanDownloadItem: Audio Track from Server index = ${audioTrackFromServer?.index}")
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())
localLibraryItem.localFiles.add(localFile)
// Create new audio track
val track = AudioTrack(audioTrackFromServer.index, audioTrackFromServer.startOffset, audioTrackFromServer.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, null, audioTrackFromServer.index)
audioTracks.add(track)
Log.d(tag, "scanDownloadItem: Created Audio Track with index ${track.index} from local file ${localFile.absolutePath}")
// Add podcast episodes to library
itemPart.episode?.let { podcastEpisode ->
val podcast = localLibraryItem.media as Podcast
val newEpisode = podcast.addEpisode(track, podcastEpisode)
localEpisodeId = newEpisode.id
Log.d(tag, "scanDownloadItem: Added episode to podcast ${podcastEpisode.title} ${track.title} | Track index: ${podcastEpisode.audioTrack?.index}")
}
} else { // Cover image
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())
localLibraryItem.coverAbsolutePath = localFile.absolutePath
localLibraryItem.coverContentUrl = localFile.contentUrl
localLibraryItem.localFiles.add(localFile)
}
}
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}")

View file

@ -1,8 +1,9 @@
package com.audiobookshelf.app.data package com.audiobookshelf.app.managers
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.audiobookshelf.app.plugins.AbsDownloader import com.audiobookshelf.app.data.*
import com.audiobookshelf.app.models.DownloadItem
import io.paperdb.Paper import io.paperdb.Paper
import java.io.File import java.io.File
@ -23,14 +24,14 @@ class DbManager {
fun getDeviceData(): DeviceData { fun getDeviceData(): DeviceData {
return Paper.book("device").read("data") ?: DeviceData(mutableListOf(), null, null, DeviceSettings.default()) return Paper.book("device").read("data") ?: DeviceData(mutableListOf(), null, null, DeviceSettings.default())
} }
fun saveDeviceData(deviceData:DeviceData) { fun saveDeviceData(deviceData: DeviceData) {
Paper.book("device").write("data", deviceData) Paper.book("device").write("data", deviceData)
} }
fun getLocalLibraryItems(mediaType:String? = null):MutableList<LocalLibraryItem> { fun getLocalLibraryItems(mediaType:String? = null):MutableList<LocalLibraryItem> {
val localLibraryItems:MutableList<LocalLibraryItem> = mutableListOf() val localLibraryItems:MutableList<LocalLibraryItem> = mutableListOf()
Paper.book("localLibraryItems").allKeys.forEach { Paper.book("localLibraryItems").allKeys.forEach {
val localLibraryItem:LocalLibraryItem? = Paper.book("localLibraryItems").read(it) val localLibraryItem: LocalLibraryItem? = Paper.book("localLibraryItems").read(it)
if (localLibraryItem != null && (mediaType.isNullOrEmpty() || mediaType == localLibraryItem.mediaType)) { if (localLibraryItem != null && (mediaType.isNullOrEmpty() || mediaType == localLibraryItem.mediaType)) {
localLibraryItems.add(localLibraryItem) localLibraryItems.add(localLibraryItem)
} }
@ -45,16 +46,16 @@ class DbManager {
} }
} }
fun getLocalLibraryItemByLId(libraryItemId:String):LocalLibraryItem? { fun getLocalLibraryItemByLId(libraryItemId:String): LocalLibraryItem? {
return getLocalLibraryItems().find { it.libraryItemId == libraryItemId } return getLocalLibraryItems().find { it.libraryItemId == libraryItemId }
} }
fun getLocalLibraryItem(localLibraryItemId:String):LocalLibraryItem? { fun getLocalLibraryItem(localLibraryItemId:String): LocalLibraryItem? {
return Paper.book("localLibraryItems").read(localLibraryItemId) return Paper.book("localLibraryItems").read(localLibraryItemId)
} }
fun getLocalLibraryItemWithEpisode(podcastEpisodeId:String):LibraryItemWithEpisode? { fun getLocalLibraryItemWithEpisode(podcastEpisodeId:String): LibraryItemWithEpisode? {
var podcastEpisode:PodcastEpisode? = null var podcastEpisode: PodcastEpisode? = null
val localLibraryItem = getLocalLibraryItems("podcast").find { localLibraryItem -> val localLibraryItem = getLocalLibraryItems("podcast").find { localLibraryItem ->
val podcast = localLibraryItem.media as Podcast val podcast = localLibraryItem.media as Podcast
podcastEpisode = podcast.episodes?.find { it.id == podcastEpisodeId } podcastEpisode = podcast.episodes?.find { it.id == podcastEpisodeId }
@ -77,15 +78,15 @@ class DbManager {
} }
} }
fun saveLocalLibraryItem(localLibraryItem:LocalLibraryItem) { fun saveLocalLibraryItem(localLibraryItem: LocalLibraryItem) {
Paper.book("localLibraryItems").write(localLibraryItem.id, localLibraryItem) Paper.book("localLibraryItems").write(localLibraryItem.id, localLibraryItem)
} }
fun saveLocalFolder(localFolder:LocalFolder) { fun saveLocalFolder(localFolder: LocalFolder) {
Paper.book("localFolders").write(localFolder.id,localFolder) Paper.book("localFolders").write(localFolder.id,localFolder)
} }
fun getLocalFolder(folderId:String):LocalFolder? { fun getLocalFolder(folderId:String): LocalFolder? {
return Paper.book("localFolders").read(folderId) return Paper.book("localFolders").read(folderId)
} }
@ -107,7 +108,7 @@ class DbManager {
Paper.book("localFolders").delete(folderId) Paper.book("localFolders").delete(folderId)
} }
fun saveDownloadItem(downloadItem: AbsDownloader.DownloadItem) { fun saveDownloadItem(downloadItem: DownloadItem) {
Paper.book("downloadItems").write(downloadItem.id, downloadItem) Paper.book("downloadItems").write(downloadItem.id, downloadItem)
} }
@ -115,21 +116,21 @@ class DbManager {
Paper.book("downloadItems").delete(downloadItemId) Paper.book("downloadItems").delete(downloadItemId)
} }
fun getDownloadItems():List<AbsDownloader.DownloadItem> { fun getDownloadItems():List<DownloadItem> {
val downloadItems:MutableList<AbsDownloader.DownloadItem> = mutableListOf() val downloadItems:MutableList<DownloadItem> = mutableListOf()
Paper.book("downloadItems").allKeys.forEach { downloadItemId -> Paper.book("downloadItems").allKeys.forEach { downloadItemId ->
Paper.book("downloadItems").read<AbsDownloader.DownloadItem>(downloadItemId)?.let { Paper.book("downloadItems").read<DownloadItem>(downloadItemId)?.let {
downloadItems.add(it) downloadItems.add(it)
} }
} }
return downloadItems return downloadItems
} }
fun saveLocalMediaProgress(mediaProgress:LocalMediaProgress) { fun saveLocalMediaProgress(mediaProgress: LocalMediaProgress) {
Paper.book("localMediaProgress").write(mediaProgress.id,mediaProgress) Paper.book("localMediaProgress").write(mediaProgress.id,mediaProgress)
} }
// For books this will just be the localLibraryItemId for podcast episodes this will be "{localLibraryItemId}-{episodeId}" // For books this will just be the localLibraryItemId for podcast episodes this will be "{localLibraryItemId}-{episodeId}"
fun getLocalMediaProgress(localMediaProgressId:String):LocalMediaProgress? { fun getLocalMediaProgress(localMediaProgressId:String): LocalMediaProgress? {
return Paper.book("localMediaProgress").read(localMediaProgressId) return Paper.book("localMediaProgress").read(localMediaProgressId)
} }
fun getAllLocalMediaProgress():List<LocalMediaProgress> { fun getAllLocalMediaProgress():List<LocalMediaProgress> {
@ -236,18 +237,18 @@ class DbManager {
} }
} }
fun saveLocalPlaybackSession(playbackSession:PlaybackSession) { fun saveLocalPlaybackSession(playbackSession: PlaybackSession) {
Paper.book("localPlaybackSession").write(playbackSession.id,playbackSession) Paper.book("localPlaybackSession").write(playbackSession.id,playbackSession)
} }
fun getLocalPlaybackSession(playbackSessionId:String):PlaybackSession? { fun getLocalPlaybackSession(playbackSessionId:String): PlaybackSession? {
return Paper.book("localPlaybackSession").read(playbackSessionId) return Paper.book("localPlaybackSession").read(playbackSessionId)
} }
fun saveMediaItemHistory(mediaItemHistory:MediaItemHistory) { fun saveMediaItemHistory(mediaItemHistory: MediaItemHistory) {
Paper.book("mediaItemHistory").write(mediaItemHistory.id,mediaItemHistory) Paper.book("mediaItemHistory").write(mediaItemHistory.id,mediaItemHistory)
} }
fun getMediaItemHistory(id:String):MediaItemHistory? { fun getMediaItemHistory(id:String): MediaItemHistory? {
return Paper.book("mediaItemHistory").read(id) return Paper.book("mediaItemHistory").read(id)
} }
} }

View file

@ -0,0 +1,234 @@
package com.audiobookshelf.app.managers
import android.app.DownloadManager
import android.net.Uri
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.file.DocumentFileCompat
import com.anggrayudi.storage.file.MimeType
import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.moveFileTo
import com.anggrayudi.storage.media.FileDescription
import com.audiobookshelf.app.MainActivity
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.device.FolderScanner
import com.audiobookshelf.app.models.DownloadItem
import com.audiobookshelf.app.models.DownloadItemPart
import com.fasterxml.jackson.core.json.JsonReadFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.getcapacitor.JSObject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner: FolderScanner, var mainActivity: MainActivity, var clientEventEmitter:DownloadEventEmitter) {
val tag = "DownloadItemManager"
private val maxSimultaneousDownloads = 5
private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
enum class DownloadCheckStatus {
InProgress,
Successful,
Failed
}
var downloadItemQueue: MutableList<DownloadItem> = mutableListOf()
var currentDownloadItemParts: MutableList<DownloadItemPart> = mutableListOf()
interface DownloadEventEmitter {
fun onDownloadItem(downloadItem:DownloadItem)
fun onDownloadItemPartUpdate(downloadItemPart:DownloadItemPart)
fun onDownloadItemComplete(jsobj:JSObject)
}
companion object {
var isDownloading:Boolean = false
}
fun addDownloadItem(downloadItem:DownloadItem) {
DeviceManager.dbManager.saveDownloadItem(downloadItem)
Log.i(tag, "Add download item ${downloadItem.media.metadata.title}")
downloadItemQueue.add(downloadItem)
clientEventEmitter.onDownloadItem(downloadItem)
checkUpdateDownloadQueue()
}
private fun checkUpdateDownloadQueue() {
for (downloadItem in downloadItemQueue) {
val numPartsToGet = maxSimultaneousDownloads - currentDownloadItemParts.size
val nextDownloadItemParts = downloadItem.getNextDownloadItemParts(numPartsToGet)
Log.d(tag, "checkUpdateDownloadQueue: numPartsToGet=$numPartsToGet, nextDownloadItemParts=${nextDownloadItemParts.size}")
if (nextDownloadItemParts.size > 0) {
nextDownloadItemParts.forEach {
val dlRequest = it.getDownloadRequest()
val downloadId = downloadManager.enqueue(dlRequest)
it.downloadId = downloadId
Log.d(tag, "checkUpdateDownloadQueue: Starting download item part, downloadId=$downloadId")
currentDownloadItemParts.add(it)
}
}
if (currentDownloadItemParts.size >= maxSimultaneousDownloads) {
break
}
}
if (currentDownloadItemParts.size > 0) startWatchingDownloads()
}
private fun startWatchingDownloads() {
if (isDownloading) return // Already watching
GlobalScope.launch(Dispatchers.IO) {
Log.d(tag, "Starting watching downloads")
isDownloading = true
while (currentDownloadItemParts.size > 0) {
val itemParts = currentDownloadItemParts.filter { !it.isMoving }.map { it }
for (downloadItemPart in itemParts) {
val downloadCheckStatus = checkDownloadItemPart(downloadItemPart)
clientEventEmitter.onDownloadItemPartUpdate(downloadItemPart)
// Will move to final destination, remove current item parts, and check if download item is finished
handleDownloadItemPartCheck(downloadCheckStatus, downloadItemPart)
}
if (currentDownloadItemParts.size < maxSimultaneousDownloads) {
checkUpdateDownloadQueue()
}
delay(500)
}
Log.d(tag, "Finished watching downloads")
isDownloading = false
}
}
private fun checkDownloadItemPart(downloadItemPart:DownloadItemPart):DownloadCheckStatus {
val downloadId = downloadItemPart.downloadId ?: return DownloadCheckStatus.Failed
val query = DownloadManager.Query().setFilterById(downloadId)
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) {
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Successful")
downloadItemPart.completed = true
return DownloadCheckStatus.Successful
} else if (downloadStatus == DownloadManager.STATUS_FAILED) {
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Failed")
downloadItemPart.completed = true
downloadItemPart.failed = true
return DownloadCheckStatus.Failed
} 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
return DownloadCheckStatus.InProgress
}
} else {
Log.d(tag, "Download ${downloadItemPart.filename} not found in dlmanager")
downloadItemPart.completed = true
downloadItemPart.failed = true
return DownloadCheckStatus.Failed
}
}
}
private fun handleDownloadItemPartCheck(downloadCheckStatus:DownloadCheckStatus, downloadItemPart:DownloadItemPart) {
val downloadItem = downloadItemQueue.find { it.id == downloadItemPart.downloadItemId }
if (downloadItem == null) {
Log.e(tag, "Download item part finished but download item not found ${downloadItemPart.filename}")
currentDownloadItemParts.remove(downloadItemPart)
} else if (downloadCheckStatus == DownloadCheckStatus.Successful) {
val file = DocumentFileCompat.fromUri(mainActivity, downloadItemPart.destinationUri)
Log.d(tag, "DOWNLOAD: DESTINATION URI ${downloadItemPart.destinationUri}")
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
downloadItemPart.isMoving = false
file?.delete()
checkDownloadItemFinished(downloadItem)
currentDownloadItemParts.remove(downloadItemPart)
}
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)}")
// Rename to fix appended .mp4 on m4b files
// REF: https://github.com/anggrayudi/SimpleStorage/issues/94
resultDocFile.renameTo(downloadItemPart.filename)
downloadItemPart.moved = true
downloadItemPart.isMoving = false
checkDownloadItemFinished(downloadItem)
currentDownloadItemParts.remove(downloadItemPart)
}
}
val localFolderFile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(downloadItemPart.localFolderUrl))
if (localFolderFile == null) {
// fAILED
downloadItemPart.failed = true
Log.e(tag, "Local Folder File from uri is null")
checkDownloadItemFinished(downloadItem)
currentDownloadItemParts.remove(downloadItemPart)
} else {
downloadItemPart.isMoving = true
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 if (downloadCheckStatus != DownloadCheckStatus.InProgress) {
checkDownloadItemFinished(downloadItem)
currentDownloadItemParts.remove(downloadItemPart)
}
}
private fun checkDownloadItemFinished(downloadItem:DownloadItem) {
if (downloadItem.isDownloadFinished) {
Log.i(tag, "Download Item finished ${downloadItem.media.metadata.title}")
val downloadItemScanResult = folderScanner.scanDownloadItem(downloadItem)
Log.d(tag, "Item download complete ${downloadItem.itemTitle} | local library item id: ${downloadItemScanResult?.localLibraryItem?.id}")
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)))
}
clientEventEmitter.onDownloadItemComplete(jsobj)
downloadItemQueue.remove(downloadItem)
DeviceManager.dbManager.removeDownloadItem(downloadItem.id)
}
}
}

View file

@ -0,0 +1,47 @@
package com.audiobookshelf.app.models
import com.audiobookshelf.app.data.LocalFolder
import com.audiobookshelf.app.data.MediaProgress
import com.audiobookshelf.app.data.MediaType
import com.fasterxml.jackson.annotation.JsonIgnore
data class DownloadItem(
val id: String,
val libraryItemId:String,
val episodeId:String?,
val userMediaProgress: MediaProgress?,
val serverConnectionConfigId:String,
val serverAddress:String,
val serverUserId:String,
val mediaType: String,
val itemFolderPath:String,
val localFolder: LocalFolder,
val itemTitle: String,
val media: MediaType,
val downloadItemParts: MutableList<DownloadItemPart>
) {
@get:JsonIgnore
val isDownloadFinished get() = !downloadItemParts.any { !it.completed || it.isMoving }
@JsonIgnore
fun getTotalFileSize(): Long {
var totalSize = 0L
downloadItemParts.forEach { totalSize += it.fileSize }
return totalSize
}
@JsonIgnore
fun getNextDownloadItemParts(limit:Int): MutableList<DownloadItemPart> {
val itemParts = mutableListOf<DownloadItemPart>()
if (limit == 0) return itemParts
for (it in downloadItemParts) {
if (!it.completed && it.downloadId == null) {
itemParts.add(it)
if (itemParts.size > limit) break
}
}
return itemParts
}
}

View file

@ -0,0 +1,82 @@
package com.audiobookshelf.app.models
import android.app.DownloadManager
import android.net.Uri
import android.os.Environment
import android.util.Log
import com.audiobookshelf.app.data.AudioTrack
import com.audiobookshelf.app.data.LocalFolder
import com.audiobookshelf.app.data.PodcastEpisode
import com.audiobookshelf.app.device.DeviceManager
import com.fasterxml.jackson.annotation.JsonIgnore
import java.io.File
data class DownloadItemPart(
val id: String,
val downloadItemId: String,
val filename: String,
val finalDestinationPath:String,
val itemTitle: String,
val serverPath: String,
val localFolderName: String,
val localFolderUrl: String,
val localFolderId: String,
val audioTrack: AudioTrack?,
val episode: PodcastEpisode?,
var completed:Boolean,
var moved:Boolean,
var isMoving:Boolean,
var failed:Boolean,
@JsonIgnore val uri: Uri,
@JsonIgnore val destinationUri: Uri,
@JsonIgnore val finalDestinationUri: Uri,
var downloadId: Long?,
var progress: Long
) {
companion object {
fun make(downloadItemId:String, filename:String, destinationFile: File, finalDestinationFile: File, itemTitle:String, serverPath:String, localFolder: LocalFolder, audioTrack: AudioTrack?, episode: PodcastEpisode?) :DownloadItemPart {
val destinationUri = Uri.fromFile(destinationFile)
val finalDestinationUri = Uri.fromFile(finalDestinationFile)
var downloadUrl = "${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}"
if (serverPath.endsWith("/cover")) downloadUrl += "&format=jpeg" // For cover images force to jpeg
val downloadUri = Uri.parse(downloadUrl)
Log.d("DownloadItemPart", "Audio File Destination Uri: $destinationUri | Final Destination Uri: $finalDestinationUri | Download URI $downloadUri")
return DownloadItemPart(
id = DeviceManager.getBase64Id(finalDestinationFile.absolutePath),
downloadItemId,
filename = filename,
finalDestinationPath = finalDestinationFile.absolutePath,
itemTitle = itemTitle,
serverPath = serverPath,
localFolderName = localFolder.name,
localFolderUrl = localFolder.contentUrl,
localFolderId = localFolder.id,
audioTrack = audioTrack,
episode = episode,
completed = false,
moved = false,
isMoving = false,
failed = false,
uri = downloadUri,
destinationUri = destinationUri,
finalDestinationUri = finalDestinationUri,
downloadId = null,
progress = 0
)
}
}
@get:JsonIgnore
val fileSize get() = audioTrack?.metadata?.size ?: 0
@JsonIgnore
fun getDownloadRequest(): DownloadManager.Request {
val dlRequest = DownloadManager.Request(uri)
dlRequest.setTitle(filename)
dlRequest.setDescription("Downloading to $localFolderName for book $itemTitle")
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
dlRequest.setDestinationUri(destinationUri)
return dlRequest
}
}

View file

@ -31,6 +31,7 @@ import com.audiobookshelf.app.R
import com.audiobookshelf.app.data.* import com.audiobookshelf.app.data.*
import com.audiobookshelf.app.data.DeviceInfo import com.audiobookshelf.app.data.DeviceInfo
import com.audiobookshelf.app.device.DeviceManager import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.managers.DbManager
import com.audiobookshelf.app.media.MediaManager import com.audiobookshelf.app.media.MediaManager
import com.audiobookshelf.app.server.ApiHandler import com.audiobookshelf.app.server.ApiHandler
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
@ -49,8 +50,8 @@ import kotlin.concurrent.schedule
const val SLEEP_TIMER_WAKE_UP_EXPIRATION = 120000L // 2m const val SLEEP_TIMER_WAKE_UP_EXPIRATION = 120000L // 2m
const val PLAYER_CAST = "cast-player"; const val PLAYER_CAST = "cast-player"
const val PLAYER_EXO = "exo-player"; const val PLAYER_EXO = "exo-player"
class PlayerNotificationService : MediaBrowserServiceCompat() { class PlayerNotificationService : MediaBrowserServiceCompat() {
@ -61,6 +62,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
var isSwitchingPlayer = false // Used when switching between cast player and exoplayer var isSwitchingPlayer = false // Used when switching between cast player and exoplayer
} }
private val tag = "PlayerNotificationService"
interface ClientEventEmitter { interface ClientEventEmitter {
fun onPlaybackSession(playbackSession:PlaybackSession) fun onPlaybackSession(playbackSession:PlaybackSession)
fun onPlaybackClosed() fun onPlaybackClosed()
@ -76,8 +79,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
fun onNetworkMeteredChanged(isUnmetered:Boolean) fun onNetworkMeteredChanged(isUnmetered:Boolean)
fun onMediaItemHistoryUpdated(mediaItemHistory:MediaItemHistory) fun onMediaItemHistoryUpdated(mediaItemHistory:MediaItemHistory)
} }
private val tag = "PlayerService"
private val binder = LocalBinder() private val binder = LocalBinder()
var clientEventEmitter:ClientEventEmitter? = null var clientEventEmitter:ClientEventEmitter? = null

View file

@ -10,12 +10,15 @@ import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.callback.FileCallback import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.file.* import com.anggrayudi.storage.file.*
import com.anggrayudi.storage.media.FileDescription 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
import com.audiobookshelf.app.device.FolderScanner import com.audiobookshelf.app.device.FolderScanner
import com.audiobookshelf.app.models.DownloadItem
import com.audiobookshelf.app.models.DownloadItemPart
import com.audiobookshelf.app.server.ApiHandler import com.audiobookshelf.app.server.ApiHandler
import com.fasterxml.jackson.annotation.JsonIgnore import com.audiobookshelf.app.managers.DownloadItemManager
import com.fasterxml.jackson.core.json.JsonReadFeature import com.fasterxml.jackson.core.json.JsonReadFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.getcapacitor.JSObject import com.getcapacitor.JSObject
@ -32,99 +35,34 @@ import java.io.File
@CapacitorPlugin(name = "AbsDownloader") @CapacitorPlugin(name = "AbsDownloader")
class AbsDownloader : Plugin() { class AbsDownloader : Plugin() {
private val tag = "AbsDownloader" private val tag = "AbsDownloader"
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()) private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
lateinit var mainActivity: MainActivity lateinit var mainActivity: MainActivity
lateinit var downloadManager: DownloadManager lateinit var downloadManager: DownloadManager
lateinit var apiHandler: ApiHandler lateinit var apiHandler: ApiHandler
lateinit var folderScanner: FolderScanner lateinit var folderScanner: FolderScanner
lateinit var downloadItemManager: DownloadItemManager
data class DownloadItemPart( private var downloadQueue: MutableList<DownloadItem> = mutableListOf()
val id: String,
val filename: String,
val finalDestinationPath:String,
val itemTitle: String,
val serverPath: String,
val localFolderName: String,
val localFolderUrl: String,
val localFolderId: String,
val audioTrack: AudioTrack?,
val episode:PodcastEpisode?,
var completed:Boolean,
var moved:Boolean,
var failed:Boolean,
@JsonIgnore val uri: Uri,
@JsonIgnore val destinationUri: Uri,
@JsonIgnore val finalDestinationUri: Uri,
var downloadId: Long?,
var progress: Long
) {
companion object {
fun make(filename:String, destinationFile:File, finalDestinationFile:File, itemTitle:String, serverPath:String, localFolder:LocalFolder, audioTrack:AudioTrack?, episode:PodcastEpisode?) :DownloadItemPart {
val destinationUri = Uri.fromFile(destinationFile)
val finalDestinationUri = Uri.fromFile(finalDestinationFile)
var downloadUrl = "${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}" val clientEventEmitter = (object : DownloadItemManager.DownloadEventEmitter {
if (serverPath.endsWith("/cover")) downloadUrl += "&format=jpeg" // For cover images force to jpeg override fun onDownloadItem(downloadItem:DownloadItem) {
val downloadUri = Uri.parse(downloadUrl) notifyListeners("onDownloadItem", JSObject(jacksonMapper.writeValueAsString(downloadItem)))
Log.d("DownloadItemPart", "Audio File Destination Uri: $destinationUri | Final Destination Uri: $finalDestinationUri | Download URI $downloadUri")
return DownloadItemPart(
id = DeviceManager.getBase64Id(finalDestinationFile.absolutePath),
filename = filename, finalDestinationFile.absolutePath,
itemTitle = itemTitle,
serverPath = serverPath,
localFolderName = localFolder.name,
localFolderUrl = localFolder.contentUrl,
localFolderId = localFolder.id,
audioTrack = audioTrack,
episode = episode,
completed = false,
moved = false,
failed = false,
uri = downloadUri,
destinationUri = destinationUri,
finalDestinationUri = finalDestinationUri,
downloadId = null,
progress = 0
)
}
} }
override fun onDownloadItemPartUpdate(downloadItemPart:DownloadItemPart) {
@JsonIgnore notifyListeners("onDownloadItemPartUpdate", JSObject(jacksonMapper.writeValueAsString(downloadItemPart)))
fun getDownloadRequest(): DownloadManager.Request {
val dlRequest = DownloadManager.Request(uri)
dlRequest.setTitle(filename)
dlRequest.setDescription("Downloading to $localFolderName for book $itemTitle")
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
dlRequest.setDestinationUri(destinationUri)
return dlRequest
} }
} override fun onDownloadItemComplete(jsobj:JSObject) {
notifyListeners("onItemDownloadComplete", jsobj)
data class DownloadItem( }
val id: String, })
val libraryItemId:String,
val episodeId:String?,
val userMediaProgress:MediaProgress?,
val serverConnectionConfigId:String,
val serverAddress:String,
val serverUserId:String,
val mediaType: String,
val itemFolderPath:String,
val localFolder: LocalFolder,
val itemTitle: String,
val media:MediaType,
val downloadItemParts: MutableList<DownloadItemPart>
)
var downloadQueue: MutableList<DownloadItem> = mutableListOf()
override fun load() { override fun load() {
mainActivity = (activity as MainActivity) mainActivity = (activity as MainActivity)
downloadManager = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager downloadManager = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
folderScanner = FolderScanner(mainActivity) folderScanner = FolderScanner(mainActivity)
apiHandler = ApiHandler(mainActivity) apiHandler = ApiHandler(mainActivity)
downloadItemManager = DownloadItemManager(downloadManager, folderScanner, mainActivity, clientEventEmitter)
Log.d(tag, "Build SDK ${Build.VERSION.SDK_INT}") Log.d(tag, "Build SDK ${Build.VERSION.SDK_INT}")
} }
@ -204,13 +142,16 @@ 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")
if (libraryItem.mediaType == "book") { if (libraryItem.mediaType == "book") {
val bookTitle = cleanStringForFileSystem(libraryItem.media.metadata.title) val bookTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
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 itemFolderPath = "${localFolder.absolutePath}/$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 downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null, libraryItem.userMediaProgress,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
// Create download item part for each audio track // Create download item part for each audio track
@ -222,43 +163,54 @@ class AbsDownloader : Plugin() {
val finalDestinationFile = File("$itemFolderPath/$destinationFilename") val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
val destinationFile = File("$tempFolderPath/$destinationFilename") val destinationFile = File("$tempFolderPath/$destinationFilename")
if (destinationFile.exists()) {
Log.d(tag, "TEMP Audio file already exists, removing it from ${destinationFile.absolutePath}")
destinationFile.delete()
}
if (finalDestinationFile.exists()) { if (finalDestinationFile.exists()) {
Log.d(tag, "Audio file already exists, removing it from ${finalDestinationFile.absolutePath}") Log.d(tag, "Audio file already exists, removing it from ${finalDestinationFile.absolutePath}")
finalDestinationFile.delete() finalDestinationFile.delete()
} }
val downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,audioTrack,null) val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,audioTrack,null)
downloadItem.downloadItemParts.add(downloadItemPart) downloadItem.downloadItemParts.add(downloadItemPart)
val dlRequest = downloadItemPart.getDownloadRequest() // val dlRequest = downloadItemPart.getDownloadRequest()
val downloadId = downloadManager.enqueue(dlRequest) // val downloadId = downloadManager.enqueue(dlRequest)
downloadItemPart.downloadId = downloadId // downloadItemPart.downloadId = downloadId
} }
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 serverPath = "/api/items/${libraryItem.id}/cover" val serverPath = "/api/items/${libraryItem.id}/cover"
val destinationFilename = "cover.jpg" val destinationFilename = "cover-${libraryItem.id}.jpg"
val destinationFile = File("$tempFolderPath/$destinationFilename") val destinationFile = File("$tempFolderPath/$destinationFilename")
val finalDestinationFile = File("$itemFolderPath/$destinationFilename") val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
if (destinationFile.exists()) {
Log.d(tag, "TEMP Audio file already exists, removing it from ${destinationFile.absolutePath}")
destinationFile.delete()
}
if (finalDestinationFile.exists()) { if (finalDestinationFile.exists()) {
Log.d(tag, "Cover already exists, removing it from ${finalDestinationFile.absolutePath}") Log.d(tag, "Cover already exists, removing it from ${finalDestinationFile.absolutePath}")
finalDestinationFile.delete() finalDestinationFile.delete()
} }
val downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,null,null) val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,null,null)
downloadItem.downloadItemParts.add(downloadItemPart) downloadItem.downloadItemParts.add(downloadItemPart)
val dlRequest = downloadItemPart.getDownloadRequest() // val dlRequest = downloadItemPart.getDownloadRequest()
val downloadId = downloadManager.enqueue(dlRequest) // val downloadId = downloadManager.enqueue(dlRequest)
downloadItemPart.downloadId = downloadId // downloadItemPart.downloadId = downloadId
} }
downloadQueue.add(downloadItem) // downloadQueue.add(downloadItem)
startWatchingDownloads(downloadItem) // startWatchingDownloads(downloadItem)
DeviceManager.dbManager.saveDownloadItem(downloadItem) // DeviceManager.dbManager.saveDownloadItem(downloadItem)
downloadItemManager.addDownloadItem(downloadItem)
} }
} else { } else {
// Podcast episode download // Podcast episode download
@ -281,7 +233,7 @@ class AbsDownloader : Plugin() {
finalDestinationFile.delete() finalDestinationFile.delete()
} }
var downloadItemPart = DownloadItemPart.make(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 dlRequest = downloadItemPart.getDownloadRequest()
@ -298,7 +250,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(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() dlRequest = downloadItemPart.getDownloadRequest()

View file

@ -85,21 +85,28 @@ class AbsFileSystem : Plugin() {
call.resolve(JSObject(jacksonMapper.writeValueAsString(localFolder))) call.resolve(JSObject(jacksonMapper.writeValueAsString(localFolder)))
} }
override fun onStorageAccessDenied(requestCode: Int, folder: DocumentFile?, storageType: StorageType) { override fun onStorageAccessDenied(
requestCode: Int,
folder: DocumentFile?,
storageType: StorageType,
storageId: String
) {
Log.e(tag, "Storage Access Denied ${folder?.getAbsolutePath(mainActivity)}")
val jsobj = JSObject() val jsobj = JSObject()
if (requestCode == REQUEST_CODE_SELECT_FOLDER) { if (requestCode == REQUEST_CODE_SELECT_FOLDER) {
val builder: AlertDialog.Builder = AlertDialog.Builder(mainActivity) val builder: AlertDialog.Builder = AlertDialog.Builder(mainActivity)
builder.setMessage( builder.setMessage(
"You have no write access to this storage, thus selecting this folder is useless." + "You have no write access to this storage, thus selecting this folder is useless." +
"\nWould you like to grant access to this folder?") "\nWould you like to grant access to this folder?")
builder.setNegativeButton("Dont Allow") { _, _ -> builder.setNegativeButton("Dont Allow") { _, _ ->
run { run {
jsobj.put("error", "User Canceled, Access Denied") jsobj.put("error", "User Canceled, Access Denied")
call.resolve(jsobj) call.resolve(jsobj)
} }
} }
builder.setPositiveButton("Allow.") { _, _ -> mainActivity.storageHelper.requestStorageAccess(REQUEST_CODE_SDCARD_ACCESS, storageType) } builder.setPositiveButton("Allow.") { _, _ -> mainActivity.storageHelper.requestStorageAccess(REQUEST_CODE_SDCARD_ACCESS, initialPath = FileFullPath(mainActivity, storageId, "")) }
builder.show() builder.show()
} else { } else {
Log.d(TAG, "STORAGE ACCESS DENIED $requestCode") Log.d(TAG, "STORAGE ACCESS DENIED $requestCode")

View file

@ -9,7 +9,7 @@
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
</style> </style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.NoActionBar"> <style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
<item name="android:background">@null</item> <item name="android:background">@null</item>
@ -18,7 +18,7 @@
</style> </style>
<style name="AppTheme.NoActionBarLaunch" parent="AppTheme.NoActionBar"> <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<!-- <item name="android:background">@drawable/screen</item>--> <!-- <item name="android:background">@drawable/screen</item>-->
</style> </style>
<style name="Widget.Android.AppWidget.Container" parent="android:Widget"> <style name="Widget.Android.AppWidget.Container" parent="android:Widget">

View file

@ -1,15 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.5.30' ext.kotlin_version = '1.7.20'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.google.gms:google-services:4.3.10' classpath 'com.google.gms:google-services:4.3.13'
classpath 'com.android.tools.build:gradle:7.2.2' classpath 'com.android.tools.build:gradle:7.4.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@ -29,3 +29,4 @@ allprojects {
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View file

@ -14,8 +14,8 @@ project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/
include ':capacitor-network' include ':capacitor-network'
project(':capacitor-network').projectDir = new File('../node_modules/@capacitor/network/android') project(':capacitor-network').projectDir = new File('../node_modules/@capacitor/network/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
include ':capacitor-status-bar' include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android') project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
include ':capacitor-storage'
project(':capacitor-storage').projectDir = new File('../node_modules/@capacitor/storage/android')

View file

@ -1,6 +1,6 @@
#Sun Apr 17 13:28:55 CDT 2022 #Sun Apr 17 13:28:55 CDT 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View file

@ -1,16 +1,16 @@
ext { ext {
minSdkVersion = 24 minSdkVersion = 24
compileSdkVersion = 31 compileSdkVersion = 33
targetSdkVersion = 31 targetSdkVersion = 32
androidxActivityVersion = '1.2.0' androidxActivityVersion = '1.4.0'
androidxAppCompatVersion = '1.4.1' androidxAppCompatVersion = '1.4.2'
androidxCoordinatorLayoutVersion = '1.2.0' androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.6.0' androidxCoreVersion = '1.8.0'
androidPlayCore = '1.9.0' androidPlayCore = '1.9.0'
androidxFragmentVersion = '1.3.0' androidxFragmentVersion = '1.4.1'
junitVersion = '4.13.1' junitVersion = '4.13.2'
androidxJunitVersion = '1.1.2' androidxJunitVersion = '1.1.3'
androidxEspressoCoreVersion = '3.3.0' androidxEspressoCoreVersion = '3.4.0'
cordovaAndroidVersion = '10.1.1' cordovaAndroidVersion = '10.1.1'
androidx_car_version = '1.0.0-alpha7' androidx_car_version = '1.0.0-alpha7'
androidx_core_ktx_version = '1.7.0' androidx_core_ktx_version = '1.7.0'
@ -27,11 +27,13 @@ ext {
gradle_version = '3.1.4' gradle_version = '3.1.4'
gson_version = '2.8.5' gson_version = '2.8.5'
junit_version = '4.13' junit_version = '4.13'
kotlin_version = '1.5.30' kotlin_version = '1.7.20'
kotlin_coroutines_version = '1.1.0' kotlin_coroutines_version = '1.1.0'
multidex_version = '1.0.3' multidex_version = '1.0.3'
play_services_auth_version = '18.1.0' play_services_auth_version = '18.1.0'
recycler_view_version = '1.1.0' recycler_view_version = '1.1.0'
robolectric_version = '4.2' robolectric_version = '4.2'
test_runner_version = '1.1.0' test_runner_version = '1.1.0'
coreSplashScreenVersion = '1.0.0-rc01'
androidxWebkitVersion = '1.4.0'
} }

View file

@ -651,7 +651,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -705,7 +705,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
@ -723,7 +723,7 @@
CURRENT_PROJECT_VERSION = 17; CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = 7UFJ7D8V6A; DEVELOPMENT_TEAM = 7UFJ7D8V6A;
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.9.60; MARKETING_VERSION = 0.9.60;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
@ -747,7 +747,7 @@
CURRENT_PROJECT_VERSION = 17; CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = 7UFJ7D8V6A; DEVELOPMENT_TEAM = 7UFJ7D8V6A;
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.9.60; MARKETING_VERSION = 0.9.60;
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app; PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
@ -768,7 +768,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
@ -790,7 +790,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.AudiobookshelfUnitTests; PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.AudiobookshelfUnitTests;

View file

@ -94,15 +94,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
backgroundCompletionHandler = completionHandler backgroundCompletionHandler = completionHandler
} }
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let statusBarRect = self.window?.windowScene?.statusBarManager?.statusBarFrame
guard let touchPoint = event?.allTouches?.first?.location(in: self.window) else { return }
if statusBarRect?.contains(touchPoint) ?? false {
NotificationCenter.default.post(name: .capacitorStatusBarTapped, object: nil)
}
}
} }

View file

@ -22,11 +22,6 @@
<string>$(CURRENT_PROJECT_VERSION)</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSUserActivityTypes</key> <key>NSUserActivityTypes</key>
<array> <array>
<string>INPlayMediaIntent</string> <string>INPlayMediaIntent</string>

View file

@ -1,4 +1,6 @@
platform :ios, '12.0' require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
platform :ios, '13.0'
use_frameworks! use_frameworks!
# workaround to avoid Xcode caching of Pods that requires # workaround to avoid Xcode caching of Pods that requires
@ -9,12 +11,12 @@ install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods def capacitor_pods
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorApp', :path => '..\..\node_modules\@capacitor\app' pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
pod 'CapacitorDialog', :path => '..\..\node_modules\@capacitor\dialog' pod 'CapacitorDialog', :path => '../../node_modules/@capacitor/dialog'
pod 'CapacitorHaptics', :path => '..\..\node_modules\@capacitor\haptics' pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
pod 'CapacitorNetwork', :path => '..\..\node_modules\@capacitor\network' pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network'
pod 'CapacitorStatusBar', :path => '..\..\node_modules\@capacitor\status-bar' pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
pod 'CapacitorStorage', :path => '..\..\node_modules\@capacitor\storage' pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins' pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
end end
@ -25,3 +27,8 @@ target 'Audiobookshelf' do
pod 'RealmSwift', '~>10' pod 'RealmSwift', '~>10'
pod 'Alamofire', '~> 5.5' pod 'Alamofire', '~> 5.5'
end end
post_install do |installer|
assertDeploymentTarget(installer)
end

719
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,16 +12,15 @@
"ionic:serve": "npm run start" "ionic:serve": "npm run start"
}, },
"dependencies": { "dependencies": {
"@capacitor/android": "^3.4.3", "@capacitor/android": "^4.0.0",
"@capacitor/app": "^1.1.1", "@capacitor/app": "^4.0.0",
"@capacitor/cli": "^3.4.3", "@capacitor/core": "^4.0.0",
"@capacitor/core": "^3.4.3", "@capacitor/dialog": "^4.0.0",
"@capacitor/dialog": "^1.0.7", "@capacitor/haptics": "^4.0.0",
"@capacitor/haptics": "^1.1.4", "@capacitor/ios": "^4.0.0",
"@capacitor/ios": "^3.2.2", "@capacitor/network": "^4.0.0",
"@capacitor/network": "^1.0.7", "@capacitor/preferences": "^4.0.2",
"@capacitor/status-bar": "^1.0.8", "@capacitor/status-bar": "^4.0.0",
"@capacitor/storage": "^1.2.5",
"@nuxtjs/axios": "^5.13.6", "@nuxtjs/axios": "^5.13.6",
"cordova-plugin-screen-orientation": "^3.0.2", "cordova-plugin-screen-orientation": "^3.0.2",
"core-js": "^3.15.1", "core-js": "^3.15.1",
@ -38,6 +37,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "7.13.15", "@babel/core": "7.13.15",
"@babel/preset-env": "7.13.15", "@babel/preset-env": "7.13.15",
"@capacitor/cli": "^4.0.0",
"@nuxtjs/tailwindcss": "^4.2.0", "@nuxtjs/tailwindcss": "^4.2.0",
"postcss": "^8.3.5" "postcss": "^8.3.5"
} }

View file

@ -1,4 +1,4 @@
import { Storage } from '@capacitor/storage' import { Preferences } from '@capacitor/preferences'
class LocalStorage { class LocalStorage {
constructor(vuexStore) { constructor(vuexStore) {
@ -7,7 +7,7 @@ class LocalStorage {
async setUserSettings(settings) { async setUserSettings(settings) {
try { try {
await Storage.set({ key: 'userSettings', value: JSON.stringify(settings) }) await Preferences.set({ key: 'userSettings', value: JSON.stringify(settings) })
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to update user settings', error) console.error('[LocalStorage] Failed to update user settings', error)
} }
@ -15,7 +15,7 @@ class LocalStorage {
async getUserSettings() { async getUserSettings() {
try { try {
const settingsObj = await Storage.get({ key: 'userSettings' }) || {} const settingsObj = await Preferences.get({ key: 'userSettings' }) || {}
return settingsObj.value ? JSON.parse(settingsObj.value) : null return settingsObj.value ? JSON.parse(settingsObj.value) : null
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to get user settings', error) console.error('[LocalStorage] Failed to get user settings', error)
@ -25,7 +25,7 @@ class LocalStorage {
async setServerSettings(settings) { async setServerSettings(settings) {
try { try {
await Storage.set({ key: 'serverSettings', value: JSON.stringify(settings) }) await Preferences.set({ key: 'serverSettings', value: JSON.stringify(settings) })
console.log('Saved server settings', JSON.stringify(settings)) console.log('Saved server settings', JSON.stringify(settings))
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to update server settings', error) console.error('[LocalStorage] Failed to update server settings', error)
@ -34,7 +34,7 @@ class LocalStorage {
async getServerSettings() { async getServerSettings() {
try { try {
var settingsObj = await Storage.get({ key: 'serverSettings' }) || {} var settingsObj = await Preferences.get({ key: 'serverSettings' }) || {}
return settingsObj.value ? JSON.parse(settingsObj.value) : null return settingsObj.value ? JSON.parse(settingsObj.value) : null
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to get server settings', error) console.error('[LocalStorage] Failed to get server settings', error)
@ -44,7 +44,7 @@ class LocalStorage {
async setUseChapterTrack(useChapterTrack) { async setUseChapterTrack(useChapterTrack) {
try { try {
await Storage.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' }) await Preferences.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' })
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to set use chapter track', error) console.error('[LocalStorage] Failed to set use chapter track', error)
} }
@ -52,7 +52,7 @@ class LocalStorage {
async getUseChapterTrack() { async getUseChapterTrack() {
try { try {
var obj = await Storage.get({ key: 'useChapterTrack' }) || {} var obj = await Preferences.get({ key: 'useChapterTrack' }) || {}
return obj.value === '1' return obj.value === '1'
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to get use chapter track', error) console.error('[LocalStorage] Failed to get use chapter track', error)
@ -62,7 +62,7 @@ class LocalStorage {
async setUseTotalTrack(useTotalTrack) { async setUseTotalTrack(useTotalTrack) {
try { try {
await Storage.set({ key: 'useTotalTrack', value: useTotalTrack ? '1' : '0' }) await Preferences.set({ key: 'useTotalTrack', value: useTotalTrack ? '1' : '0' })
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to set use total track', error) console.error('[LocalStorage] Failed to set use total track', error)
} }
@ -70,7 +70,7 @@ class LocalStorage {
async getUseTotalTrack() { async getUseTotalTrack() {
try { try {
var obj = await Storage.get({ key: 'useTotalTrack' }) || {} var obj = await Preferences.get({ key: 'useTotalTrack' }) || {}
return obj.value === '1' return obj.value === '1'
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to get use total track', error) console.error('[LocalStorage] Failed to get use total track', error)
@ -80,7 +80,7 @@ class LocalStorage {
async setPlayerLock(lock) { async setPlayerLock(lock) {
try { try {
await Storage.set({ key: 'playerLock', value: lock ? '1' : '0' }) await Preferences.set({ key: 'playerLock', value: lock ? '1' : '0' })
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to set player lock', error) console.error('[LocalStorage] Failed to set player lock', error)
} }
@ -88,7 +88,7 @@ class LocalStorage {
async getPlayerLock() { async getPlayerLock() {
try { try {
var obj = await Storage.get({ key: 'playerLock' }) || {} var obj = await Preferences.get({ key: 'playerLock' }) || {}
return obj.value === '1' return obj.value === '1'
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to get player lock', error) console.error('[LocalStorage] Failed to get player lock', error)
@ -98,7 +98,7 @@ class LocalStorage {
async setBookshelfListView(useIt) { async setBookshelfListView(useIt) {
try { try {
await Storage.set({ key: 'bookshelfListView', value: useIt ? '1' : '0' }) await Preferences.set({ key: 'bookshelfListView', value: useIt ? '1' : '0' })
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to set bookshelf list view', error) console.error('[LocalStorage] Failed to set bookshelf list view', error)
} }
@ -106,7 +106,7 @@ class LocalStorage {
async getBookshelfListView() { async getBookshelfListView() {
try { try {
var obj = await Storage.get({ key: 'bookshelfListView' }) || {} var obj = await Preferences.get({ key: 'bookshelfListView' }) || {}
return obj.value === '1' return obj.value === '1'
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to get bookshelf list view', error) console.error('[LocalStorage] Failed to get bookshelf list view', error)
@ -116,7 +116,7 @@ class LocalStorage {
async setLastLibraryId(libraryId) { async setLastLibraryId(libraryId) {
try { try {
await Storage.set({ key: 'lastLibraryId', value: libraryId }) await Preferences.set({ key: 'lastLibraryId', value: libraryId })
console.log('[LocalStorage] Set Last Library Id', libraryId) console.log('[LocalStorage] Set Last Library Id', libraryId)
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to set last library id', error) console.error('[LocalStorage] Failed to set last library id', error)
@ -125,7 +125,7 @@ class LocalStorage {
async removeLastLibraryId() { async removeLastLibraryId() {
try { try {
await Storage.remove({ key: 'lastLibraryId' }) await Preferences.remove({ key: 'lastLibraryId' })
console.log('[LocalStorage] Remove Last Library Id') console.log('[LocalStorage] Remove Last Library Id')
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to remove last library id', error) console.error('[LocalStorage] Failed to remove last library id', error)
@ -134,7 +134,7 @@ class LocalStorage {
async getLastLibraryId() { async getLastLibraryId() {
try { try {
var obj = await Storage.get({ key: 'lastLibraryId' }) || {} var obj = await Preferences.get({ key: 'lastLibraryId' }) || {}
return obj.value || null return obj.value || null
} catch (error) { } catch (error) {
console.error('[LocalStorage] Failed to get last library id', error) console.error('[LocalStorage] Failed to get last library id', error)