diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 183158ef1..c8a0b79fd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,7 +22,7 @@ android { defaultConfig { applicationId = "eu.kanade.tachiyomi" - versionCode = 112 + versionCode = 113 versionName = "0.14.7" buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 988bc98a3..eef754b69 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -41,7 +41,6 @@ import eu.kanade.tachiyomi.data.backup.BackupCreateJob import eu.kanade.tachiyomi.data.backup.BackupFileValidator import eu.kanade.tachiyomi.data.backup.BackupRestoreJob import eu.kanade.tachiyomi.data.cache.ChapterCache -import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.copyToClipboard @@ -98,7 +97,6 @@ object SettingsDataScreen : SearchableSettings { UniFile.fromUri(context, uri)?.let { storageDirPref.set(it.uri.toString()) } - Injekt.get().invalidateCache() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 60e85547f..022dcefcd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -381,12 +381,7 @@ object Migrations { newKey = { Preference.privateKey(it) }, ) } - if (oldVersion < 111) { - File(context.cacheDir, "dl_index_cache") - .takeIf { it.exists() } - ?.delete() - } - if (oldVersion < 112) { + if (oldVersion < 113) { val prefsToReplace = listOf( "pref_download_only", "incognito_mode", @@ -406,6 +401,9 @@ object Migrations { filterPredicate = { it.key in prefsToReplace }, newKey = { Preference.appStateKey(it) }, ) + + // Deleting old download cache index files, but might as well clear it all out + context.cacheDir.deleteRecursively() } return true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt index 17930a89e..604745279 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt @@ -18,7 +18,6 @@ import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart @@ -48,7 +47,7 @@ import tachiyomi.core.util.system.logcat import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.source.service.SourceManager -import tachiyomi.domain.storage.service.StoragePreferences +import tachiyomi.domain.storage.service.StorageManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File @@ -66,7 +65,7 @@ class DownloadCache( private val provider: DownloadProvider = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), private val extensionManager: ExtensionManager = Injekt.get(), - storagePreferences: StoragePreferences = Injekt.get(), + private val storageManager: StorageManager = Injekt.get(), ) { private val scope = CoroutineScope(Dispatchers.IO) @@ -74,7 +73,7 @@ class DownloadCache( private val _changes: Channel = Channel(Channel.UNLIMITED) val changes = _changes.receiveAsFlow() .onStart { emit(Unit) } - .shareIn(scope, SharingStarted.Eagerly, 1) + .shareIn(scope, SharingStarted.Lazily, 1) /** * The interval after which this cache should be invalidated. 1 hour shouldn't cause major @@ -94,10 +93,10 @@ class DownloadCache( .stateIn(scope, SharingStarted.WhileSubscribed(), false) private val diskCacheFile: File - get() = File(context.cacheDir, "dl_index_cache_v2") + get() = File(context.cacheDir, "dl_index_cache_v3") private val rootDownloadsDirLock = Mutex() - private var rootDownloadsDir = RootDirectory(provider.downloadsDir) + private var rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory()) init { // Attempt to read cache file @@ -115,12 +114,8 @@ class DownloadCache( } } - storagePreferences.baseStorageDirectory().changes() - .drop(1) - .onEach { - rootDownloadsDir = RootDirectory(provider.downloadsDir) - invalidateCache() - } + storageManager.changes + .onEach { invalidateCache() } .launchIn(scope) } @@ -294,6 +289,8 @@ class DownloadCache( fun invalidateCache() { lastRenew = 0L renewalJob?.cancel() + diskCacheFile.delete() + renewCache() } /** @@ -310,23 +307,26 @@ class DownloadCache( _isInitializing.emit(true) } - var sources = getSources() - // Try to wait until extensions and sources have loaded - withTimeoutOrNull(30.seconds) { - while (!extensionManager.isInitialized) { - delay(2.seconds) - } + var sources = getSources() + if (sources.isEmpty()) { + withTimeoutOrNull(30.seconds) { + while (!extensionManager.isInitialized) { + delay(2.seconds) + } - while (sources.isEmpty()) { - delay(2.seconds) - sources = getSources() + while (extensionManager.availableExtensionsFlow.value.isNotEmpty() && sources.isEmpty()) { + delay(2.seconds) + sources = getSources() + } } } val sourceMap = sources.associate { provider.getSourceDirName(it).lowercase() to it.id } rootDownloadsDirLock.withLock { + rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory()) + val sourceDirs = rootDownloadsDir.dir?.listFiles().orEmpty() .filter { it.isDirectory && !it.name.isNullOrBlank() } .mapNotNull { dir -> @@ -371,10 +371,9 @@ class DownloadCache( }.also { it.invokeOnCompletion(onCancelling = true) { exception -> if (exception != null && exception !is CancellationException) { - logcat(LogPriority.ERROR, exception) { "Failed to create download cache" } + logcat(LogPriority.ERROR, exception) { "DownloadCache: failed to create cache" } } lastRenew = System.currentTimeMillis() - notifyChanges() } } diff --git a/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt b/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt index a1fff4269..4527fe89a 100644 --- a/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt +++ b/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt @@ -6,8 +6,14 @@ import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.util.storage.DiskUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.shareIn class StorageManager( private val context: Context, @@ -16,24 +22,33 @@ class StorageManager( private val scope = CoroutineScope(Dispatchers.IO) - private var baseDir: UniFile? = storagePreferences.baseStorageDirectory().get().let(::getBaseDir) + private var baseDir: UniFile? = getBaseDir(storagePreferences.baseStorageDirectory().get()) + + private val _changes: Channel = Channel(Channel.UNLIMITED) + val changes = _changes.receiveAsFlow() + .shareIn(scope, SharingStarted.Lazily, 1) init { storagePreferences.baseStorageDirectory().changes() - .onEach { baseDir = getBaseDir(it) } + .drop(1) + .distinctUntilChanged() + .onEach { uri -> + baseDir = getBaseDir(uri) + baseDir?.let { parent -> + parent.createDirectory(AUTOMATIC_BACKUPS_PATH) + parent.createDirectory(LOCAL_SOURCE_PATH) + parent.createDirectory(DOWNLOADS_PATH).also { + DiskUtil.createNoMediaFile(it, context) + } + } + _changes.send(Unit) + } .launchIn(scope) } - private fun getBaseDir(path: String): UniFile? { - val file = UniFile.fromUri(context, path.toUri()) - - return file.takeIf { it?.exists() == true }?.also { parent -> - parent.createDirectory(AUTOMATIC_BACKUPS_PATH) - parent.createDirectory(LOCAL_SOURCE_PATH) - parent.createDirectory(DOWNLOADS_PATH).also { - DiskUtil.createNoMediaFile(it, context) - } - } + private fun getBaseDir(uri: String): UniFile? { + return UniFile.fromUri(context, uri.toUri()) + .takeIf { it?.exists() == true } } fun getAutomaticBackupsDirectory(): UniFile? {