Fix local chapters deletion
This commit is contained in:
@@ -10,7 +10,6 @@ import android.provider.OpenableColumns
|
||||
import androidx.core.database.getStringOrNull
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.fs.FileSequence
|
||||
import java.io.File
|
||||
@@ -53,7 +52,7 @@ fun File.getStorageName(context: Context): String = runCatching {
|
||||
|
||||
fun Uri.toFileOrNull() = if (scheme == URI_SCHEME_FILE) path?.let(::File) else null
|
||||
|
||||
suspend fun File.deleteAwait() = withContext(Dispatchers.IO) {
|
||||
suspend fun File.deleteAwait() = runInterruptible(Dispatchers.IO) {
|
||||
delete() || deleteRecursively()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.plus
|
||||
@@ -144,6 +145,7 @@ class DetailsViewModel @Inject constructor(
|
||||
val localSize = details
|
||||
.map { it?.local }
|
||||
.distinctUntilChanged()
|
||||
.combine(localStorageChanges.onStart { emit(null) }) { x, _ -> x }
|
||||
.map { local ->
|
||||
if (local != null) {
|
||||
runCatchingCancellable {
|
||||
|
||||
@@ -106,7 +106,7 @@ class MangaIndex(source: String?) {
|
||||
}
|
||||
|
||||
fun removeChapter(id: Long): Boolean {
|
||||
return json.getJSONObject("chapters").remove(id.toString()) != null
|
||||
return json.has("chapters") && json.getJSONObject("chapters").remove(id.toString()) != null
|
||||
}
|
||||
|
||||
fun getChapterFileName(chapterId: Long): String? {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.koitharu.kotatsu.local.data.output
|
||||
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@@ -9,6 +11,7 @@ import org.koitharu.kotatsu.core.util.ext.deleteAwait
|
||||
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
||||
import org.koitharu.kotatsu.core.zip.ZipOutput
|
||||
import org.koitharu.kotatsu.local.data.MangaIndex
|
||||
import org.koitharu.kotatsu.local.data.input.LocalMangaDirInput
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
||||
@@ -46,22 +49,23 @@ class LocalMangaDirOutput(
|
||||
flushIndex()
|
||||
}
|
||||
|
||||
override suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, ext: String) = mutex.withLock {
|
||||
val output = chaptersOutput.getOrPut(chapter.value) {
|
||||
ZipOutput(File(rootFile, chapterFileName(chapter) + SUFFIX_TMP))
|
||||
}
|
||||
val name = buildString {
|
||||
append(FILENAME_PATTERN.format(chapter.value.branch.hashCode(), chapter.index + 1, pageNumber))
|
||||
if (ext.isNotEmpty() && ext.length <= 4) {
|
||||
append('.')
|
||||
append(ext)
|
||||
override suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, ext: String) =
|
||||
mutex.withLock {
|
||||
val output = chaptersOutput.getOrPut(chapter.value) {
|
||||
ZipOutput(File(rootFile, chapterFileName(chapter) + SUFFIX_TMP))
|
||||
}
|
||||
val name = buildString {
|
||||
append(FILENAME_PATTERN.format(chapter.value.branch.hashCode(), chapter.index + 1, pageNumber))
|
||||
if (ext.isNotEmpty() && ext.length <= 4) {
|
||||
append('.')
|
||||
append(ext)
|
||||
}
|
||||
}
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
output.put(name, file)
|
||||
}
|
||||
index.addChapter(chapter, chapterFileName(chapter))
|
||||
}
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
output.put(name, file)
|
||||
}
|
||||
index.addChapter(chapter, chapterFileName(chapter))
|
||||
}
|
||||
|
||||
override suspend fun flushChapter(chapter: MangaChapter): Boolean = mutex.withLock {
|
||||
val output = chaptersOutput.remove(chapter) ?: return@withLock false
|
||||
@@ -90,13 +94,24 @@ class LocalMangaDirOutput(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteChapter(chapterId: Long) = mutex.withLock {
|
||||
val chapter = checkNotNull(index.getMangaInfo()?.chapters?.withIndex()) {
|
||||
suspend fun deleteChapters(ids: Set<Long>) = mutex.withLock {
|
||||
val chapters = checkNotNull((index.getMangaInfo() ?: LocalMangaDirInput(rootFile).getManga().manga).chapters) {
|
||||
"No chapters found"
|
||||
}.find { x -> x.value.id == chapterId } ?: error("Chapter not found")
|
||||
val chapterDir = File(rootFile, chapterFileName(chapter))
|
||||
chapterDir.deleteAwait()
|
||||
index.removeChapter(chapterId)
|
||||
}.withIndex()
|
||||
val victimsIds = ids.toMutableSet()
|
||||
for (chapter in chapters) {
|
||||
if (!victimsIds.remove(chapter.value.id)) {
|
||||
continue
|
||||
}
|
||||
val chapterFile = index.getChapterFileName(chapter.value.id)?.let {
|
||||
File(rootFile, it)
|
||||
} ?: chapter.value.url.toUri().toFile()
|
||||
chapterFile.deleteAwait()
|
||||
index.removeChapter(chapter.value.id)
|
||||
}
|
||||
check(victimsIds.isEmpty()) {
|
||||
"${victimsIds.size} of ${ids.size} chapters was not removed: not found"
|
||||
}
|
||||
}
|
||||
|
||||
fun setIndex(newIndex: MangaIndex) {
|
||||
|
||||
@@ -25,9 +25,7 @@ class LocalMangaUtil(
|
||||
}
|
||||
|
||||
is LocalMangaDirOutput -> {
|
||||
for (id in ids) {
|
||||
output.deleteChapter(id)
|
||||
}
|
||||
output.deleteChapters(ids)
|
||||
output.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.core.content.ContextCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ErrorReporterReceiver
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
@@ -45,10 +46,13 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
||||
val manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
|
||||
val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return
|
||||
startForeground()
|
||||
val mangaWithChapters = localMangaRepository.getDetails(manga)
|
||||
localMangaRepository.deleteChapters(mangaWithChapters, chaptersIds)
|
||||
localStorageChanges.emit(LocalManga(localMangaRepository.getDetails(manga)))
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
try {
|
||||
val mangaWithChapters = localMangaRepository.getDetails(manga)
|
||||
localMangaRepository.deleteChapters(mangaWithChapters, chaptersIds)
|
||||
localStorageChanges.emit(LocalManga(localMangaRepository.getDetails(manga)))
|
||||
} finally {
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(startId: Int, error: Throwable) {
|
||||
@@ -60,6 +64,7 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
||||
.setContentText(error.getDisplayMessage(resources))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(ErrorReporterReceiver.getPendingIntent(this, error))
|
||||
.build()
|
||||
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
nm.notify(NOTIFICATION_ID + startId, notification)
|
||||
|
||||
Reference in New Issue
Block a user