Fix pages saving #151

This commit is contained in:
Koitharu
2022-04-29 11:41:14 +03:00
parent 684b494edb
commit 4987d43042
7 changed files with 93 additions and 46 deletions

View File

@@ -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()) }

View File

@@ -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(),
)
}
}

View File

@@ -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<Float>): 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") {

View File

@@ -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<Uri>? = null
private val contentResolver = context.contentResolver
suspend fun savePage(
pageLoader: PageLoader,
page: MangaPage,
saveLauncher: ActivityResultLauncher<String>,
): 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<Uri> { 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
}

View File

@@ -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) {

View File

@@ -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<MangaChapter>()
@@ -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<String>,
) {
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 {

View File

@@ -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")
}
}
}