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 # Changelog
## dev ## 2.5.0
#### What's New #### What's New
- Massively overhauled how music is loaded [#72]: - 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 shuffle button appearing below playback bar on Android 10 and lower
- Fixed incorrect song being shown in the notification in some cases [#179] - Fixed incorrect song being shown in the notification in some cases [#179]
- Fixed issue where toolbar will be clipped on Lollipop devices - Fixed issue where toolbar will be clipped on Lollipop devices
- Fixed infinite loading if one had no music folders set [#182]
#### What's Changed #### What's Changed
- Reworked typography and iconography to be more aligned with material design guidelines - 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 #### Dev/Meta
- Migrated preferences from shared object to utility - 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.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.formatDuration import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.logD
class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() { class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
private val detailModel: DetailViewModel by androidActivityViewModels() private val detailModel: DetailViewModel by androidActivityViewModels()
@ -58,8 +57,6 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
private fun updateSong(song: DetailViewModel.DetailSong?) { private fun updateSong(song: DetailViewModel.DetailSong?) {
val binding = requireBinding() val binding = requireBinding()
logD("$song")
if (song != null) { if (song != null) {
if (song.info != null) { if (song.info != null) {
binding.detailContainer.isGone = false binding.detailContainer.isGone = false

View file

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

View file

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

View file

@ -42,7 +42,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* *
* @author OxygenCobalt * @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() { class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
private data class Gain(val track: Float, val album: Float) 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. * 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. * This should ***NOT*** be used outside of the playback module.
* - If you want to use the playback state in the UI, use * - If you want to use the playback state in the UI, use
* [org.oxycblt.auxio.playback.PlaybackViewModel] as it can withstand volatile UIs. * [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 * - Headset management
* - Widgets * - Widgets
* *
* This service relies on [PlaybackStateManager.Callback] and [Settings.Callback], so therefore * This service is headless and does not manage the playback state. Moreover, the player instance is
* there's no need to bind to it to deliver commands. * 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 * 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. */ /** Get the list of directories that music should be hidden/loaded from. */
fun getMusicDirs(storageManager: StorageManager): MusicDirs { 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 = val dirs =
(inner.getStringSet(key, null) ?: emptySet()).mapNotNull { (inner.getStringSet(context.getString(R.string.set_key_music_dirs), null) ?: emptySet())
Directory.fromDocumentUri(storageManager, it) .mapNotNull { Directory.fromDocumentUri(storageManager, it) }
}
return MusicDirs( return MusicDirs(
dirs, inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false)) 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.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.os.Build import android.os.Build
import android.os.storage.StorageManager
import android.util.Log import android.util.Log
import androidx.core.content.edit import androidx.core.content.edit
import java.io.File
import org.oxycblt.auxio.R 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.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. // A couple of utils for migrating from old settings values to the new formats.
// Usually, these will last for 6 months before being removed. // 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)) 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. */ /** Cache of the old keys used in Auxio. */
private object OldKeys { private object OldKeys {
const val KEY_ACCENT3 = "auxio_accent" 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. * Collect a [stateFlow] into [block] immediately.
* *
* This method automatically calls [block] when initially starting to ensure UI state consistency. * This method automatically calls [block] when initially starting to ensure UI state consistency at
* This does nominally mean that there are two initializing collections, but this is considered * soon as the view is visible. This does nominally mean that there are two initializing
* okay. [block] should be a function pointer in order to ensure lifecycle consistency. * 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) { fun <T> Fragment.collectImmediately(stateFlow: StateFlow<T>, block: (T) -> Unit) {
block(stateFlow.value) 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 * 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 * 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 * @author OxygenCobalt
*/ */
@ -89,7 +89,7 @@ class TaskGuard {
*/ */
@Synchronized fun newHandle() = ++currentHandle @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 @Synchronized fun check(handle: Long) = handle == currentHandle
/** /**