Fix local manga operations
This commit is contained in:
@@ -15,8 +15,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 530
|
versionCode 531
|
||||||
versionName '5.0-a1'
|
versionName '5.0-a2'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -87,26 +87,26 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.core:core-ktx:1.9.0'
|
implementation 'androidx.core:core-ktx:1.9.0'
|
||||||
implementation 'androidx.activity:activity-ktx:1.6.1'
|
implementation 'androidx.activity:activity-ktx:1.7.0'
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.5.5'
|
implementation 'androidx.fragment:fragment-ktx:1.5.6'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.0'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-service:2.6.0'
|
implementation 'androidx.lifecycle:lifecycle-service:2.6.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-process:2.6.0'
|
implementation 'androidx.lifecycle:lifecycle-process:2.6.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||||
implementation 'androidx.work:work-runtime-ktx:2.8.0'
|
implementation 'androidx.work:work-runtime-ktx:2.8.1'
|
||||||
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
|
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
|
||||||
implementation 'com.google.android.material:material:1.8.0'
|
implementation 'com.google.android.material:material:1.8.0'
|
||||||
//noinspection LifecycleAnnotationProcessorWithJava8
|
//noinspection LifecycleAnnotationProcessorWithJava8
|
||||||
kapt 'androidx.lifecycle:lifecycle-compiler:2.6.0'
|
kapt 'androidx.lifecycle:lifecycle-compiler:2.6.1'
|
||||||
|
|
||||||
implementation 'androidx.room:room-runtime:2.5.0'
|
implementation 'androidx.room:room-runtime:2.5.1'
|
||||||
implementation 'androidx.room:room-ktx:2.5.0'
|
implementation 'androidx.room:room-ktx:2.5.1'
|
||||||
kapt 'androidx.room:room-compiler:2.5.0'
|
kapt 'androidx.room:room-compiler:2.5.1'
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
|
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
|
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
|
||||||
@@ -120,8 +120,8 @@ dependencies {
|
|||||||
implementation 'androidx.hilt:hilt-work:1.0.0'
|
implementation 'androidx.hilt:hilt-work:1.0.0'
|
||||||
kapt 'androidx.hilt:hilt-compiler:1.0.0'
|
kapt 'androidx.hilt:hilt-compiler:1.0.0'
|
||||||
|
|
||||||
implementation 'io.coil-kt:coil-base:2.2.2'
|
implementation 'io.coil-kt:coil-base:2.3.0'
|
||||||
implementation 'io.coil-kt:coil-svg:2.2.2'
|
implementation 'io.coil-kt:coil-svg:2.3.0'
|
||||||
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:1b19231b2f'
|
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:1b19231b2f'
|
||||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||||
implementation 'io.noties.markwon:core:4.6.2'
|
implementation 'io.noties.markwon:core:4.6.2'
|
||||||
@@ -142,7 +142,7 @@ dependencies {
|
|||||||
|
|
||||||
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
|
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.room:room-testing:2.5.0'
|
androidTestImplementation 'androidx.room:room-testing:2.5.1'
|
||||||
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
|
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
|
||||||
|
|
||||||
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.45'
|
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.45'
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ class ExceptionResolver private constructor(
|
|||||||
sourceAuthContract = fragment.registerForActivityResult(SourceAuthActivity.Contract(), this)
|
sourceAuthContract = fragment.registerForActivityResult(SourceAuthActivity.Contract(), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(result: TaggedActivityResult?) {
|
override fun onActivityResult(result: TaggedActivityResult) {
|
||||||
result ?: return
|
|
||||||
continuations.remove(result.tag)?.resume(result.isSuccess)
|
continuations.remove(result.tag)?.resume(result.isSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ class LocalMangaDirOutput(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
runInterruptible(Dispatchers.IO) {
|
runInterruptible(Dispatchers.IO) {
|
||||||
file.copyTo(File(rootFile, name))
|
file.copyTo(File(rootFile, name), overwrite = true)
|
||||||
}
|
}
|
||||||
index.setCoverEntry(name)
|
index.setCoverEntry(name)
|
||||||
|
flushIndex()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) {
|
override suspend fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) {
|
||||||
@@ -59,12 +60,11 @@ class LocalMangaDirOutput(
|
|||||||
override suspend fun flushChapter(chapter: MangaChapter) {
|
override suspend fun flushChapter(chapter: MangaChapter) {
|
||||||
val output = chaptersOutput.remove(chapter) ?: return
|
val output = chaptersOutput.remove(chapter) ?: return
|
||||||
output.flushAndFinish()
|
output.flushAndFinish()
|
||||||
|
flushIndex()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun finish() {
|
override suspend fun finish() {
|
||||||
runInterruptible(Dispatchers.IO) {
|
flushIndex()
|
||||||
File(rootFile, ENTRY_NAME_INDEX).writeText(index.toString())
|
|
||||||
}
|
|
||||||
for (output in chaptersOutput.values) {
|
for (output in chaptersOutput.values) {
|
||||||
output.flushAndFinish()
|
output.flushAndFinish()
|
||||||
}
|
}
|
||||||
@@ -103,6 +103,10 @@ class LocalMangaDirOutput(
|
|||||||
return "${chapter.number}_${chapter.name.toFileNameSafe()}".take(18) + ".cbz"
|
return "${chapter.number}_${chapter.name.toFileNameSafe()}".take(18) + ".cbz"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun flushIndex() = runInterruptible(Dispatchers.IO) {
|
||||||
|
File(rootFile, ENTRY_NAME_INDEX).writeText(index.toString())
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val FILENAME_PATTERN = "%08d_%03d%03d"
|
private const val FILENAME_PATTERN = "%08d_%03d%03d"
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import kotlinx.coroutines.runInterruptible
|
|||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.local.data.LocalManga
|
import org.koitharu.kotatsu.local.data.LocalManga
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||||
import org.koitharu.kotatsu.local.data.MangaIndex
|
|
||||||
import org.koitharu.kotatsu.local.data.TempFileFilter
|
import org.koitharu.kotatsu.local.data.TempFileFilter
|
||||||
import org.koitharu.kotatsu.local.data.input.LocalMangaInput
|
import org.koitharu.kotatsu.local.data.input.LocalMangaInput
|
||||||
import org.koitharu.kotatsu.local.data.output.LocalMangaDirOutput
|
import org.koitharu.kotatsu.local.data.output.LocalMangaDirOutput
|
||||||
import org.koitharu.kotatsu.local.data.output.LocalMangaOutput
|
|
||||||
import org.koitharu.kotatsu.local.data.output.LocalMangaZipOutput
|
import org.koitharu.kotatsu.local.data.output.LocalMangaZipOutput
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
@@ -27,10 +25,9 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
|
|||||||
import org.koitharu.kotatsu.utils.AlphanumComparator
|
import org.koitharu.kotatsu.utils.AlphanumComparator
|
||||||
import org.koitharu.kotatsu.utils.CompositeMutex
|
import org.koitharu.kotatsu.utils.CompositeMutex
|
||||||
import org.koitharu.kotatsu.utils.ext.deleteAwait
|
import org.koitharu.kotatsu.utils.ext.deleteAwait
|
||||||
import org.koitharu.kotatsu.utils.ext.readText
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.zip.ZipFile
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -114,16 +111,11 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getRemoteManga(localManga: Manga): Manga? {
|
suspend fun getRemoteManga(localManga: Manga): Manga? {
|
||||||
val file = runCatching {
|
return runCatchingCancellable {
|
||||||
Uri.parse(localManga.url).toFile()
|
LocalMangaInput.of(localManga).getMangaInfo()
|
||||||
}.getOrNull() ?: return null
|
}.onFailure {
|
||||||
return runInterruptible(Dispatchers.IO) {
|
it.printStackTraceDebug()
|
||||||
ZipFile(file).use { zip ->
|
}.getOrNull()
|
||||||
val entry = zip.getEntry(LocalMangaOutput.ENTRY_NAME_INDEX)
|
|
||||||
val index = entry?.let(zip::readText)?.let(::MangaIndex)
|
|
||||||
index?.getMangaInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun findSavedManga(remoteManga: Manga): LocalManga? {
|
suspend fun findSavedManga(remoteManga: Manga): LocalManga? {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ fun File.getStorageName(context: Context): String = runCatching {
|
|||||||
fun Uri.toFileOrNull() = if (scheme == "file") path?.let(::File) else null
|
fun Uri.toFileOrNull() = if (scheme == "file") path?.let(::File) else null
|
||||||
|
|
||||||
suspend fun File.deleteAwait() = withContext(Dispatchers.IO) {
|
suspend fun File.deleteAwait() = withContext(Dispatchers.IO) {
|
||||||
delete()
|
delete() || deleteRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ContentResolver.resolveName(uri: Uri): String? {
|
fun ContentResolver.resolveName(uri: Uri): String? {
|
||||||
|
|||||||
Reference in New Issue
Block a user