Skip to content

Commit

Permalink
fix(downloader): Don't invalidate anime downloads on startup (#1753)
Browse files Browse the repository at this point in the history
  • Loading branch information
Secozzi authored Aug 29, 2024
1 parent 5c93981 commit dabfdf9
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.data.download.anime

import android.app.Application
import android.content.Context
import android.net.Uri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
Expand All @@ -12,6 +14,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.debounce
Expand All @@ -26,7 +30,15 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.protobuf.ProtoBuf
import logcat.LogPriority
import tachiyomi.core.common.storage.extension
Expand Down Expand Up @@ -212,27 +224,28 @@ class AnimeDownloadCache(
* @param animeUniFile the directory of the anime.
* @param anime the anime of the episode.
*/
@Synchronized
fun addEpisode(episodeDirName: String, animeUniFile: UniFile, anime: Anime) {
// Retrieve the cached source directory or cache a new one
var sourceDir = rootDownloadsDir.sourceDirs[anime.source]
if (sourceDir == null) {
val source = sourceManager.get(anime.source) ?: return
val sourceUniFile = provider.findSourceDir(source) ?: return
sourceDir = SourceDirectory(sourceUniFile)
rootDownloadsDir.sourceDirs += anime.source to sourceDir
}
suspend fun addEpisode(episodeDirName: String, animeUniFile: UniFile, anime: Anime) {
rootDownloadsDirLock.withLock {
// Retrieve the cached source directory or cache a new one
var sourceDir = rootDownloadsDir.sourceDirs[anime.source]
if (sourceDir == null) {
val source = sourceManager.get(anime.source) ?: return
val sourceUniFile = provider.findSourceDir(source) ?: return
sourceDir = SourceDirectory(sourceUniFile)
rootDownloadsDir.sourceDirs += anime.source to sourceDir
}

// Retrieve the cached anime directory or cache a new one
val animeDirName = provider.getAnimeDirName(anime.title)
var animeDir = sourceDir.animeDirs[animeDirName]
if (animeDir == null) {
animeDir = AnimeDirectory(animeUniFile)
sourceDir.animeDirs += animeDirName to animeDir
}
// Retrieve the cached anime directory or cache a new one
val animeDirName = provider.getAnimeDirName(anime.title)
var animeDir = sourceDir.animeDirs[animeDirName]
if (animeDir == null) {
animeDir = AnimeDirectory(animeUniFile)
sourceDir.animeDirs += animeDirName to animeDir
}

// Save the episode directory
animeDir.episodeDirs += episodeDirName
// Save the chapter directory
animeDir.episodeDirs += episodeDirName
}

notifyChanges()
}
Expand All @@ -243,15 +256,18 @@ class AnimeDownloadCache(
* @param episode the episode to remove.
* @param anime the anime of the episode.
*/
@Synchronized
fun removeEpisode(episode: Episode, anime: Anime) {
val sourceDir = rootDownloadsDir.sourceDirs[anime.source] ?: return
val animeDir = sourceDir.animeDirs[provider.getAnimeDirName(anime.title)] ?: return
provider.getValidEpisodeDirNames(episode.name, episode.scanlator).forEach {
if (it in animeDir.episodeDirs) {
animeDir.episodeDirs -= it
suspend fun removeEpisode(episode: Episode, anime: Anime) {
rootDownloadsDirLock.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[anime.source] ?: return
val animeDir = sourceDir.animeDirs[provider.getAnimeDirName(anime.title)] ?: return
provider.getValidEpisodeDirNames(episode.name, episode.scanlator).forEach {
if (it in animeDir.episodeDirs) {
animeDir.episodeDirs -= it
}
}
}

notifyChanges()
}

/**
Expand All @@ -260,17 +276,19 @@ class AnimeDownloadCache(
* @param episodes the list of episode to remove.
* @param anime the anime of the episode.
*/
@Synchronized
fun removeEpisodes(episodes: List<Episode>, anime: Anime) {
val sourceDir = rootDownloadsDir.sourceDirs[anime.source] ?: return
val animeDir = sourceDir.animeDirs[provider.getAnimeDirName(anime.title)] ?: return
episodes.forEach { episode ->
provider.getValidEpisodeDirNames(episode.name, episode.scanlator).forEach {
if (it in animeDir.episodeDirs) {
animeDir.episodeDirs -= it
suspend fun removeEpisodes(episodes: List<Episode>, anime: Anime) {
rootDownloadsDirLock.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[anime.source] ?: return
val animeDir = sourceDir.animeDirs[provider.getAnimeDirName(anime.title)] ?: return
episodes.forEach { episode ->
provider.getValidEpisodeDirNames(episode.name, episode.scanlator).forEach {
if (it in animeDir.episodeDirs) {
animeDir.episodeDirs -= it
}
}
}
}

notifyChanges()
}

Expand All @@ -279,19 +297,22 @@ class AnimeDownloadCache(
*
* @param anime the anime to remove.
*/
@Synchronized
fun removeAnime(anime: Anime) {
val sourceDir = rootDownloadsDir.sourceDirs[anime.source] ?: return
val animeDirName = provider.getAnimeDirName(anime.title)
if (sourceDir.animeDirs.containsKey(animeDirName)) {
sourceDir.animeDirs -= animeDirName
suspend fun removeAnime(anime: Anime) {
rootDownloadsDirLock.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[anime.source] ?: return
val animeDirName = provider.getAnimeDirName(anime.title)
if (sourceDir.animeDirs.containsKey(animeDirName)) {
sourceDir.animeDirs -= animeDirName
}
}

notifyChanges()
}

fun removeSource(source: AnimeSource) {
rootDownloadsDir.sourceDirs -= source.id
suspend fun removeSource(source: AnimeSource) {
rootDownloadsDirLock.withLock {
rootDownloadsDir.sourceDirs -= source.id
}

notifyChanges()
}
Expand Down Expand Up @@ -340,7 +361,6 @@ class AnimeDownloadCache(
sourceId?.let { it to SourceDirectory(dir) }
}
.toMap()
.let { ConcurrentHashMap(it) }

rootDownloadsDir.sourceDirs = sourceDirs

Expand All @@ -353,7 +373,7 @@ class AnimeDownloadCache(

sourceDir.animeDirs = ConcurrentHashMap(animeDirs)

animeDirs.values.forEach { animeDir ->
sourceDir.animeDirs.values.forEach { animeDir ->
val episodeDirs = animeDir.dir?.listFiles().orEmpty()
.mapNotNull {
when {
Expand Down Expand Up @@ -384,7 +404,6 @@ class AnimeDownloadCache(
if (exception != null && exception !is CancellationException) {
logcat(LogPriority.ERROR, exception) { "Failed to create download cache" }
}

lastRenew = System.currentTimeMillis()
notifyChanges()
}
Expand All @@ -402,29 +421,78 @@ class AnimeDownloadCache(
scope.launchNonCancellable {
_changes.send(Unit)
}
updateDiskCache()
}

private var updateDiskCacheJob: Job? = null

@Suppress("MagicNumber")
private fun updateDiskCache() {
updateDiskCacheJob?.cancel()
updateDiskCacheJob = scope.launchIO {
delay(1000)
ensureActive()
val bytes = ProtoBuf.encodeToByteArray(rootDownloadsDir)
ensureActive()
try {
diskCacheFile.writeBytes(bytes)
} catch (e: Throwable) {
logcat(
priority = LogPriority.ERROR,
throwable = e,
message = { "Failed to write disk cache file" },
)
}
}
}
}

/**
* Class to store the files under the root downloads directory.
*/
@Serializable
private class RootDirectory(
@Serializable(with = UniFileAsStringSerializer::class)
val dir: UniFile?,
var sourceDirs: ConcurrentHashMap<Long, SourceDirectory> = ConcurrentHashMap(),
var sourceDirs: Map<Long, SourceDirectory> = mapOf(),
)

/**
* Class to store the files under a source directory.
*/
@Serializable
private class SourceDirectory(
@Serializable(with = UniFileAsStringSerializer::class)
val dir: UniFile?,
var animeDirs: ConcurrentHashMap<String, AnimeDirectory> = ConcurrentHashMap(),
var animeDirs: Map<String, AnimeDirectory> = mapOf(),
)

/**
* Class to store the files under a manga directory.
*/
@Serializable
private class AnimeDirectory(
@Serializable(with = UniFileAsStringSerializer::class)
val dir: UniFile?,
var episodeDirs: MutableSet<String> = mutableSetOf(),
)

private object UniFileAsStringSerializer : KSerializer<UniFile?> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UniFile", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: UniFile?) {
return if (value == null) {
encoder.encodeNull()
} else {
encoder.encodeString(value.uri.toString())
}
}

override fun deserialize(decoder: Decoder): UniFile? {
return if (decoder.decodeNotNullMark()) {
UniFile.fromUri(Injekt.get<Application>(), Uri.parse(decoder.decodeString()))
} else {
decoder.decodeNull()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ class AnimeDownloadManager(
* @param oldEpisode the existing episode with the old name.
* @param newEpisode the target episode with the new name.
*/
fun renameEpisode(source: AnimeSource, anime: Anime, oldEpisode: Episode, newEpisode: Episode) {
suspend fun renameEpisode(source: AnimeSource, anime: Anime, oldEpisode: Episode, newEpisode: Episode) {
val oldNames = provider.getValidEpisodeDirNames(oldEpisode.name, oldEpisode.scanlator)
val animeDir = provider.getAnimeDir(anime.title, source)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1244,7 +1244,7 @@ class AnimeDownloader(
* @param tmpDir the directory where the download is currently stored.
* @param dirname the real (non temporary) directory name of the download.
*/
private fun ensureSuccessfulAnimeDownload(
private suspend fun ensureSuccessfulAnimeDownload(
download: AnimeDownload,
animeDir: UniFile,
tmpDir: UniFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ class MangaDownloadCache(
// Save the chapter directory
mangaDir.chapterDirs += chapterDirName
}

notifyChanges()
}

Expand Down Expand Up @@ -279,7 +280,6 @@ class MangaDownloadCache(
* @param chapters the list of chapter to remove.
* @param manga the manga of the chapter.
*/

suspend fun removeChapters(chapters: List<Chapter>, manga: Manga) {
rootDownloadsDirLock.withLock {
val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
Expand Down Expand Up @@ -375,7 +375,7 @@ class MangaDownloadCache(

sourceDir.mangaDirs = ConcurrentHashMap(mangaDirs)

mangaDirs.values.forEach { mangaDir ->
sourceDir.mangaDirs.values.forEach { mangaDir ->
val chapterDirs = mangaDir.dir?.listFiles().orEmpty()
.mapNotNull {
when {
Expand Down

0 comments on commit dabfdf9

Please sign in to comment.