From 1f263b9cfc927bf1d7beff845fb606521f4eb5e7 Mon Sep 17 00:00:00 2001 From: arkon Date: Tue, 3 May 2022 22:23:28 -0400 Subject: [PATCH] Minor cleanup - Remove some unused StorIO queries - Clean up tall image splitting a bit (no need for creating an unscaled scaled bitmap copy, or tracking coordinates) - Clean up library updater a bit (still needs a lot of work though) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt # app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt # app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt # app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt # app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt --- .../data/database/queries/ChapterQueries.kt | 4 -- .../data/database/queries/HistoryQueries.kt | 10 --- .../data/database/queries/MangaQueries.kt | 10 --- .../tachiyomi/data/download/Downloader.kt | 68 ++++++++----------- .../data/library/LibraryUpdateService.kt | 54 ++++++--------- .../ui/reader/viewer/pager/PagerPageHolder.kt | 2 +- .../viewer/webtoon/WebtoonPageHolder.kt | 2 +- .../kanade/tachiyomi/util/system/ImageUtil.kt | 44 ++++++------ app/src/main/res/values/strings.xml | 1 + 9 files changed, 72 insertions(+), 123 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt index b117e9bf1..15f4351b5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt @@ -99,12 +99,8 @@ interface ChapterQueries : DbProvider { .prepare() // SY <-- - fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare() - fun insertChapters(chapters: List) = db.put().objects(chapters).prepare() - fun deleteChapter(chapter: Chapter) = db.delete().`object`(chapter).prepare() - fun deleteChapters(chapters: List) = db.delete().objects(chapters).prepare() fun updateChaptersBackup(chapters: List) = db.put() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt index a47604825..cb99d1a0b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt @@ -52,16 +52,6 @@ interface HistoryQueries : DbProvider { .withPutResolver(HistoryUpsertResolver()) .prepare() - fun deleteHistoryNoLastRead() = db.delete() - .byQuery( - DeleteQuery.builder() - .table(HistoryTable.TABLE) - .where("${HistoryTable.COL_LAST_READ} = ?") - .whereArgs(0) - .build(), - ) - .prepare() - // SY --> fun updateHistoryChapterIds(history: List) = db.put() .objects(history) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt index a8abed653..28f093af9 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt @@ -186,8 +186,6 @@ interface MangaQueries : DbProvider { fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare() - fun deleteMangas(mangas: List) = db.delete().objects(mangas).prepare() - fun deleteMangasNotInLibraryBySourceIds(sourceIds: List) = db.delete() .byQuery( DeleteQuery.builder() @@ -226,14 +224,6 @@ interface MangaQueries : DbProvider { .prepare() // SY <-- - fun deleteMangas() = db.delete() - .byQuery( - DeleteQuery.builder() - .table(MangaTable.TABLE) - .build(), - ) - .prepare() - fun getLastReadManga() = db.get() .listOfObjects(Manga::class.java) .withQuery( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 2d7d07004..953492f80 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -4,7 +4,6 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.webkit.MimeTypeMap -import androidx.core.graphics.BitmapCompat import com.hippo.unifile.UniFile import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.PublishRelay @@ -46,7 +45,6 @@ import uy.kohesive.injekt.injectLazy import java.io.BufferedOutputStream import java.io.File import java.io.FileOutputStream -import java.io.OutputStream import java.util.zip.CRC32 import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream @@ -360,7 +358,7 @@ class Downloader( // Do when page is downloaded. .doOnNext { page -> if (preferences.splitTallImages().get()) { - splitTallImage(page, download, tmpDir) + splitTallImage(page, tmpDir) } notifier.onProgressChange(download) } @@ -566,51 +564,39 @@ class Downloader( /** * Splits tall images to improve performance of reader */ - private fun splitTallImage(page: Page, download: Download, tmpDir: UniFile) { + private fun splitTallImage(page: Page, tmpDir: UniFile) { val filename = String.format("%03d", page.number) - val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") } - if (imageFile == null) { - notifier.onError("Error: imageFile was not found", download.chapter.name, download.manga.title) + val imageFile = tmpDir.listFiles()?.find { it.name!!.startsWith("$filename.") } + ?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number)) + + if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) { return } - if (!isAnimatedAndSupported(imageFile.openInputStream()) && isTallImage(imageFile.openInputStream())) { - // Getting the scaled bitmap of the source image - val bitmap = BitmapFactory.decodeFile(imageFile.filePath) - val scaledBitmap: Bitmap = - BitmapCompat.createScaledBitmap(bitmap, bitmap.width, bitmap.height, null, true) + val bitmap = BitmapFactory.decodeFile(imageFile.filePath) + val splitsCount = bitmap.height / context.resources.displayMetrics.heightPixels + 1 + val heightPerSplit = bitmap.height / splitsCount - val splitsCount: Int = bitmap.height / context.resources.displayMetrics.heightPixels + 1 - val splitHeight = bitmap.height / splitsCount - - // xCoord and yCoord are the pixel positions of the image splits - val xCoord = 0 - var yCoord = 0 - try { - for (i in 0 until splitsCount) { - val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(i + 1)}.jpg" - // Compress the bitmap and save in jpg format - val stream: OutputStream = FileOutputStream(splitPath) - stream.use { - Bitmap.createBitmap( - scaledBitmap, - xCoord, - yCoord, - bitmap.width, - splitHeight, - ).compress(Bitmap.CompressFormat.JPEG, 100, stream) - } - yCoord += splitHeight + try { + (0..splitsCount).forEach { split -> + val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(split + 1)}.jpg" + FileOutputStream(splitPath).use { stream -> + Bitmap.createBitmap( + bitmap, + 0, + split * heightPerSplit, + bitmap.width, + heightPerSplit, + ).compress(Bitmap.CompressFormat.JPEG, 100, stream) } - imageFile.delete() - } catch (e: Exception) { - // Image splits were not successfully saved so delete them and keep the original image - for (i in 0 until splitsCount) { - val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(i + 1)}.jpg" - File(splitPath).delete() - } - throw e } + imageFile.delete() + } catch (e: Exception) { + // Image splits were not successfully saved so delete them and keep the original image + (0..splitsCount) + .map { imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(it + 1)}.jpg" } + .forEach { File(it).delete() } + throw e } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index f41c56762..867c43601 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -29,6 +29,7 @@ import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.UnmeteredSource import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.model.toMangaInfo import eu.kanade.tachiyomi.source.model.toSChapter import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.online.all.MergedSource @@ -95,7 +96,7 @@ class LibraryUpdateService( private lateinit var wakeLock: PowerManager.WakeLock private lateinit var notifier: LibraryUpdateNotifier - private lateinit var ioScope: CoroutineScope + private var ioScope: CoroutineScope? = null private var mangaToUpdate: List = mutableListOf() private var updateJob: Job? = null @@ -105,9 +106,7 @@ class LibraryUpdateService( */ enum class Target { CHAPTERS, // Manga chapters - COVERS, // Manga covers - TRACKING, // Tracking metadata // SY --> @@ -194,7 +193,6 @@ class LibraryUpdateService( override fun onCreate() { super.onCreate() - ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) notifier = LibraryUpdateNotifier(this) wakeLock = acquireWakeLock(javaClass.name) @@ -207,8 +205,6 @@ class LibraryUpdateService( */ override fun onDestroy() { updateJob?.cancel() - // Despite what Android Studio - // states this can be null ioScope?.cancel() if (wakeLock.isHeld) { wakeLock.release() @@ -222,9 +218,7 @@ class LibraryUpdateService( /** * This method needs to be implemented, but it's not used/needed. */ - override fun onBind(intent: Intent): IBinder? { - return null - } + override fun onBind(intent: Intent): IBinder? = null /** * Method called when the service receives an intent. @@ -243,6 +237,7 @@ class LibraryUpdateService( // Unsubscribe from any previous subscription if needed updateJob?.cancel() + ioScope?.cancel() // Update favorite manga val categoryId = intent.getIntExtra(KEY_CATEGORY, -1) @@ -255,7 +250,8 @@ class LibraryUpdateService( logcat(LogPriority.ERROR, exception) stopSelf(startId) } - updateJob = ioScope.launch(handler) { + ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + updateJob = ioScope?.launch(handler) { when (target) { Target.CHAPTERS -> updateChapterList() Target.COVERS -> updateCovers() @@ -428,16 +424,10 @@ class LibraryUpdateService( } } catch (e: Throwable) { val errorMessage = when (e) { - is NoChaptersException -> { - getString(R.string.no_chapters_error) - } - is SourceManager.SourceNotInstalledException -> { - // failedUpdates will already have the source, don't need to copy it into the message - getString(R.string.loader_not_implemented_error) - } - else -> { - e.message - } + is NoChaptersException -> getString(R.string.no_chapters_error) + // failedUpdates will already have the source, don't need to copy it into the message + is SourceManager.SourceNotInstalledException -> getString(R.string.loader_not_implemented_error) + else -> e.message } failedUpdates.add(mangaWithNotif to errorMessage) } @@ -498,11 +488,12 @@ class LibraryUpdateService( private suspend fun updateManga(manga: Manga, loggedServices: List): Pair, List> { val source = sourceManager.getOrStub(manga.source).getMainSource() - var networkSManga: SManga? = null + var updatedManga: SManga = manga + // Update manga details metadata if (preferences.autoUpdateMetadata()) { - val updatedManga = source.getMangaDetails(manga.toMangaInfo()) - val sManga = updatedManga.toSManga() + val updatedMangaDetails = source.getMangaDetails(manga.toMangaInfo()) + val sManga = updatedMangaDetails.toSManga() // Avoid "losing" existing cover if (!sManga.thumbnail_url.isNullOrEmpty()) { manga.prepUpdateCover(coverCache, sManga, false) @@ -510,7 +501,7 @@ class LibraryUpdateService( sManga.thumbnail_url = manga.thumbnail_url } - networkSManga = sManga + updatedManga = sManga } // SY --> @@ -518,7 +509,7 @@ class LibraryUpdateService( val handler = CoroutineExceptionHandler { _, exception -> xLogE("Error adding initial track for ${manga.title}", exception) } - ioScope.launch(handler) { + ioScope?.launch(handler) { val tracks = db.getTracks(manga).executeAsBlocking() if (tracks.isEmpty() || tracks.none { it.sync_id == TrackManager.MDLIST }) { val track = trackManager.mdList.createInitialTracker(manga) @@ -532,22 +523,19 @@ class LibraryUpdateService( } // SY <-- - val chapters = source.getChapterList(manga.toMangaInfo()) + val chapters = source.getChapterList(updatedManga.toMangaInfo()) .map { it.toSChapter() } - // Get manga from database to account for if it was removed - // from library or database + // Get manga from database to account for if it was removed during the update val dbManga = db.getManga(manga.id!!).executeAsBlocking() ?: return Pair(emptyList(), emptyList()) // Copy into [dbManga] to retain favourite value - networkSManga?.let { - dbManga.copyFrom(it) - db.insertManga(dbManga).executeAsBlocking() - } + dbManga.copyFrom(updatedManga) + db.insertManga(dbManga).executeAsBlocking() // [dbmanga] was used so that manga data doesn't get overwritten - // incase manga gets new chapter + // in case manga gets new chapter return syncChaptersWithSource(db, chapters, dbManga, source) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 41964ffd8..7dfe59143 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -341,7 +341,7 @@ class PagerPageHolder( return splitInHalf(imageStream) } - val isDoublePage = ImageUtil.isDoublePage(imageStream) + val isDoublePage = ImageUtil.isWideImage(imageStream) if (!isDoublePage) { return imageStream } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index 1b9abf6c7..50e60cacf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt @@ -277,7 +277,7 @@ class WebtoonPageHolder( return imageStream } - val isDoublePage = ImageUtil.isDoublePage(imageStream) + val isDoublePage = ImageUtil.isWideImage(imageStream) if (!isDoublePage) { return imageStream } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt index 3d537c39e..ede9c45a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt @@ -102,38 +102,24 @@ object ImageUtil { } /** - * Check whether the image is a double-page spread + * Check whether the image is wide (which we consider a double-page spread). + * * @return true if the width is greater than the height */ - fun isDoublePage(imageStream: InputStream): Boolean { - imageStream.mark(imageStream.available() + 1) - - val imageBytes = imageStream.readBytes() - - val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } - BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options) - + fun isWideImage(imageStream: InputStream): Boolean { + val options = extractImageOptions(imageStream) imageStream.reset() - return options.outWidth > options.outHeight } /** - * Check whether the image is considered a tall image - * @return true if the height:width ratio is greater than the 3:! + * Check whether the image is considered a tall image. + * + * @return true if the height:width ratio is greater than 3. */ fun isTallImage(imageStream: InputStream): Boolean { - imageStream.mark(imageStream.available() + 1) - - val imageBytes = imageStream.readBytes() - // Checking the image dimensions without loading it in the memory. - val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } - BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options) - val width = options.outWidth - val height = options.outHeight - val ratio = height / width - - return ratio > 3 + val options = extractImageOptions(imageStream) + return (options.outHeight / options.outWidth) > 3 } /** @@ -414,6 +400,18 @@ object ImageUtil { private fun Int.isWhite(): Boolean = red + blue + green > 740 + /** + * Used to check an image's dimensions without loading it in the memory. + */ + private fun extractImageOptions(imageStream: InputStream): BitmapFactory.Options { + imageStream.mark(imageStream.available() + 1) + + val imageBytes = imageStream.readBytes() + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options) + return options + } + fun mergeBitmaps( imageBitmap: Bitmap, imageBitmap2: Bitmap, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ab34a214..c1ea82303 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -814,6 +814,7 @@ No network connection available Download paused Download completed + Page %d not found while splitting Common