mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
feat: clean up the 1.x app's leftover data on upgrade
The React Native 1.x app shipped as com.subtracks and left an AsyncStorage database (RKStorage), an older `servers` dump and a downloaded/cached artwork+audio tree in app-specific storage. v3 reads none of those paths, so an update would otherwise leave the data behind, reachable only by wiping the whole app. Remove them once at startup, gated on one of those 1.x paths existing, so a fresh install does nothing and an upgrade is cleaned exactly once. The cached trees can be large, so the delete runs off the main thread. Kept in a single file so it can be deleted once no installs can come from 1.x.
This commit is contained in:
@@ -5,6 +5,7 @@ import coil3.ImageLoader
|
||||
import coil3.SingletonImageLoader
|
||||
import coil3.annotation.DelicateCoilApi
|
||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||
import com.subtracks.data.LegacyDataCleanup
|
||||
import com.subtracks.data.prefs.UserPreferences
|
||||
import com.subtracks.data.repo.ArtworkSeedStore
|
||||
import com.subtracks.di.appModule
|
||||
@@ -25,6 +26,7 @@ class SubtracksApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.init(this)
|
||||
LegacyDataCleanup.run(this)
|
||||
stopKoin()
|
||||
SingletonImageLoader.reset()
|
||||
val koin =
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.subtracks.data
|
||||
|
||||
import android.content.Context
|
||||
import com.subtracks.log.Log
|
||||
import java.io.File
|
||||
|
||||
// One-shot cleanup of the 1.x React Native app, which shipped as com.subtracks before this rewrite.
|
||||
// It left an AsyncStorage database (RKStorage), an older `servers` dump and a downloaded/cached
|
||||
// artwork+audio tree (under the app's external files dir) that v3 never reads. Only a real 1.x upgrade
|
||||
// has those paths, so a fresh install does nothing and an upgrade is cleaned exactly once.
|
||||
//
|
||||
// Delete this file and its call in SubtracksApp once installs can no longer be coming from 1.x.
|
||||
internal object LegacyDataCleanup {
|
||||
fun run(context: Context) {
|
||||
// The cached trees can be large, so keep the walk off the main thread.
|
||||
Thread { run(context.dataDir, context.getExternalFilesDir(null)) }.start()
|
||||
}
|
||||
|
||||
internal fun run(
|
||||
dataDir: File,
|
||||
externalFilesDir: File?,
|
||||
) {
|
||||
val database = File(dataDir, "databases/RKStorage")
|
||||
val servers = File(dataDir, "files/servers")
|
||||
val cache = externalFilesDir?.let { File(it, "s") }
|
||||
if (!database.exists() && !servers.exists() && cache?.exists() != true) return
|
||||
deleteDatabase(database)
|
||||
deleteTree(servers)
|
||||
cache?.let(::deleteTree)
|
||||
}
|
||||
|
||||
private fun deleteDatabase(database: File) {
|
||||
listOf("", "-wal", "-shm", "-journal").forEach { deleteTree(File(database.path + it)) }
|
||||
}
|
||||
|
||||
private fun deleteTree(target: File) {
|
||||
if (!target.exists()) return
|
||||
val removed = target.deleteRecursively()
|
||||
Log.i("legacy", "removed ${target.path} ($removed)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.subtracks.data
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
|
||||
class LegacyDataCleanupTest {
|
||||
@get:Rule
|
||||
val tmp = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun removesThe1xLeftoversAndLeavesTheNativeData() {
|
||||
val dataDir = tmp.newFolder("data")
|
||||
val external = tmp.newFolder("external")
|
||||
|
||||
val legacyDatabase = file(dataDir, "databases/RKStorage")
|
||||
val legacyDatabaseWal = file(dataDir, "databases/RKStorage-wal")
|
||||
val legacyServers = file(dataDir, "files/servers/0.json")
|
||||
val legacyCache = file(external, "s/0/coverArt/cover")
|
||||
val nativeDatabase = file(dataDir, "databases/subtracks.db")
|
||||
val nativeDownload = file(external, "Music/downloads/song.mp3")
|
||||
|
||||
LegacyDataCleanup.run(dataDir, external)
|
||||
|
||||
assertFalse(legacyDatabase.exists())
|
||||
assertFalse(legacyDatabaseWal.exists())
|
||||
assertFalse(legacyServers.exists())
|
||||
assertFalse(legacyCache.exists())
|
||||
assertTrue(nativeDatabase.exists())
|
||||
assertTrue(nativeDownload.exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun leavesTheNativeDataAloneOnAFreshInstall() {
|
||||
val dataDir = tmp.newFolder("data2")
|
||||
val external = tmp.newFolder("external2")
|
||||
val nativeDatabase = file(dataDir, "databases/subtracks.db")
|
||||
val nativeDownload = file(external, "Music/downloads/song.mp3")
|
||||
|
||||
LegacyDataCleanup.run(dataDir, external)
|
||||
|
||||
assertTrue(nativeDatabase.exists())
|
||||
assertTrue(nativeDownload.exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cleansUpEvenWhenExternalStorageIsUnavailable() {
|
||||
val dataDir = tmp.newFolder("data3")
|
||||
val legacyDatabase = file(dataDir, "databases/RKStorage")
|
||||
|
||||
LegacyDataCleanup.run(dataDir, null)
|
||||
|
||||
assertFalse(legacyDatabase.exists())
|
||||
}
|
||||
|
||||
private fun file(
|
||||
dir: File,
|
||||
path: String,
|
||||
): File =
|
||||
File(dir, path).apply {
|
||||
parentFile?.mkdirs()
|
||||
createNewFile()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user