Auto formatting AbsDownloader.kt

This commit is contained in:
Nicholas Wallace
2025-02-28 20:37:08 -07:00
parent feef527bed
commit ee0ab43fc5
@@ -8,10 +8,10 @@ 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.managers.DownloadItemManager
import com.audiobookshelf.app.models.DownloadItem import com.audiobookshelf.app.models.DownloadItem
import com.audiobookshelf.app.models.DownloadItemPart import com.audiobookshelf.app.models.DownloadItemPart
import com.audiobookshelf.app.server.ApiHandler import com.audiobookshelf.app.server.ApiHandler
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
@@ -24,7 +24,9 @@ import java.io.File
@CapacitorPlugin(name = "AbsDownloader") @CapacitorPlugin(name = "AbsDownloader")
class AbsDownloader : Plugin() { class AbsDownloader : Plugin() {
private val tag = "AbsDownloader" private val tag = "AbsDownloader"
private 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
@@ -32,24 +34,32 @@ class AbsDownloader : Plugin() {
lateinit var folderScanner: FolderScanner lateinit var folderScanner: FolderScanner
lateinit var downloadItemManager: DownloadItemManager lateinit var downloadItemManager: DownloadItemManager
private val clientEventEmitter = (object : DownloadItemManager.DownloadEventEmitter { private val clientEventEmitter =
override fun onDownloadItem(downloadItem:DownloadItem) { (object : DownloadItemManager.DownloadEventEmitter {
notifyListeners("onDownloadItem", JSObject(jacksonMapper.writeValueAsString(downloadItem))) override fun onDownloadItem(downloadItem: DownloadItem) {
} notifyListeners(
override fun onDownloadItemPartUpdate(downloadItemPart:DownloadItemPart) { "onDownloadItem",
notifyListeners("onDownloadItemPartUpdate", JSObject(jacksonMapper.writeValueAsString(downloadItemPart))) JSObject(jacksonMapper.writeValueAsString(downloadItem))
} )
override fun onDownloadItemComplete(jsobj:JSObject) { }
notifyListeners("onItemDownloadComplete", jsobj) override fun onDownloadItemPartUpdate(downloadItemPart: DownloadItemPart) {
} notifyListeners(
}) "onDownloadItemPartUpdate",
JSObject(jacksonMapper.writeValueAsString(downloadItemPart))
)
}
override fun onDownloadItemComplete(jsobj: JSObject) {
notifyListeners("onItemDownloadComplete", jsobj)
}
})
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) downloadItemManager =
DownloadItemManager(downloadManager, folderScanner, mainActivity, clientEventEmitter)
} }
@PluginMethod @PluginMethod
@@ -58,12 +68,17 @@ class AbsDownloader : Plugin() {
var episodeId = call.data.getString("episodeId").toString() var episodeId = call.data.getString("episodeId").toString()
if (episodeId == "null") episodeId = "" if (episodeId == "null") episodeId = ""
var localFolderId = call.data.getString("localFolderId", "").toString() var localFolderId = call.data.getString("localFolderId", "").toString()
Log.d(tag, "Download library item $libraryItemId to folder $localFolderId / episode: $episodeId") Log.d(
tag,
"Download library item $libraryItemId to folder $localFolderId / episode: $episodeId"
)
val downloadId = if (episodeId.isEmpty()) libraryItemId else "$libraryItemId-$episodeId" val downloadId = if (episodeId.isEmpty()) libraryItemId else "$libraryItemId-$episodeId"
if (downloadItemManager.downloadItemQueue.find { it.id == downloadId } != null) { if (downloadItemManager.downloadItemQueue.find { it.id == downloadId } != null) {
Log.d(tag, "Download already started for this media entity $downloadId") Log.d(tag, "Download already started for this media entity $downloadId")
return call.resolve(JSObject("{\"error\":\"Download already started for this media entity\"}")) return call.resolve(
JSObject("{\"error\":\"Download already started for this media entity\"}")
)
} }
apiHandler.getLibraryItemWithProgress(libraryItemId, episodeId) { libraryItem -> apiHandler.getLibraryItemWithProgress(libraryItemId, episodeId) { libraryItem ->
@@ -79,7 +94,17 @@ class AbsDownloader : Plugin() {
if (localFolder == null && localFolderId.startsWith("internal-")) { if (localFolder == null && localFolderId.startsWith("internal-")) {
Log.d(tag, "Creating new App Storage internal LocalFolder $localFolderId") Log.d(tag, "Creating new App Storage internal LocalFolder $localFolderId")
localFolder = LocalFolder(localFolderId, "Internal App Storage", "", "", "", "", "internal", libraryItem.mediaType) localFolder =
LocalFolder(
localFolderId,
"Internal App Storage",
"",
"",
"",
"",
"internal",
libraryItem.mediaType
)
DeviceManager.dbManager.saveLocalFolder(localFolder) DeviceManager.dbManager.saveLocalFolder(localFolder)
} }
@@ -89,9 +114,8 @@ class AbsDownloader : Plugin() {
call.resolve(JSObject("{\"error\":\"Invalid library item not a podcast\"}")) call.resolve(JSObject("{\"error\":\"Invalid library item not a podcast\"}"))
} else if (episodeId.isNotEmpty()) { } else if (episodeId.isNotEmpty()) {
val podcast = libraryItem.media as Podcast val podcast = libraryItem.media as Podcast
val episode = podcast.episodes?.find { podcastEpisode -> val episode =
podcastEpisode.id == episodeId podcast.episodes?.find { podcastEpisode -> podcastEpisode.id == episodeId }
}
if (episode == null) { if (episode == null) {
call.resolve(JSObject("{\"error\":\"Invalid podcast episode not found\"}")) call.resolve(JSObject("{\"error\":\"Invalid podcast episode not found\"}"))
} else { } else {
@@ -118,21 +142,25 @@ class AbsDownloader : Plugin() {
// Replace characters that cant be used in the file system // Replace characters that cant be used in the file system
// Reserved characters: ?:\"*|/\\<> // Reserved characters: ?:\"*|/\\<>
private fun cleanStringForFileSystem(str:String):String { private fun cleanStringForFileSystem(str: String): String {
val reservedCharacters = listOf("?", "\"", "*", "|", "/", "\\", "<", ">") val reservedCharacters = listOf("?", "\"", "*", "|", "/", "\\", "<", ">")
var newTitle = str var newTitle = str
newTitle = newTitle.replace(":", " -") // Special case replace : with - newTitle = newTitle.replace(":", " -") // Special case replace : with -
reservedCharacters.forEach { reservedCharacters.forEach { newTitle = newTitle.replace(it, "") }
newTitle = newTitle.replace(it, "")
}
return newTitle return newTitle
} }
private fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder, episode:PodcastEpisode?) { private fun startLibraryItemDownload(
libraryItem: LibraryItem,
localFolder: LocalFolder,
episode: PodcastEpisode?
) {
val isInternal = localFolder.id.startsWith("internal-") val isInternal = localFolder.id.startsWith("internal-")
val tempFolderPath = if (isInternal) "${mainActivity.filesDir}/downloads/${libraryItem.id}" else mainActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) val tempFolderPath =
if (isInternal) "${mainActivity.filesDir}/downloads/${libraryItem.id}"
else mainActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
Log.d(tag, "downloadCacheDirectory=$tempFolderPath") Log.d(tag, "downloadCacheDirectory=$tempFolderPath")
@@ -143,8 +171,25 @@ class AbsDownloader : Plugin() {
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 itemSubfolder = "$bookAuthor/$bookTitle" val itemSubfolder = "$bookAuthor/$bookTitle"
val itemFolderPath = if (isInternal) "$tempFolderPath" else "${localFolder.absolutePath}/$itemSubfolder" val itemFolderPath =
val downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null, libraryItem.userMediaProgress,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, itemSubfolder, libraryItem.media, mutableListOf()) if (isInternal) "$tempFolderPath" else "${localFolder.absolutePath}/$itemSubfolder"
val downloadItem =
DownloadItem(
libraryItem.id,
libraryItem.id,
null,
libraryItem.userMediaProgress,
DeviceManager.serverConnectionConfig?.id ?: "",
DeviceManager.serverAddress,
DeviceManager.serverUserId,
libraryItem.mediaType,
itemFolderPath,
localFolder,
bookTitle,
itemSubfolder,
libraryItem.media,
mutableListOf()
)
val book = libraryItem.media as Book val book = libraryItem.media as Book
book.ebookFile?.let { ebookFile -> book.ebookFile?.let { ebookFile ->
@@ -155,16 +200,35 @@ class AbsDownloader : Plugin() {
val destinationFile = File("$tempFolderPath/$destinationFilename") val destinationFile = File("$tempFolderPath/$destinationFilename")
if (destinationFile.exists()) { if (destinationFile.exists()) {
Log.d(tag, "TEMP ebook file already exists, removing it from ${destinationFile.absolutePath}") Log.d(
tag,
"TEMP ebook file already exists, removing it from ${destinationFile.absolutePath}"
)
destinationFile.delete() destinationFile.delete()
} }
if (finalDestinationFile.exists()) { if (finalDestinationFile.exists()) {
Log.d(tag, "ebook file already exists, removing it from ${finalDestinationFile.absolutePath}") Log.d(
tag,
"ebook file already exists, removing it from ${finalDestinationFile.absolutePath}"
)
finalDestinationFile.delete() finalDestinationFile.delete()
} }
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename, fileSize, destinationFile,finalDestinationFile,itemSubfolder,serverPath,localFolder,ebookFile,null,null) val downloadItemPart =
DownloadItemPart.make(
downloadItem.id,
destinationFilename,
fileSize,
destinationFile,
finalDestinationFile,
itemSubfolder,
serverPath,
localFolder,
ebookFile,
null,
null
)
downloadItem.downloadItemParts.add(downloadItemPart) downloadItem.downloadItemParts.add(downloadItemPart)
} }
@@ -173,34 +237,59 @@ class AbsDownloader : Plugin() {
tracks.forEach { audioTrack -> tracks.forEach { audioTrack ->
val fileSize = audioTrack.metadata?.size ?: 0 val fileSize = audioTrack.metadata?.size ?: 0
// TODO: Currently file ino is only stored on AudioFile. This should be updated server side to be in FileMetadata or on the AudioTrack // TODO: Currently file ino is only stored on AudioFile. This should be updated server side
// to be in FileMetadata or on the AudioTrack
val audioFileIno = audioFiles.find { it.metadata.path == audioTrack.metadata?.path }?.ino val audioFileIno = audioFiles.find { it.metadata.path == audioTrack.metadata?.path }?.ino
val serverPath = "/api/items/${libraryItem.id}/file/${audioFileIno}/download" val serverPath = "/api/items/${libraryItem.id}/file/${audioFileIno}/download"
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"
)
val finalDestinationFile = File("$itemFolderPath/$destinationFilename") val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
val destinationFile = File("$tempFolderPath/$destinationFilename") val destinationFile = File("$tempFolderPath/$destinationFilename")
if (destinationFile.exists()) { if (destinationFile.exists()) {
Log.d(tag, "TEMP Audio file already exists, removing it from ${destinationFile.absolutePath}") Log.d(
tag,
"TEMP Audio file already exists, removing it from ${destinationFile.absolutePath}"
)
destinationFile.delete() 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(downloadItem.id, destinationFilename, fileSize, destinationFile,finalDestinationFile,itemSubfolder,serverPath,localFolder,null,audioTrack,null) val downloadItemPart =
DownloadItemPart.make(
downloadItem.id,
destinationFilename,
fileSize,
destinationFile,
finalDestinationFile,
itemSubfolder,
serverPath,
localFolder,
null,
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 coverLibraryFile =
libraryItem.libraryFiles?.find { it.metadata.path == libraryItem.media.coverPath }
val coverFileSize = coverLibraryFile?.metadata?.size ?: 0 val coverFileSize = coverLibraryFile?.metadata?.size ?: 0
val serverPath = "/api/items/${libraryItem.id}/cover" val serverPath = "/api/items/${libraryItem.id}/cover"
@@ -209,16 +298,35 @@ class AbsDownloader : Plugin() {
val finalDestinationFile = File("$itemFolderPath/$destinationFilename") val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
if (destinationFile.exists()) { if (destinationFile.exists()) {
Log.d(tag, "TEMP Audio file already exists, removing it from ${destinationFile.absolutePath}") Log.d(
tag,
"TEMP Audio file already exists, removing it from ${destinationFile.absolutePath}"
)
destinationFile.delete() 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(downloadItem.id, destinationFilename, coverFileSize, destinationFile,finalDestinationFile,itemSubfolder,serverPath,localFolder,null,null,null) val downloadItemPart =
DownloadItemPart.make(
downloadItem.id,
destinationFilename,
coverFileSize,
destinationFile,
finalDestinationFile,
itemSubfolder,
serverPath,
localFolder,
null,
null,
null
)
downloadItem.downloadItemParts.add(downloadItemPart) downloadItem.downloadItemParts.add(downloadItemPart)
} }
@@ -233,26 +341,64 @@ class AbsDownloader : Plugin() {
val fileSize = audioTrack?.metadata?.size ?: 0 val fileSize = audioTrack?.metadata?.size ?: 0
Log.d(tag, "Starting podcast episode download") Log.d(tag, "Starting podcast episode download")
val itemFolderPath = if (isInternal) "$tempFolderPath" else "${localFolder.absolutePath}/$podcastTitle" val itemFolderPath =
if (isInternal) "$tempFolderPath" else "${localFolder.absolutePath}/$podcastTitle"
val downloadItemId = "${libraryItem.id}-${episode?.id}" val downloadItemId = "${libraryItem.id}-${episode?.id}"
val downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, libraryItem.userMediaProgress, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, podcastTitle, podcastTitle, libraryItem.media, mutableListOf()) val downloadItem =
DownloadItem(
downloadItemId,
libraryItem.id,
episode?.id,
libraryItem.userMediaProgress,
DeviceManager.serverConnectionConfig?.id ?: "",
DeviceManager.serverAddress,
DeviceManager.serverUserId,
libraryItem.mediaType,
itemFolderPath,
localFolder,
podcastTitle,
podcastTitle,
libraryItem.media,
mutableListOf()
)
var serverPath = "/api/items/${libraryItem.id}/file/${audioFileIno}/download" var serverPath = "/api/items/${libraryItem.id}/file/${audioFileIno}/download"
var destinationFilename = getFilenameFromRelPath(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") Log.d(
tag,
"Audio File Server Path $serverPath | AF RelPath ${audioTrack?.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName $destinationFilename"
)
var destinationFile = File("$tempFolderPath/$destinationFilename") var destinationFile = File("$tempFolderPath/$destinationFilename")
var finalDestinationFile = File("$itemFolderPath/$destinationFilename") var finalDestinationFile = File("$itemFolderPath/$destinationFilename")
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()
} }
var downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,fileSize, destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,null,audioTrack,episode) var downloadItemPart =
DownloadItemPart.make(
downloadItem.id,
destinationFilename,
fileSize,
destinationFile,
finalDestinationFile,
podcastTitle,
serverPath,
localFolder,
null,
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 coverLibraryFile =
libraryItem.libraryFiles?.find { it.metadata.path == libraryItem.media.coverPath }
val coverFileSize = coverLibraryFile?.metadata?.size ?: 0 val coverFileSize = coverLibraryFile?.metadata?.size ?: 0
serverPath = "/api/items/${libraryItem.id}/cover" serverPath = "/api/items/${libraryItem.id}/cover"
@@ -264,7 +410,20 @@ 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,coverFileSize,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,null,null,null) downloadItemPart =
DownloadItemPart.make(
downloadItem.id,
destinationFilename,
coverFileSize,
destinationFile,
finalDestinationFile,
podcastTitle,
serverPath,
localFolder,
null,
null,
null
)
downloadItem.downloadItemParts.add(downloadItemPart) downloadItem.downloadItemParts.add(downloadItemPart)
} }
} }