fix: some download optimizations

This commit is contained in:
rebelonion 2024-05-27 07:08:47 -05:00
parent b30047804a
commit 5800dcf3e7
6 changed files with 107 additions and 63 deletions

View file

@ -125,7 +125,7 @@ class DownloadCompat {
Logger.log(e) Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e) Injekt.get<CrashlyticsInterface>().logException(e)
return OfflineAnimeModel( return OfflineAnimeModel(
"unknown", downloadedType.titleName,
"0", "0",
"??", "??",
"??", "??",
@ -188,7 +188,7 @@ class DownloadCompat {
Logger.log(e) Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e) Injekt.get<CrashlyticsInterface>().logException(e)
return OfflineMangaModel( return OfflineMangaModel(
"unknown", downloadedType.titleName,
"0", "0",
"??", "??",
"??", "??",

View file

@ -181,7 +181,6 @@ class AnimeDownloaderService : Service() {
} }
private fun updateNotification() { private fun updateNotification() {
// Update the notification to reflect the current state of the queue
val pendingDownloads = AnimeServiceDataSingleton.downloadQueue.size val pendingDownloads = AnimeServiceDataSingleton.downloadQueue.size
val text = if (pendingDownloads > 0) { val text = if (pendingDownloads > 0) {
"Pending downloads: $pendingDownloads" "Pending downloads: $pendingDownloads"
@ -201,7 +200,7 @@ class AnimeDownloaderService : Service() {
@androidx.annotation.OptIn(UnstableApi::class) @androidx.annotation.OptIn(UnstableApi::class)
suspend fun download(task: AnimeDownloadTask) { suspend fun download(task: AnimeDownloadTask) {
withContext(Dispatchers.Main) { withContext(Dispatchers.IO) {
try { try {
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission( ContextCompat.checkSelfPermission(
@ -214,13 +213,21 @@ class AnimeDownloaderService : Service() {
builder.setContentText("Downloading ${getTaskName(task.title, task.episode)}") builder.setContentText("Downloading ${getTaskName(task.title, task.episode)}")
if (notifi) { if (notifi) {
withContext(Dispatchers.Main) {
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
} }
}
val outputDir = getSubDirectory( val baseOutputDir = getSubDirectory(
this@AnimeDownloaderService, this@AnimeDownloaderService,
MediaType.ANIME, MediaType.ANIME,
false, false,
task.title
) ?: throw Exception("Failed to create output directory")
val outputDir = getSubDirectory(
this@AnimeDownloaderService,
MediaType.ANIME,
true,
task.title, task.title,
task.episode task.episode
) ?: throw Exception("Failed to create output directory") ) ?: throw Exception("Failed to create output directory")
@ -277,7 +284,7 @@ class AnimeDownloaderService : Service() {
currentTasks.find { it.getTaskName() == task.getTaskName() }?.sessionId = currentTasks.find { it.getTaskName() == task.getTaskName() }?.sessionId =
ffTask ffTask
saveMediaInfo(task) saveMediaInfo(task, baseOutputDir)
// periodically check if the download is complete // periodically check if the download is complete
while (ffExtension.getState(ffTask) != "COMPLETED") { while (ffExtension.getState(ffTask) != "COMPLETED") {
@ -291,7 +298,11 @@ class AnimeDownloaderService : Service() {
) )
} Download failed" } Download failed"
) )
if (notifi) {
withContext(Dispatchers.Main) {
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
}
}
toast("${getTaskName(task.title, task.episode)} Download failed") toast("${getTaskName(task.title, task.episode)} Download failed")
Logger.log("Download failed: ${ffExtension.getStackTrace(ffTask)}") Logger.log("Download failed: ${ffExtension.getStackTrace(ffTask)}")
downloadsManager.removeDownload( downloadsManager.removeDownload(
@ -324,8 +335,10 @@ class AnimeDownloaderService : Service() {
percent.coerceAtMost(99) percent.coerceAtMost(99)
) )
if (notifi) { if (notifi) {
withContext(Dispatchers.Main) {
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
} }
}
kotlinx.coroutines.delay(2000) kotlinx.coroutines.delay(2000)
} }
if (ffExtension.getState(ffTask) == "COMPLETED") { if (ffExtension.getState(ffTask) == "COMPLETED") {
@ -339,7 +352,11 @@ class AnimeDownloaderService : Service() {
) )
} Download failed" } Download failed"
) )
if (notifi) {
withContext(Dispatchers.Main) {
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
}
}
snackString("${getTaskName(task.title, task.episode)} Download failed") snackString("${getTaskName(task.title, task.episode)} Download failed")
downloadsManager.removeDownload( downloadsManager.removeDownload(
DownloadedType( DownloadedType(
@ -371,7 +388,11 @@ class AnimeDownloaderService : Service() {
) )
} Download completed" } Download completed"
) )
if (notifi) {
withContext(Dispatchers.Main) {
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
}
}
snackString("${getTaskName(task.title, task.episode)} Download completed") snackString("${getTaskName(task.title, task.episode)} Download completed")
PrefManager.getAnimeDownloadPreferences().edit().putString( PrefManager.getAnimeDownloadPreferences().edit().putString(
task.getTaskName(), 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 { 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) directory.findFile("media.json")?.forceDelete(this@AnimeDownloaderService)
val file = directory.createFile("application/json", "media.json") val file = directory.createFile("application/json", "media.json")
?: throw Exception("File not created") ?: throw Exception("File not created")

View file

@ -30,6 +30,7 @@ import ani.dantotsu.bottomBar
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadCompat
import ani.dantotsu.download.DownloadCompat.Companion.loadMediaCompat import ani.dantotsu.download.DownloadCompat.Companion.loadMediaCompat
import ani.dantotsu.download.DownloadCompat.Companion.loadOfflineAnimeModelCompat import ani.dantotsu.download.DownloadCompat.Companion.loadOfflineAnimeModelCompat
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
@ -319,17 +320,20 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
) )
val gson = GsonBuilder() val gson = GsonBuilder()
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> { .registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
SChapterImpl() // Provide an instance of SChapterImpl SChapterImpl()
}) })
.registerTypeAdapter(SAnime::class.java, InstanceCreator<SAnime> { .registerTypeAdapter(SAnime::class.java, InstanceCreator<SAnime> {
SAnimeImpl() // Provide an instance of SAnimeImpl SAnimeImpl()
}) })
.registerTypeAdapter(SEpisode::class.java, InstanceCreator<SEpisode> { .registerTypeAdapter(SEpisode::class.java, InstanceCreator<SEpisode> {
SEpisodeImpl() // Provide an instance of SEpisodeImpl SEpisodeImpl()
}) })
.create() .create()
val media = directory?.findFile("media.json") 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 = val mediaJson =
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use { media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
it?.readText() it?.readText()
@ -394,6 +398,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
bannerUri bannerUri
) )
} catch (e: Exception) { } catch (e: Exception) {
Logger.log(e)
return try { return try {
loadOfflineAnimeModelCompat(downloadedType) loadOfflineAnimeModelCompat(downloadedType)
} catch (e: Exception) { } catch (e: Exception) {
@ -401,7 +406,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
Logger.log(e) Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e) Injekt.get<CrashlyticsInterface>().logException(e)
OfflineAnimeModel( OfflineAnimeModel(
"unknown", downloadedType.titleName,
"0", "0",
"??", "??",
"??", "??",

View file

@ -134,15 +134,15 @@ class MangaDownloaderService : Service() {
mutex.withLock { mutex.withLock {
downloadJobs[task.chapter] = job downloadJobs[task.chapter] = job
} }
job.join() // Wait for the job to complete before continuing to the next task job.join()
mutex.withLock { mutex.withLock {
downloadJobs.remove(task.chapter) downloadJobs.remove(task.chapter)
} }
updateNotification() // Update the notification after each task is completed updateNotification()
} }
if (MangaServiceDataSingleton.downloadQueue.isEmpty()) { if (MangaServiceDataSingleton.downloadQueue.isEmpty()) {
withContext(Dispatchers.Main) { 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) { suspend fun download(task: DownloadTask) {
try { try {
withContext(Dispatchers.Main) { withContext(Dispatchers.IO) {
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission( ContextCompat.checkSelfPermission(
this@MangaDownloaderService, this@MangaDownloaderService,
@ -194,18 +194,27 @@ class MangaDownloaderService : Service() {
val deferredMap = mutableMapOf<Int, Deferred<Bitmap?>>() val deferredMap = mutableMapOf<Int, Deferred<Bitmap?>>()
builder.setContentText("Downloading ${task.title} - ${task.chapter}") builder.setContentText("Downloading ${task.title} - ${task.chapter}")
if (notifi) { if (notifi) {
withContext(Dispatchers.Main) {
notificationManager.notify(NOTIFICATION_ID, builder.build()) 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, this@MangaDownloaderService,
MediaType.MANGA, MediaType.MANGA,
false, false,
task.title, task.title,
task.chapter 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 var farthest = 0
for ((index, image) in task.imageData.withIndex()) { for ((index, image) in task.imageData.withIndex()) {
if (deferredMap.size >= task.simultaneousDownloads) { if (deferredMap.size >= task.simultaneousDownloads) {
@ -226,30 +235,36 @@ class MangaDownloaderService : Service() {
} }
if (bitmap != null) { if (bitmap != null) {
saveToDisk("$index.jpg", bitmap, task.title, task.chapter) saveToDisk("$index.jpg", outputDir, bitmap)
} }
farthest++ farthest++
builder.setProgress(task.imageData.size, farthest, false) builder.setProgress(task.imageData.size, farthest, false)
broadcastDownloadProgress( broadcastDownloadProgress(
task.chapter, task.chapter,
farthest * 100 / task.imageData.size farthest * 100 / task.imageData.size
) )
if (notifi) { if (notifi) {
withContext(Dispatchers.Main) {
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
} }
}
bitmap bitmap
} }
} }
// Wait for any remaining deferred to complete
deferredMap.values.awaitAll() deferredMap.values.awaitAll()
withContext(Dispatchers.Main) {
builder.setContentText("${task.title} - ${task.chapter} Download complete") builder.setContentText("${task.title} - ${task.chapter} Download complete")
.setProgress(0, 0, false) .setProgress(0, 0, false)
if (notifi) {
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
}
}
saveMediaInfo(task) saveMediaInfo(task, baseOutputDir)
downloadsManager.addDownload( downloadsManager.addDownload(
DownloadedType( DownloadedType(
task.title, 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 { 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) directory.findFile(fileName)?.forceDelete(this)
// Create a file reference within that directory for the image
val file = val file =
directory.createFile("image/jpeg", fileName) ?: throw Exception("File not created") 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 -> file.openOutputStream(this, false).use { outputStream ->
if (outputStream == null) throw Exception("Output stream is null") if (outputStream == null) throw Exception("Output stream is null")
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
@ -292,11 +306,8 @@ class MangaDownloaderService : Service() {
} }
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
private fun saveMediaInfo(task: DownloadTask) { private fun saveMediaInfo(task: DownloadTask, directory: DocumentFile) {
launchIO { launchIO {
val directory =
getSubDirectory(this@MangaDownloaderService, MediaType.MANGA, false, task.title)
?: throw Exception("Directory not found")
directory.findFile("media.json")?.forceDelete(this@MangaDownloaderService) directory.findFile("media.json")?.forceDelete(this@MangaDownloaderService)
val file = directory.createFile("application/json", "media.json") val file = directory.createFile("application/json", "media.json")
?: throw Exception("File not created") ?: throw Exception("File not created")

View file

@ -171,7 +171,11 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
val item = adapter.getItem(position) as OfflineMangaModel val item = adapter.getItem(position) as OfflineMangaModel
val media = val media =
downloadManager.mangaDownloadedTypes.firstOrNull { it.titleName.compareName(item.title) } 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 { media?.let {
lifecycleScope.launch { lifecycleScope.launch {
ContextCompat.startActivity( ContextCompat.startActivity(
@ -279,10 +283,12 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
downloads = listOf() downloads = listOf()
downloadsJob = Job() downloadsJob = Job()
CoroutineScope(Dispatchers.IO + downloadsJob).launch { 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<OfflineMangaModel>() val newMangaDownloads = mutableListOf<OfflineMangaModel>()
for (title in mangaTitles) { 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 download = tDownloads.firstOrNull() ?: continue
val offlineMangaModel = loadOfflineMangaModel(download) val offlineMangaModel = loadOfflineMangaModel(download)
newMangaDownloads += offlineMangaModel newMangaDownloads += offlineMangaModel
@ -291,7 +297,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
val novelTitles = downloadManager.novelDownloadedTypes.map { it.titleName }.distinct() val novelTitles = downloadManager.novelDownloadedTypes.map { it.titleName }.distinct()
val newNovelDownloads = mutableListOf<OfflineMangaModel>() val newNovelDownloads = mutableListOf<OfflineMangaModel>()
for (title in novelTitles) { 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 download = tDownloads.firstOrNull() ?: continue
val offlineMangaModel = loadOfflineMangaModel(download) val offlineMangaModel = loadOfflineMangaModel(download)
newNovelDownloads += offlineMangaModel newNovelDownloads += offlineMangaModel
@ -320,11 +327,14 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
) )
val gson = GsonBuilder() val gson = GsonBuilder()
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> { .registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
SChapterImpl() // Provide an instance of SChapterImpl SChapterImpl()
}) })
.create() .create()
val media = directory?.findFile("media.json") 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 = val mediaJson =
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use { media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
it?.readText() it?.readText()
@ -340,7 +350,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
private suspend fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel { private suspend fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
val type = downloadedType.type.asText() val type = downloadedType.type.asText()
//load media.json and convert to media class with gson
try { try {
val directory = getSubDirectory( val directory = getSubDirectory(
context ?: currContext()!!, downloadedType.type, context ?: currContext()!!, downloadedType.type,
@ -378,6 +387,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
bannerUri bannerUri
) )
} catch (e: Exception) { } catch (e: Exception) {
Logger.log(e)
return try { return try {
loadOfflineMangaModelCompat(downloadedType) loadOfflineMangaModelCompat(downloadedType)
} catch (e: Exception) { } catch (e: Exception) {
@ -385,7 +395,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
Logger.log(e) Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e) Injekt.get<CrashlyticsInterface>().logException(e)
return OfflineMangaModel( return OfflineMangaModel(
"unknown", downloadedType.titleName,
"0", "0",
"??", "??",
"??", "??",

View file

@ -239,6 +239,13 @@ class NovelDownloaderService : Service() {
return@withContext return@withContext
} }
val baseDirectory = getSubDirectory(
this@NovelDownloaderService,
MediaType.NOVEL,
false,
task.title
) ?: throw Exception("Directory not found")
// Start the download // Start the download
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
@ -334,7 +341,7 @@ class NovelDownloaderService : Service() {
notificationManager.notify(NOTIFICATION_ID, builder.build()) notificationManager.notify(NOTIFICATION_ID, builder.build())
} }
saveMediaInfo(task) saveMediaInfo(task, baseDirectory)
downloadsManager.addDownload( downloadsManager.addDownload(
DownloadedType( DownloadedType(
task.title, task.title,
@ -354,15 +361,8 @@ class NovelDownloaderService : Service() {
} }
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
private fun saveMediaInfo(task: DownloadTask) { private fun saveMediaInfo(task: DownloadTask, directory: DocumentFile) {
launchIO { launchIO {
val directory =
getSubDirectory(
this@NovelDownloaderService,
MediaType.NOVEL,
false,
task.title
) ?: throw Exception("Directory not found")
directory.findFile("media.json")?.forceDelete(this@NovelDownloaderService) directory.findFile("media.json")?.forceDelete(this@NovelDownloaderService)
val file = directory.createFile("application/json", "media.json") val file = directory.createFile("application/json", "media.json")
?: throw Exception("File not created") ?: throw Exception("File not created")