diff --git a/app/src/main/kotlin/com/subtracks/SubtracksApp.kt b/app/src/main/kotlin/com/subtracks/SubtracksApp.kt index cd2ed7dc..44c95eb3 100644 --- a/app/src/main/kotlin/com/subtracks/SubtracksApp.kt +++ b/app/src/main/kotlin/com/subtracks/SubtracksApp.kt @@ -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 = diff --git a/app/src/main/kotlin/com/subtracks/data/LegacyDataCleanup.kt b/app/src/main/kotlin/com/subtracks/data/LegacyDataCleanup.kt new file mode 100644 index 00000000..157db816 --- /dev/null +++ b/app/src/main/kotlin/com/subtracks/data/LegacyDataCleanup.kt @@ -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)") + } +} diff --git a/app/src/test/kotlin/com/subtracks/data/LegacyDataCleanupTest.kt b/app/src/test/kotlin/com/subtracks/data/LegacyDataCleanupTest.kt new file mode 100644 index 00000000..18b1ad2b --- /dev/null +++ b/app/src/test/kotlin/com/subtracks/data/LegacyDataCleanupTest.kt @@ -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() + } +}