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:
parent
60b637e1ce
commit
f3aca45690
11 changed files with 25 additions and 108 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue