From 4987d43042c4f4c29b4a146bb882a9886e7a4471 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 29 Apr 2022 11:41:14 +0300 Subject: [PATCH] Fix pages saving #151 --- .../org/koitharu/kotatsu/local/LocalModule.kt | 3 - .../koitharu/kotatsu/reader/ReaderModule.kt | 6 +- .../kotatsu/reader/domain/PageLoader.kt | 6 +- .../kotatsu/reader/ui/PageSaveHelper.kt | 60 +++++++++++++++++++ .../kotatsu/reader/ui/ReaderActivity.kt | 10 +--- .../kotatsu/reader/ui/ReaderViewModel.kt | 28 ++++++--- .../kotatsu/utils/ExternalStorageHelper.kt | 26 -------- 7 files changed, 93 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/utils/ExternalStorageHelper.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/local/LocalModule.kt b/app/src/main/java/org/koitharu/kotatsu/local/LocalModule.kt index 928fe706b..3366248a4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/LocalModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/LocalModule.kt @@ -7,7 +7,6 @@ import org.koitharu.kotatsu.download.domain.DownloadManager import org.koitharu.kotatsu.local.data.LocalStorageManager import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.ui.LocalListViewModel -import org.koitharu.kotatsu.utils.ExternalStorageHelper val localModule get() = module { @@ -15,8 +14,6 @@ val localModule single { LocalStorageManager(androidContext(), get()) } single { LocalMangaRepository(get()) } - factory { ExternalStorageHelper(androidContext()) } - factory { DownloadManager.Factory(androidContext(), get(), get(), get(), get(), get()) } viewModel { LocalListViewModel(get(), get(), get(), get()) } diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ReaderModule.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ReaderModule.kt index 0d0aec467..f27f061c6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ReaderModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ReaderModule.kt @@ -1,9 +1,11 @@ package org.koitharu.kotatsu.reader +import org.koin.android.ext.koin.androidContext import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module import org.koitharu.kotatsu.base.domain.MangaDataRepository import org.koitharu.kotatsu.local.data.PagesCache +import org.koitharu.kotatsu.reader.ui.PageSaveHelper import org.koitharu.kotatsu.reader.ui.ReaderViewModel val readerModule @@ -12,6 +14,8 @@ val readerModule single { MangaDataRepository(get()) } single { PagesCache(get()) } + factory { PageSaveHelper(get(), androidContext()) } + viewModel { params -> ReaderViewModel( intent = params[0], @@ -21,7 +25,7 @@ val readerModule historyRepository = get(), shortcutsRepository = get(), settings = get(), - externalStorageHelper = get(), + pageSaveHelper = get(), ) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt b/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt index 523a18881..696f48cc3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt @@ -113,6 +113,10 @@ class PageLoader : KoinComponent, Closeable { } } + suspend fun getPageUrl(page: MangaPage): String { + return getRepository(page.source).getPageUrl(page) + } + private fun onIdle() { synchronized(prefetchQueue) { while (prefetchQueue.isNotEmpty()) { @@ -151,7 +155,7 @@ class PageLoader : KoinComponent, Closeable { } private suspend fun loadPageImpl(page: MangaPage, progress: MutableStateFlow): File { - val pageUrl = getRepository(page.source).getPageUrl(page) + val pageUrl = getPageUrl(page) check(pageUrl.isNotBlank()) { "Cannot obtain full image url" } val uri = Uri.parse(pageUrl) return if (uri.scheme == "cbz") { diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt new file mode 100644 index 000000000..a2d8f7ac5 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt @@ -0,0 +1,60 @@ +package org.koitharu.kotatsu.reader.ui + +import android.content.Context +import android.net.Uri +import androidx.activity.result.ActivityResultLauncher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.suspendCancellableCoroutine +import okhttp3.HttpUrl.Companion.toHttpUrl +import okio.IOException +import org.koitharu.kotatsu.local.data.PagesCache +import org.koitharu.kotatsu.parsers.model.MangaPage +import org.koitharu.kotatsu.reader.domain.PageLoader +import kotlin.coroutines.Continuation +import kotlin.coroutines.coroutineContext +import kotlin.coroutines.resume + +class PageSaveHelper( + private val cache: PagesCache, + context: Context, +) { + + private var continuation: Continuation? = null + private val contentResolver = context.contentResolver + + suspend fun savePage( + pageLoader: PageLoader, + page: MangaPage, + saveLauncher: ActivityResultLauncher, + ): Uri { + var pageFile = cache[page.url] + var fileName = pageFile?.name + if (fileName == null) { + fileName = pageLoader.getPageUrl(page).toHttpUrl().pathSegments.last() + } + val cc = coroutineContext + val destination = suspendCancellableCoroutine { cont -> + continuation = cont + Dispatchers.Main.dispatch(cc) { + saveLauncher.launch(fileName) + } + } + continuation = null + if (pageFile == null) { + pageFile = pageLoader.loadPage(page, force = false) + } + runInterruptible(Dispatchers.IO) { + contentResolver.openOutputStream(destination)?.use { output -> + pageFile.inputStream().use { input -> + input.copyTo(output) + } + } ?: throw IOException("Output stream is null") + } + return destination + } + + fun onActivityResult(uri: Uri): Boolean = continuation?.apply { + resume(uri) + } != null +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index 914b96ae3..ca9228207 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -9,7 +9,6 @@ import android.view.* import android.widget.Toast import androidx.activity.result.ActivityResultCallback import androidx.core.graphics.Insets -import androidx.core.net.toUri import androidx.core.view.* import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope @@ -187,10 +186,7 @@ class ReaderActivity : R.id.action_save_page -> { viewModel.getCurrentPage()?.also { page -> viewModel.saveCurrentState(reader?.getCurrentState()) - val name = page.url.toUri().run { - fragment ?: lastPathSegment ?: "" - } - savePageRequest.launch(name) + viewModel.saveCurrentPage(page, savePageRequest) } ?: showWaitWhileLoading() } else -> return super.onOptionsItemSelected(item) @@ -199,9 +195,7 @@ class ReaderActivity : } override fun onActivityResult(uri: Uri?) { - if (uri != null) { - viewModel.saveCurrentPage(uri) - } + viewModel.onActivityResult(uri) } private fun onLoadingStateChanged(isLoading: Boolean) { diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 688cca84f..5a746c1a3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.reader.ui import android.net.Uri import android.util.LongSparseArray +import androidx.activity.result.ActivityResultLauncher import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.* @@ -26,7 +27,6 @@ import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState -import org.koitharu.kotatsu.utils.ExternalStorageHelper import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.IgnoreErrors import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct @@ -40,10 +40,11 @@ class ReaderViewModel( private val historyRepository: HistoryRepository, private val shortcutsRepository: ShortcutsRepository, private val settings: AppSettings, - private val externalStorageHelper: ExternalStorageHelper, + private val pageSaveHelper: PageSaveHelper, ) : BaseViewModel() { private var loadingJob: Job? = null + private var pageSaveJob: Job? = null private val currentState = MutableStateFlow(initialState) private val mangaData = MutableStateFlow(intent.manga) private val chapters = LongSparseArray() @@ -137,12 +138,16 @@ class ReaderViewModel( return pages.filter { it.chapterId == chapterId }.map { it.toMangaPage() } } - fun saveCurrentPage(destination: Uri) { - launchJob(Dispatchers.Default) { + fun saveCurrentPage( + page: MangaPage, + saveLauncher: ActivityResultLauncher, + ) { + val prevJob = pageSaveJob + pageSaveJob = launchLoadingJob(Dispatchers.Default) { + prevJob?.cancelAndJoin() try { - val page = getCurrentPage() ?: error("Page not found") - externalStorageHelper.savePage(page, destination) - onPageSaved.postCall(destination) + val dest = pageSaveHelper.savePage(pageLoader, page, saveLauncher) + onPageSaved.postCall(dest) } catch (e: CancellationException) { throw e } catch (e: Exception) { @@ -154,6 +159,15 @@ class ReaderViewModel( } } + fun onActivityResult(uri: Uri?) { + if (uri != null) { + pageSaveHelper.onActivityResult(uri) + } else { + pageSaveJob?.cancel() + pageSaveJob = null + } + } + fun getCurrentPage(): MangaPage? { val state = currentState.value ?: return null return content.value?.pages?.find { diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ExternalStorageHelper.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ExternalStorageHelper.kt deleted file mode 100644 index b769645b8..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ExternalStorageHelper.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.koitharu.kotatsu.utils - -import android.content.Context -import android.net.Uri -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runInterruptible -import okio.IOException -import org.koitharu.kotatsu.parsers.model.MangaPage -import org.koitharu.kotatsu.reader.domain.PageLoader - -class ExternalStorageHelper(context: Context) { - - private val contentResolver = context.contentResolver - - suspend fun savePage(page: MangaPage, destination: Uri) { - val pageLoader = PageLoader() - val pageFile = pageLoader.loadPage(page, force = false) - runInterruptible(Dispatchers.IO) { - contentResolver.openOutputStream(destination)?.use { output -> - pageFile.inputStream().use { input -> - input.copyTo(output) - } - } ?: throw IOException("Output stream is null") - } - } -} \ No newline at end of file