diff --git a/app/src/main/java/ani/dantotsu/download/DownloadCompat.kt b/app/src/main/java/ani/dantotsu/download/DownloadCompat.kt index 892c652141..bd4e5c8450 100644 --- a/app/src/main/java/ani/dantotsu/download/DownloadCompat.kt +++ b/app/src/main/java/ani/dantotsu/download/DownloadCompat.kt @@ -125,7 +125,7 @@ class DownloadCompat { Logger.log(e) Injekt.get().logException(e) return OfflineAnimeModel( - "unknown", + downloadedType.titleName, "0", "??", "??", @@ -188,7 +188,7 @@ class DownloadCompat { Logger.log(e) Injekt.get().logException(e) return OfflineMangaModel( - "unknown", + downloadedType.titleName, "0", "??", "??", diff --git a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt index 6515e46a7d..96b4e39e30 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt @@ -181,7 +181,6 @@ class AnimeDownloaderService : Service() { } private fun updateNotification() { - // Update the notification to reflect the current state of the queue val pendingDownloads = AnimeServiceDataSingleton.downloadQueue.size val text = if (pendingDownloads > 0) { "Pending downloads: $pendingDownloads" @@ -201,7 +200,7 @@ class AnimeDownloaderService : Service() { @androidx.annotation.OptIn(UnstableApi::class) suspend fun download(task: AnimeDownloadTask) { - withContext(Dispatchers.Main) { + withContext(Dispatchers.IO) { try { val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ContextCompat.checkSelfPermission( @@ -214,13 +213,21 @@ class AnimeDownloaderService : Service() { builder.setContentText("Downloading ${getTaskName(task.title, task.episode)}") if (notifi) { - notificationManager.notify(NOTIFICATION_ID, builder.build()) + withContext(Dispatchers.Main) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } } - val outputDir = getSubDirectory( + val baseOutputDir = getSubDirectory( this@AnimeDownloaderService, MediaType.ANIME, false, + task.title + ) ?: throw Exception("Failed to create output directory") + val outputDir = getSubDirectory( + this@AnimeDownloaderService, + MediaType.ANIME, + true, task.title, task.episode ) ?: throw Exception("Failed to create output directory") @@ -277,7 +284,7 @@ class AnimeDownloaderService : Service() { currentTasks.find { it.getTaskName() == task.getTaskName() }?.sessionId = ffTask - saveMediaInfo(task) + saveMediaInfo(task, baseOutputDir) // periodically check if the download is complete while (ffExtension.getState(ffTask) != "COMPLETED") { @@ -291,7 +298,11 @@ class AnimeDownloaderService : Service() { ) } Download failed" ) - notificationManager.notify(NOTIFICATION_ID, builder.build()) + if (notifi) { + withContext(Dispatchers.Main) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } + } toast("${getTaskName(task.title, task.episode)} Download failed") Logger.log("Download failed: ${ffExtension.getStackTrace(ffTask)}") downloadsManager.removeDownload( @@ -324,7 +335,9 @@ class AnimeDownloaderService : Service() { percent.coerceAtMost(99) ) if (notifi) { - notificationManager.notify(NOTIFICATION_ID, builder.build()) + withContext(Dispatchers.Main) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } } kotlinx.coroutines.delay(2000) } @@ -339,7 +352,11 @@ class AnimeDownloaderService : Service() { ) } Download failed" ) - notificationManager.notify(NOTIFICATION_ID, builder.build()) + if (notifi) { + withContext(Dispatchers.Main) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } + } snackString("${getTaskName(task.title, task.episode)} Download failed") downloadsManager.removeDownload( DownloadedType( @@ -371,7 +388,11 @@ class AnimeDownloaderService : Service() { ) } Download completed" ) - notificationManager.notify(NOTIFICATION_ID, builder.build()) + if (notifi) { + withContext(Dispatchers.Main) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } + } snackString("${getTaskName(task.title, task.episode)} Download completed") PrefManager.getAnimeDownloadPreferences().edit().putString( task.getTaskName(), @@ -401,11 +422,8 @@ class AnimeDownloaderService : Service() { } } - private fun saveMediaInfo(task: AnimeDownloadTask) { + private fun saveMediaInfo(task: AnimeDownloadTask, directory: DocumentFile) { CoroutineScope(Dispatchers.IO).launch { - val directory = - getSubDirectory(this@AnimeDownloaderService, MediaType.ANIME, false, task.title) - ?: throw Exception("Directory not found") directory.findFile("media.json")?.forceDelete(this@AnimeDownloaderService) val file = directory.createFile("application/json", "media.json") ?: throw Exception("File not created") diff --git a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt index cdf7e99327..3bcdffe1ee 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt @@ -30,6 +30,7 @@ import ani.dantotsu.bottomBar import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.currActivity import ani.dantotsu.currContext +import ani.dantotsu.download.DownloadCompat import ani.dantotsu.download.DownloadCompat.Companion.loadMediaCompat import ani.dantotsu.download.DownloadCompat.Companion.loadOfflineAnimeModelCompat import ani.dantotsu.download.DownloadedType @@ -319,17 +320,20 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { ) val gson = GsonBuilder() .registerTypeAdapter(SChapter::class.java, InstanceCreator { - SChapterImpl() // Provide an instance of SChapterImpl + SChapterImpl() }) .registerTypeAdapter(SAnime::class.java, InstanceCreator { - SAnimeImpl() // Provide an instance of SAnimeImpl + SAnimeImpl() }) .registerTypeAdapter(SEpisode::class.java, InstanceCreator { - SEpisodeImpl() // Provide an instance of SEpisodeImpl + SEpisodeImpl() }) .create() val media = directory?.findFile("media.json") - ?: return loadMediaCompat(downloadedType) + if (media == null) { + Logger.log("No media.json found at ${directory?.uri?.path}") + return loadMediaCompat(downloadedType) + } val mediaJson = media.openInputStream(context ?: currContext()!!)?.bufferedReader().use { it?.readText() @@ -394,6 +398,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { bannerUri ) } catch (e: Exception) { + Logger.log(e) return try { loadOfflineAnimeModelCompat(downloadedType) } catch (e: Exception) { @@ -401,7 +406,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { Logger.log(e) Injekt.get().logException(e) OfflineAnimeModel( - "unknown", + downloadedType.titleName, "0", "??", "??", diff --git a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt index 08ddb3a79b..4452a2df09 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt @@ -134,15 +134,15 @@ class MangaDownloaderService : Service() { mutex.withLock { downloadJobs[task.chapter] = job } - job.join() // Wait for the job to complete before continuing to the next task + job.join() mutex.withLock { downloadJobs.remove(task.chapter) } - updateNotification() // Update the notification after each task is completed + updateNotification() } if (MangaServiceDataSingleton.downloadQueue.isEmpty()) { withContext(Dispatchers.Main) { - stopSelf() // Stop the service when the queue is empty + stopSelf() } } } @@ -181,7 +181,7 @@ class MangaDownloaderService : Service() { suspend fun download(task: DownloadTask) { try { - withContext(Dispatchers.Main) { + withContext(Dispatchers.IO) { val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ContextCompat.checkSelfPermission( this@MangaDownloaderService, @@ -194,18 +194,27 @@ class MangaDownloaderService : Service() { val deferredMap = mutableMapOf>() builder.setContentText("Downloading ${task.title} - ${task.chapter}") if (notifi) { - notificationManager.notify(NOTIFICATION_ID, builder.build()) + withContext(Dispatchers.Main) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } } - getSubDirectory( + val baseOutputDir = getSubDirectory( + this@MangaDownloaderService, + MediaType.MANGA, + false, + task.title + ) ?: throw Exception("Base output directory not found") + val outputDir = getSubDirectory( this@MangaDownloaderService, MediaType.MANGA, false, task.title, task.chapter - )?.deleteRecursively(this@MangaDownloaderService) + ) ?: throw Exception("Output directory not found") + + outputDir.deleteRecursively(this@MangaDownloaderService, true) - // Loop through each ImageData object from the task var farthest = 0 for ((index, image) in task.imageData.withIndex()) { if (deferredMap.size >= task.simultaneousDownloads) { @@ -226,30 +235,36 @@ class MangaDownloaderService : Service() { } if (bitmap != null) { - saveToDisk("$index.jpg", bitmap, task.title, task.chapter) + saveToDisk("$index.jpg", outputDir, bitmap) } farthest++ + builder.setProgress(task.imageData.size, farthest, false) + broadcastDownloadProgress( task.chapter, farthest * 100 / task.imageData.size ) if (notifi) { - notificationManager.notify(NOTIFICATION_ID, builder.build()) + withContext(Dispatchers.Main) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } } - bitmap } } - // Wait for any remaining deferred to complete deferredMap.values.awaitAll() - builder.setContentText("${task.title} - ${task.chapter} Download complete") - .setProgress(0, 0, false) - notificationManager.notify(NOTIFICATION_ID, builder.build()) + withContext(Dispatchers.Main) { + builder.setContentText("${task.title} - ${task.chapter} Download complete") + .setProgress(0, 0, false) + if (notifi) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } + } - saveMediaInfo(task) + saveMediaInfo(task, baseOutputDir) downloadsManager.addDownload( DownloadedType( task.title, @@ -269,17 +284,16 @@ class MangaDownloaderService : Service() { } - private fun saveToDisk(fileName: String, bitmap: Bitmap, title: String, chapter: String) { + private fun saveToDisk( + fileName: String, + directory: DocumentFile, + bitmap: Bitmap + ) { try { - // Define the directory within the private external storage space - val directory = getSubDirectory(this, MediaType.MANGA, false, title, chapter) - ?: throw Exception("Directory not found") directory.findFile(fileName)?.forceDelete(this) - // Create a file reference within that directory for the image val file = directory.createFile("image/jpeg", fileName) ?: throw Exception("File not created") - // Use a FileOutputStream to write the bitmap to the file file.openOutputStream(this, false).use { outputStream -> if (outputStream == null) throw Exception("Output stream is null") bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) @@ -292,11 +306,8 @@ class MangaDownloaderService : Service() { } @OptIn(DelicateCoroutinesApi::class) - private fun saveMediaInfo(task: DownloadTask) { + private fun saveMediaInfo(task: DownloadTask, directory: DocumentFile) { launchIO { - val directory = - getSubDirectory(this@MangaDownloaderService, MediaType.MANGA, false, task.title) - ?: throw Exception("Directory not found") directory.findFile("media.json")?.forceDelete(this@MangaDownloaderService) val file = directory.createFile("application/json", "media.json") ?: throw Exception("File not created") diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt index 36e90e60f4..6d88918e6c 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt @@ -171,7 +171,11 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { val item = adapter.getItem(position) as OfflineMangaModel val media = downloadManager.mangaDownloadedTypes.firstOrNull { it.titleName.compareName(item.title) } - ?: downloadManager.novelDownloadedTypes.firstOrNull { it.titleName.compareName(item.title) } + ?: downloadManager.novelDownloadedTypes.firstOrNull { + it.titleName.compareName( + item.title + ) + } media?.let { lifecycleScope.launch { ContextCompat.startActivity( @@ -279,10 +283,12 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { downloads = listOf() downloadsJob = Job() CoroutineScope(Dispatchers.IO + downloadsJob).launch { - val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.titleName.findValidName() }.distinct() + val mangaTitles = + downloadManager.mangaDownloadedTypes.map { it.titleName.findValidName() }.distinct() val newMangaDownloads = mutableListOf() for (title in mangaTitles) { - val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.titleName.findValidName() == title } + val tDownloads = + downloadManager.mangaDownloadedTypes.filter { it.titleName.findValidName() == title } val download = tDownloads.firstOrNull() ?: continue val offlineMangaModel = loadOfflineMangaModel(download) newMangaDownloads += offlineMangaModel @@ -291,7 +297,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { val novelTitles = downloadManager.novelDownloadedTypes.map { it.titleName }.distinct() val newNovelDownloads = mutableListOf() for (title in novelTitles) { - val tDownloads = downloadManager.novelDownloadedTypes.filter { it.titleName.findValidName() == title } + val tDownloads = + downloadManager.novelDownloadedTypes.filter { it.titleName.findValidName() == title } val download = tDownloads.firstOrNull() ?: continue val offlineMangaModel = loadOfflineMangaModel(download) newNovelDownloads += offlineMangaModel @@ -320,11 +327,14 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { ) val gson = GsonBuilder() .registerTypeAdapter(SChapter::class.java, InstanceCreator { - SChapterImpl() // Provide an instance of SChapterImpl + SChapterImpl() }) .create() val media = directory?.findFile("media.json") - ?: return DownloadCompat.loadMediaCompat(downloadedType) + if (media == null) { + Logger.log("No media.json found at ${directory?.uri?.path}") + return DownloadCompat.loadMediaCompat(downloadedType) + } val mediaJson = media.openInputStream(context ?: currContext()!!)?.bufferedReader().use { it?.readText() @@ -340,7 +350,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { private suspend fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel { val type = downloadedType.type.asText() - //load media.json and convert to media class with gson try { val directory = getSubDirectory( context ?: currContext()!!, downloadedType.type, @@ -378,6 +387,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { bannerUri ) } catch (e: Exception) { + Logger.log(e) return try { loadOfflineMangaModelCompat(downloadedType) } catch (e: Exception) { @@ -385,7 +395,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { Logger.log(e) Injekt.get().logException(e) return OfflineMangaModel( - "unknown", + downloadedType.titleName, "0", "??", "??", diff --git a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt index bc1d8e3114..38f7e23103 100644 --- a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt @@ -239,6 +239,13 @@ class NovelDownloaderService : Service() { return@withContext } + val baseDirectory = getSubDirectory( + this@NovelDownloaderService, + MediaType.NOVEL, + false, + task.title + ) ?: throw Exception("Directory not found") + // Start the download withContext(Dispatchers.IO) { try { @@ -334,7 +341,7 @@ class NovelDownloaderService : Service() { notificationManager.notify(NOTIFICATION_ID, builder.build()) } - saveMediaInfo(task) + saveMediaInfo(task, baseDirectory) downloadsManager.addDownload( DownloadedType( task.title, @@ -354,15 +361,8 @@ class NovelDownloaderService : Service() { } @OptIn(DelicateCoroutinesApi::class) - private fun saveMediaInfo(task: DownloadTask) { + private fun saveMediaInfo(task: DownloadTask, directory: DocumentFile) { launchIO { - val directory = - getSubDirectory( - this@NovelDownloaderService, - MediaType.NOVEL, - false, - task.title - ) ?: throw Exception("Directory not found") directory.findFile("media.json")?.forceDelete(this@NovelDownloaderService) val file = directory.createFile("application/json", "media.json") ?: throw Exception("File not created")