Fix download indexing with changed storage locations

Fixes #10218
This commit is contained in:
arkon 2023-12-15 18:44:37 -05:00
parent dd1a19745a
commit 36f400d542
5 changed files with 54 additions and 44 deletions

View file

@ -22,7 +22,7 @@ android {
defaultConfig {
applicationId = "eu.kanade.tachiyomi"
versionCode = 112
versionCode = 113
versionName = "0.14.7"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")

View file

@ -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<DownloadCache>().invalidateCache()
}
}
}

View file

@ -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
}

View file

@ -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<Unit> = 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()
}
}

View file

@ -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<Unit> = 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? {