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 { defaultConfig {
applicationId = "eu.kanade.tachiyomi" applicationId = "eu.kanade.tachiyomi"
versionCode = 112 versionCode = 113
versionName = "0.14.7" versionName = "0.14.7"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") 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.BackupFileValidator
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
import eu.kanade.tachiyomi.data.cache.ChapterCache 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.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
@ -98,7 +97,6 @@ object SettingsDataScreen : SearchableSettings {
UniFile.fromUri(context, uri)?.let { UniFile.fromUri(context, uri)?.let {
storageDirPref.set(it.uri.toString()) storageDirPref.set(it.uri.toString())
} }
Injekt.get<DownloadCache>().invalidateCache()
} }
} }
} }

View file

@ -381,12 +381,7 @@ object Migrations {
newKey = { Preference.privateKey(it) }, newKey = { Preference.privateKey(it) },
) )
} }
if (oldVersion < 111) { if (oldVersion < 113) {
File(context.cacheDir, "dl_index_cache")
.takeIf { it.exists() }
?.delete()
}
if (oldVersion < 112) {
val prefsToReplace = listOf( val prefsToReplace = listOf(
"pref_download_only", "pref_download_only",
"incognito_mode", "incognito_mode",
@ -406,6 +401,9 @@ object Migrations {
filterPredicate = { it.key in prefsToReplace }, filterPredicate = { it.key in prefsToReplace },
newKey = { Preference.appStateKey(it) }, newKey = { Preference.appStateKey(it) },
) )
// Deleting old download cache index files, but might as well clear it all out
context.cacheDir.deleteRecursively()
} }
return true return true
} }

View file

@ -18,7 +18,6 @@ import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
@ -48,7 +47,7 @@ import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
@ -66,7 +65,7 @@ class DownloadCache(
private val provider: DownloadProvider = Injekt.get(), private val provider: DownloadProvider = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(), private val extensionManager: ExtensionManager = Injekt.get(),
storagePreferences: StoragePreferences = Injekt.get(), private val storageManager: StorageManager = Injekt.get(),
) { ) {
private val scope = CoroutineScope(Dispatchers.IO) private val scope = CoroutineScope(Dispatchers.IO)
@ -74,7 +73,7 @@ class DownloadCache(
private val _changes: Channel<Unit> = Channel(Channel.UNLIMITED) private val _changes: Channel<Unit> = Channel(Channel.UNLIMITED)
val changes = _changes.receiveAsFlow() val changes = _changes.receiveAsFlow()
.onStart { emit(Unit) } .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 * 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) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
private val diskCacheFile: File 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 val rootDownloadsDirLock = Mutex()
private var rootDownloadsDir = RootDirectory(provider.downloadsDir) private var rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory())
init { init {
// Attempt to read cache file // Attempt to read cache file
@ -115,12 +114,8 @@ class DownloadCache(
} }
} }
storagePreferences.baseStorageDirectory().changes() storageManager.changes
.drop(1) .onEach { invalidateCache() }
.onEach {
rootDownloadsDir = RootDirectory(provider.downloadsDir)
invalidateCache()
}
.launchIn(scope) .launchIn(scope)
} }
@ -294,6 +289,8 @@ class DownloadCache(
fun invalidateCache() { fun invalidateCache() {
lastRenew = 0L lastRenew = 0L
renewalJob?.cancel() renewalJob?.cancel()
diskCacheFile.delete()
renewCache()
} }
/** /**
@ -310,23 +307,26 @@ class DownloadCache(
_isInitializing.emit(true) _isInitializing.emit(true)
} }
var sources = getSources()
// Try to wait until extensions and sources have loaded // Try to wait until extensions and sources have loaded
withTimeoutOrNull(30.seconds) { var sources = getSources()
while (!extensionManager.isInitialized) { if (sources.isEmpty()) {
delay(2.seconds) withTimeoutOrNull(30.seconds) {
} while (!extensionManager.isInitialized) {
delay(2.seconds)
}
while (sources.isEmpty()) { while (extensionManager.availableExtensionsFlow.value.isNotEmpty() && sources.isEmpty()) {
delay(2.seconds) delay(2.seconds)
sources = getSources() sources = getSources()
}
} }
} }
val sourceMap = sources.associate { provider.getSourceDirName(it).lowercase() to it.id } val sourceMap = sources.associate { provider.getSourceDirName(it).lowercase() to it.id }
rootDownloadsDirLock.withLock { rootDownloadsDirLock.withLock {
rootDownloadsDir = RootDirectory(storageManager.getDownloadsDirectory())
val sourceDirs = rootDownloadsDir.dir?.listFiles().orEmpty() val sourceDirs = rootDownloadsDir.dir?.listFiles().orEmpty()
.filter { it.isDirectory && !it.name.isNullOrBlank() } .filter { it.isDirectory && !it.name.isNullOrBlank() }
.mapNotNull { dir -> .mapNotNull { dir ->
@ -371,10 +371,9 @@ class DownloadCache(
}.also { }.also {
it.invokeOnCompletion(onCancelling = true) { exception -> it.invokeOnCompletion(onCancelling = true) { exception ->
if (exception != null && exception !is CancellationException) { 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() lastRenew = System.currentTimeMillis()
notifyChanges() notifyChanges()
} }
} }

View file

@ -6,8 +6,14 @@ import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers 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.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.shareIn
class StorageManager( class StorageManager(
private val context: Context, private val context: Context,
@ -16,24 +22,33 @@ class StorageManager(
private val scope = CoroutineScope(Dispatchers.IO) 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 { init {
storagePreferences.baseStorageDirectory().changes() 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) .launchIn(scope)
} }
private fun getBaseDir(path: String): UniFile? { private fun getBaseDir(uri: String): UniFile? {
val file = UniFile.fromUri(context, path.toUri()) return UniFile.fromUri(context, uri.toUri())
.takeIf { it?.exists() == true }
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)
}
}
} }
fun getAutomaticBackupsDirectory(): UniFile? { fun getAutomaticBackupsDirectory(): UniFile? {