Refactor pages saving

This commit is contained in:
Koitharu
2024-11-05 11:46:59 +02:00
parent 02073f6d45
commit 57d1f54318
5 changed files with 48 additions and 65 deletions

View File

@@ -72,7 +72,7 @@ android {
}
lint {
abortOnError true
disable 'MissingTranslation', 'PrivateResource', 'SetJavaScriptEnabled'
disable 'MissingTranslation', 'PrivateResource', 'SetJavaScriptEnabled', 'SimpleDateFormat'
}
testOptions {
unitTests.includeAndroidResources true

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.core.backup
import android.annotation.SuppressLint
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
@@ -34,7 +33,6 @@ class BackupZipOutput(val file: File) : Closeable {
companion object {
const val DIR_BACKUPS = "backups"
@SuppressLint("SimpleDateFormat")
private val dateTimeFormat = SimpleDateFormat("yyyyMMdd-HHmm")
fun generateFileName(context: Context) = buildString {

View File

@@ -36,10 +36,11 @@ import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
import org.koitharu.kotatsu.reader.domain.PageLoader
import java.io.File
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.text.SimpleDateFormat
import java.util.Date
import javax.inject.Provider
import kotlin.coroutines.resume
@@ -66,20 +67,20 @@ class PageSaveHelper @AssistedInject constructor(
}
}
suspend fun save(manga: Manga, chapter: MangaChapter, pageNumber: Int, pages: Set<MangaPage>): Uri? = when (pages.size) {
suspend fun save(tasks: Set<Task>): Uri? = when (tasks.size) {
0 -> null
1 -> saveImpl(manga, chapter, pageNumber, pages.first())
1 -> saveImpl(tasks.first())
else -> {
saveImpl(manga, chapter, pageNumber, pages)
saveImpl(tasks)
null
}
}
private suspend fun saveImpl(manga: Manga, chapter: MangaChapter, pageNumber: Int, page: MangaPage): Uri {
private suspend fun saveImpl(task: Task): Uri {
val pageLoader = pageLoaderProvider.get()
val pageUrl = pageLoader.getPageUrl(page).toUri()
val pageUri = pageLoader.loadPage(page, force = false)
val proposedName = getProposedFileName(manga, chapter, pageNumber, pageUrl, pageUri)
val pageUrl = pageLoader.getPageUrl(task.page).toUri()
val pageUri = pageLoader.loadPage(task.page, force = false)
val proposedName = task.getFileBaseName() + "." + getPageExtension(pageUrl, pageUri)
val destination = getDefaultFileUri(proposedName)?.uri ?: run {
val defaultUri = settings.getPagesSaveDir(context)?.uri?.buildUpon()?.appendPath(proposedName)?.toString()
savePageRequest.launchAndAwait(defaultUri ?: proposedName)
@@ -88,49 +89,28 @@ class PageSaveHelper @AssistedInject constructor(
return destination
}
private suspend fun saveImpl(manga: Manga, chapter: MangaChapter, pageNumber: Int, pages: Collection<MangaPage>) {
private suspend fun saveImpl(tasks: Collection<Task>) {
val pageLoader = pageLoaderProvider.get()
val destinationDir = getDefaultFileUri(null) ?: run {
val defaultUri = settings.getPagesSaveDir(context)?.uri
DocumentFile.fromTreeUri(context, pickDirectoryRequest.launchAndAwait(defaultUri))
} ?: throw IOException("Cannot get destination directory")
var count = 0
for (page in pages) {
val pageUrl = pageLoader.getPageUrl(page).toUri()
val pageUri = pageLoader.loadPage(page, force = false)
val proposedName = getProposedFileName(manga, chapter, pageNumber.plus(count), pageUrl, pageUri)
val ext = proposedName.substringAfterLast('.', "")
for (task in tasks) {
val pageUrl = pageLoader.getPageUrl(task.page).toUri()
val pageUri = pageLoader.loadPage(task.page, force = false)
val proposedName = task.getFileBaseName()
val ext = getPageExtension(pageUrl, pageUri)
val mime = requireNotNull(MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)) {
"Unknown type of $proposedName"
}
val destination = destinationDir.createFile(mime, proposedName.substringBeforeLast('.'))
copyImpl(pageUri, destination?.uri ?: throw IOException("Cannot create destination file"))
count++
}
}
private suspend fun getProposedFileName(manga: Manga, chapter: MangaChapter, pageNumber: Number, pageUrl: Uri, pageUri: Uri): String {
var mangaInfos = getNameFromMangaChapterPage(manga, chapter, pageNumber)
var currentTime = getCurrentTime()
var extension = getPageExtension(pageUrl, pageUri)
return mangaInfos + "_" + currentTime + extension
}
private fun getNameFromMangaChapterPage(manga: Manga, chapter: MangaChapter, pageNumber: Number): String {
return manga.title + "-" + chapter.number + "-" + pageNumber
}
private fun getCurrentTime(): String {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmm")
val current = LocalDateTime.now().format(formatter)
return current
}
private suspend fun getPageExtension(url: Uri, fileUri: Uri): String {
var name = requireNotNull(
val name = requireNotNull(
if (url.isZipUri()) {
url.fragment?.substringAfterLast(File.separatorChar)
} else {
@@ -146,7 +126,7 @@ class PageSaveHelper @AssistedInject constructor(
EXTENSION_FALLBACK
}
}
return ".$extension"
return extension
}
private suspend fun <I> ActivityResultLauncher<I>.launchAndAwait(input: I): Uri {
@@ -203,6 +183,24 @@ class PageSaveHelper @AssistedInject constructor(
options.outMimeType
}
data class Task(
val manga: Manga,
val chapter: MangaChapter,
val pageNumber: Int,
val page: MangaPage,
) {
fun getFileBaseName() = buildString {
append(manga.title.toFileNameSafe().take(MAX_BASENAME_LENGTH))
append('-')
append(chapter.number)
append('-')
append(pageNumber)
append('_')
append(SimpleDateFormat("yyyy-MM-dd_HHmm").format(Date()))
}
}
@AssistedFactory
interface Factory {
@@ -211,7 +209,7 @@ class PageSaveHelper @AssistedInject constructor(
private companion object {
private const val MAX_FILENAME_LENGTH = 16
private const val MAX_BASENAME_LENGTH = 12
private const val EXTENSION_FALLBACK = "png"
}
}

View File

@@ -57,7 +57,6 @@ import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.reader.domain.ChaptersLoader
import org.koitharu.kotatsu.reader.domain.DetectReaderModeUseCase
@@ -260,25 +259,17 @@ class ReaderViewModel @Inject constructor(
prevJob?.cancelAndJoin()
val state = checkNotNull(getCurrentState())
val currentManga = manga.requireValue()
val currentChapter = checkNotNull(currentManga.findChapter(state.chapterId))
val currentPageNumber = state.page
val currentPage = checkNotNull(getCurrentPage()) { "Cannot find current page" }
val dest = pageSaveHelper.save(currentManga, currentChapter, currentPageNumber, setOf(currentPage))
val task = PageSaveHelper.Task(
manga = currentManga,
chapter = checkNotNull(currentManga.findChapter(state.chapterId)),
pageNumber = state.page,
page = checkNotNull(getCurrentPage()) { "Cannot find current page" },
)
val dest = pageSaveHelper.save(setOf(task))
onPageSaved.call(dest)
}
}
fun getCurrentManga(): Manga? {
return manga.value
}
fun getCurrentChapter(): MangaChapter? {
val state = readingState.value?: return null
return manga.value?.chapters?.find {
it.id == state.chapterId
}
}
fun getCurrentPage(): MangaPage? {
val state = readingState.value ?: return null
return content.value.pages.find {
@@ -286,11 +277,6 @@ class ReaderViewModel @Inject constructor(
}?.toMangaPage()
}
fun getPageNumber(): Int? {
val state = readingState.value?: return null
return state.page
}
fun switchChapter(id: Long, page: Int) {
val prevJob = loadingJob
loadingJob = launchLoadingJob(Dispatchers.Default) {

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@color/launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>