Fix pages saving #151
This commit is contained in:
@@ -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()) }
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user