Fix local chapters deletion

master
Koitharu 2 years ago
parent 16027e3295
commit 7e581a5ed7
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -10,7 +10,6 @@ import android.provider.OpenableColumns
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.fs.FileSequence import org.koitharu.kotatsu.core.fs.FileSequence
import java.io.File 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 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() delete() || deleteRecursively()
} }

@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
@ -144,6 +145,7 @@ class DetailsViewModel @Inject constructor(
val localSize = details val localSize = details
.map { it?.local } .map { it?.local }
.distinctUntilChanged() .distinctUntilChanged()
.combine(localStorageChanges.onStart { emit(null) }) { x, _ -> x }
.map { local -> .map { local ->
if (local != null) { if (local != null) {
runCatchingCancellable { runCatchingCancellable {

@ -106,7 +106,7 @@ class MangaIndex(source: String?) {
} }
fun removeChapter(id: Long): Boolean { 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? { fun getChapterFileName(chapterId: Long): String? {

@ -1,5 +1,7 @@
package org.koitharu.kotatsu.local.data.output package org.koitharu.kotatsu.local.data.output
import androidx.core.net.toFile
import androidx.core.net.toUri
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex 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.util.ext.takeIfReadable
import org.koitharu.kotatsu.core.zip.ZipOutput import org.koitharu.kotatsu.core.zip.ZipOutput
import org.koitharu.kotatsu.local.data.MangaIndex 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.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.util.toFileNameSafe import org.koitharu.kotatsu.parsers.util.toFileNameSafe
@ -46,22 +49,23 @@ class LocalMangaDirOutput(
flushIndex() flushIndex()
} }
override suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, ext: String) = mutex.withLock { override suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, ext: String) =
val output = chaptersOutput.getOrPut(chapter.value) { mutex.withLock {
ZipOutput(File(rootFile, chapterFileName(chapter) + SUFFIX_TMP)) 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)
} }
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 { override suspend fun flushChapter(chapter: MangaChapter): Boolean = mutex.withLock {
val output = chaptersOutput.remove(chapter) ?: return@withLock false val output = chaptersOutput.remove(chapter) ?: return@withLock false
@ -90,13 +94,24 @@ class LocalMangaDirOutput(
} }
} }
suspend fun deleteChapter(chapterId: Long) = mutex.withLock { suspend fun deleteChapters(ids: Set<Long>) = mutex.withLock {
val chapter = checkNotNull(index.getMangaInfo()?.chapters?.withIndex()) { val chapters = checkNotNull((index.getMangaInfo() ?: LocalMangaDirInput(rootFile).getManga().manga).chapters) {
"No chapters found" "No chapters found"
}.find { x -> x.value.id == chapterId } ?: error("Chapter not found") }.withIndex()
val chapterDir = File(rootFile, chapterFileName(chapter)) val victimsIds = ids.toMutableSet()
chapterDir.deleteAwait() for (chapter in chapters) {
index.removeChapter(chapterId) 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) { fun setIndex(newIndex: MangaIndex) {

@ -25,9 +25,7 @@ class LocalMangaUtil(
} }
is LocalMangaDirOutput -> { is LocalMangaDirOutput -> {
for (id in ids) { output.deleteChapters(ids)
output.deleteChapter(id)
}
output.finish() output.finish()
} }
} }

@ -11,6 +11,7 @@ import androidx.core.content.ContextCompat
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ErrorReporterReceiver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.ui.CoroutineIntentService import org.koitharu.kotatsu.core.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage 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 manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return
startForeground() startForeground()
val mangaWithChapters = localMangaRepository.getDetails(manga) try {
localMangaRepository.deleteChapters(mangaWithChapters, chaptersIds) val mangaWithChapters = localMangaRepository.getDetails(manga)
localStorageChanges.emit(LocalManga(localMangaRepository.getDetails(manga))) localMangaRepository.deleteChapters(mangaWithChapters, chaptersIds)
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) localStorageChanges.emit(LocalManga(localMangaRepository.getDetails(manga)))
} finally {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
}
} }
override fun onError(startId: Int, error: Throwable) { override fun onError(startId: Int, error: Throwable) {
@ -60,6 +64,7 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
.setContentText(error.getDisplayMessage(resources)) .setContentText(error.getDisplayMessage(resources))
.setSmallIcon(android.R.drawable.stat_notify_error) .setSmallIcon(android.R.drawable.stat_notify_error)
.setAutoCancel(true) .setAutoCancel(true)
.setContentIntent(ErrorReporterReceiver.getPendingIntent(this, error))
.build() .build()
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(NOTIFICATION_ID + startId, notification) nm.notify(NOTIFICATION_ID + startId, notification)

Loading…
Cancel
Save