settings: remove excluded dir migration code

Remove the excluded directory migration code, as it causes far more
issues than it fixes.

Due to an unfixable logic bug that occurs when trying to read the
setting, Auxio will always try to migrate the database when there is
no music folders, causing a hang in some situations. Fix it by just
removing the migration.
This commit is contained in:
OxygenCobalt 2022-07-12 09:56:38 -06:00
parent 60b637e1ce
commit f3aca45690
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
11 changed files with 25 additions and 108 deletions

View file

@ -1,6 +1,6 @@
# Changelog
## dev
## 2.5.0
#### What's New
- Massively overhauled how music is loaded [#72]:
@ -29,9 +29,11 @@ finished saving
- Fixed shuffle button appearing below playback bar on Android 10 and lower
- Fixed incorrect song being shown in the notification in some cases [#179]
- Fixed issue where toolbar will be clipped on Lollipop devices
- Fixed infinite loading if one had no music folders set [#182]
#### What's Changed
- Reworked typography and iconography to be more aligned with material design guidelines
- Old excluded directories from 2.3.1 will no longer be migrated
#### Dev/Meta
- Migrated preferences from shared object to utility

View file

@ -30,7 +30,6 @@ import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.logD
class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
private val detailModel: DetailViewModel by androidActivityViewModels()
@ -58,8 +57,6 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
private fun updateSong(song: DetailViewModel.DetailSong?) {
val binding = requireBinding()
logD("$song")
if (song != null) {
if (song.info != null) {
binding.detailContainer.isGone = false

View file

@ -212,8 +212,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
}
override fun onChange(selfChange: Boolean) {
// Batch rapid-fire updates to the library into a single call to run after an
// arbitrary amount of time.
// Batch rapid-fire updates to the library into a single call to run after 500ms
handler.removeCallbacks(this)
handler.postDelayed(this, REINDEX_DELAY)
}

View file

@ -454,7 +454,10 @@ class Api21MediaStoreBackend : MediaStoreBackend() {
val rawTrack = cursor.getIntOrNull(trackIndex)
if (rawTrack != null) {
rawTrack.packedTrackNo?.let { audio.track = it }
rawTrack.packedTrackNo?.let {
logD(it)
audio.track = it
}
rawTrack.packedDiscNo?.let { audio.disc = it }
}

View file

@ -42,7 +42,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
*
* @author OxygenCobalt
*
* TODO: Convert a low-level audio processor capable of handling any kind of PCM data.
* TODO: Convert to a low-level audio processor capable of handling any kind of PCM data.
*/
class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
private data class Gain(val track: Float, val album: Float)

View file

@ -34,6 +34,10 @@ import org.oxycblt.auxio.util.logW
/**
* Master class (and possible god object) for the playback state.
*
* Whereas other apps centralize the playback state around the MediaSession, Auxio does not, as
* MediaSession is a terrible API that prevents nice features like better album cover loading and a
* reasonable queue system.
*
* This should ***NOT*** be used outside of the playback module.
* - If you want to use the playback state in the UI, use
* [org.oxycblt.auxio.playback.PlaybackViewModel] as it can withstand volatile UIs.

View file

@ -63,8 +63,9 @@ import org.oxycblt.auxio.widgets.WidgetProvider
* - Headset management
* - Widgets
*
* This service relies on [PlaybackStateManager.Callback] and [Settings.Callback], so therefore
* there's no need to bind to it to deliver commands.
* This service is headless and does not manage the playback state. Moreover, the player instance is
* not the source of truth for the state, but rather the means to control system-side playback. Both
* of those tasks are what [PlaybackStateManager] is for.
*
* TODO: Android Auto
*

View file

@ -195,20 +195,9 @@ class Settings(private val context: Context, private val callback: Callback? = n
/** Get the list of directories that music should be hidden/loaded from. */
fun getMusicDirs(storageManager: StorageManager): MusicDirs {
val key = context.getString(R.string.set_key_music_dirs)
if (!inner.contains(key)) {
logD("Attempting to migrate excluded directories")
// We need to migrate this setting now while we have a context. Note that while
// this does do IO work, the old excluded directory database is so small as to make
// it negligible.
setMusicDirs(MusicDirs(handleExcludedCompat(context, storageManager), false))
}
val dirs =
(inner.getStringSet(key, null) ?: emptySet()).mapNotNull {
Directory.fromDocumentUri(storageManager, it)
}
(inner.getStringSet(context.getString(R.string.set_key_music_dirs), null) ?: emptySet())
.mapNotNull { Directory.fromDocumentUri(storageManager, it) }
return MusicDirs(
dirs, inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false))

View file

@ -19,21 +19,11 @@ package org.oxycblt.auxio.settings
import android.content.Context
import android.content.SharedPreferences
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.os.Build
import android.os.storage.StorageManager
import android.util.Log
import androidx.core.content.edit
import java.io.File
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.directoryCompat
import org.oxycblt.auxio.music.isInternalCompat
import org.oxycblt.auxio.music.storageVolumesCompat
import org.oxycblt.auxio.ui.accent.Accent
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.queryAll
// A couple of utils for migrating from old settings values to the new formats.
// Usually, these will last for 6 months before being removed.
@ -62,75 +52,6 @@ fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent {
return Accent.from(prefs.getInt(currentKey, Accent.DEFAULT))
}
/**
* Converts paths from the old excluded directory database to a list of modern [Directory]
* instances.
*
* Historically, Auxio used an excluded directory database shamelessly ripped from Phonograph. This
* was a dumb idea, as the choice of a full-blown database for a few paths was overkill, version
* boundaries were not respected, and the data format limited us to grokking DATA.
*
* In 2.4.0, Auxio switched to a system based on SharedPreferences, also switching from a path-based
* excluded system to a volume-based excluded system at the same time. These are both rolled into
* this conversion.
*/
fun handleExcludedCompat(context: Context, storageManager: StorageManager): List<Directory> {
Log.d("Auxio.SettingsCompat", "Migrating old excluded database")
// /storage/emulated/0 (the old path prefix) should correspond to primary *emulated* storage.
val primaryVolume =
storageManager.storageVolumesCompat.find { it.isInternalCompat } ?: return emptyList()
val primaryDirectory =
(primaryVolume.directoryCompat ?: return emptyList()) + File.separatorChar
return LegacyExcludedDatabase(context).readPaths().map { path ->
val relativePath = path.removePrefix(primaryDirectory)
Log.d("Auxio.SettingsCompat", "Migrate $path -> $relativePath")
Directory(primaryVolume, relativePath)
}
}
class LegacyExcludedDatabase(context: Context) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_PATH TEXT NOT NULL)")
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
onCreate(db)
}
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
onUpgrade(db, newVersion, oldVersion)
}
/** Get the current list of paths from the database. */
fun readPaths(): List<String> {
val paths = mutableListOf<String>()
readableDatabase.queryAll(TABLE_NAME) { cursor ->
while (cursor.moveToNext()) {
paths.add(cursor.getString(0))
}
}
logD("Successfully read ${paths.size} paths from db")
return paths
}
companion object {
// Blacklist is still used here for compatibility reasons, please don't get
// your pants in a twist about it.
const val DB_VERSION = 1
const val DB_NAME = "auxio_blacklist_database.db"
const val TABLE_NAME = "blacklist_dirs_table"
const val COLUMN_PATH = "COLUMN_PATH"
}
}
/** Cache of the old keys used in Auxio. */
private object OldKeys {
const val KEY_ACCENT3 = "auxio_accent"

View file

@ -171,11 +171,12 @@ fun <T> Fragment.collect(stateFlow: StateFlow<T>, block: (T) -> Unit) {
/**
* Collect a [stateFlow] into [block] immediately.
*
* This method automatically calls [block] when initially starting to ensure UI state consistency.
* This does nominally mean that there are two initializing collections, but this is considered
* okay. [block] should be a function pointer in order to ensure lifecycle consistency.
* This method automatically calls [block] when initially starting to ensure UI state consistency at
* soon as the view is visible. This does nominally mean that there are two initializing
* collections, but this is considered okay. [block] should be a function pointer in order to ensure
* lifecycle consistency.
*
* This should be used for state the absolutely needs to be shown at draw-time.
* This should be used if the state absolutely needs to be shown at draw-time.
*/
fun <T> Fragment.collectImmediately(stateFlow: StateFlow<T>, block: (T) -> Unit) {
block(stateFlow.value)

View file

@ -77,7 +77,7 @@ fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy {
/**
* An abstraction that allows cheap cooperative multi-threading in shared object contexts. Every new
* task should call [newHandle], while every running task should call [check] or [yield] depending
* on the context.
* on the context to determine if it should continue.
*
* @author OxygenCobalt
*/
@ -89,7 +89,7 @@ class TaskGuard {
*/
@Synchronized fun newHandle() = ++currentHandle
/** Check if the given [handle] is still the one stored by this class. */
/** Check if the given [handle] is still valid. */
@Synchronized fun check(handle: Long) = handle == currentHandle
/**