system: handle fallible databases
Handle errors from databases. Either way, a crash from a database or a silent error will be equally nightmarish to debug. May as well keep going if they fail.
This commit is contained in:
parent
aa805e351c
commit
aa50c82635
5 changed files with 76 additions and 23 deletions
|
@ -26,6 +26,7 @@ import androidx.core.database.getStringOrNull
|
|||
import androidx.core.database.sqlite.transaction
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logE
|
||||
import org.oxycblt.auxio.util.queryAll
|
||||
import org.oxycblt.auxio.util.requireBackgroundThread
|
||||
import java.io.File
|
||||
|
@ -42,7 +43,12 @@ class CacheExtractor(private val context: Context) {
|
|||
private var shouldWriteCache = false
|
||||
|
||||
fun init() {
|
||||
cacheMap = CacheDatabase.getInstance(context).read()
|
||||
try {
|
||||
cacheMap = CacheDatabase.getInstance(context).read()
|
||||
} catch (e: Exception) {
|
||||
logE("Unable to load cache database.")
|
||||
logE(e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,16 +61,21 @@ class CacheExtractor(private val context: Context) {
|
|||
// If the entire library could not be loaded from the cache, we need to re-write it
|
||||
// with the new library.
|
||||
logD("Cache was invalidated during loading, rewriting")
|
||||
CacheDatabase.getInstance(context).write(rawSongs)
|
||||
try {
|
||||
CacheDatabase.getInstance(context).write(rawSongs)
|
||||
} catch (e: Exception) {
|
||||
logE("Unable to save cache database.")
|
||||
logE(e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe copy a cached raw song into this instance, assuming that it has not changed
|
||||
* since it was last saved.
|
||||
* since it was last saved. Returns true if a song was loaded.
|
||||
*/
|
||||
fun populateFromCache(rawSong: Song.Raw): Boolean {
|
||||
val map = requireNotNull(cacheMap) { "CacheExtractor was not properly initialized" }
|
||||
val map = cacheMap ?: return false
|
||||
|
||||
val cachedRawSong = map[rawSong.mediaStoreId]
|
||||
if (cachedRawSong != null && cachedRawSong.dateAdded == rawSong.dateAdded && cachedRawSong.dateModified == rawSong.dateModified) {
|
||||
|
|
|
@ -248,26 +248,32 @@ class PlaybackViewModel(application: Application) :
|
|||
|
||||
// --- SAVE/RESTORE FUNCTIONS ---
|
||||
|
||||
/** Force save the current [PlaybackStateManager] state to the database. */
|
||||
fun savePlaybackState(onDone: () -> Unit) {
|
||||
/**
|
||||
* Force save the current [PlaybackStateManager] state to the database. [onDone]
|
||||
* will be called with true if it was done, or false if an error occurred.
|
||||
*/
|
||||
fun savePlaybackState(onDone: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
playbackManager.saveState(PlaybackStateDatabase.getInstance(application))
|
||||
onDone()
|
||||
val saved = playbackManager.saveState(PlaybackStateDatabase.getInstance(application))
|
||||
onDone(saved)
|
||||
}
|
||||
}
|
||||
|
||||
/** Wipe the saved playback state (if any). */
|
||||
fun wipePlaybackState(onDone: () -> Unit) {
|
||||
/**
|
||||
* Wipe the saved playback state (if any). [onDone] will be called with true if it was
|
||||
* successfully done, or false if an error occurred.
|
||||
*/
|
||||
fun wipePlaybackState(onDone: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
playbackManager.wipeState(PlaybackStateDatabase.getInstance(application))
|
||||
onDone()
|
||||
val wiped = playbackManager.wipeState(PlaybackStateDatabase.getInstance(application))
|
||||
onDone(wiped)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force restore the last [PlaybackStateManager] saved state, regardless of if a library exists
|
||||
* or not. [onDone] will be called with true if it was successfully done, or false if there was
|
||||
* no state or if a library was not present.
|
||||
* no state, a library was not present, or there was an error.
|
||||
*/
|
||||
fun tryRestorePlaybackState(onDone: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.oxycblt.auxio.music.Song
|
|||
import org.oxycblt.auxio.playback.state.PlaybackStateManager.Callback
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logE
|
||||
import org.oxycblt.auxio.util.logW
|
||||
import kotlin.math.max
|
||||
|
||||
|
@ -368,7 +369,13 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
val library = musicStore.library ?: return false
|
||||
val internalPlayer = internalPlayer ?: return false
|
||||
val state = withContext(Dispatchers.IO) { database.read(library) }
|
||||
val state = try {
|
||||
withContext(Dispatchers.IO) { database.read(library) }
|
||||
} catch (e: Exception) {
|
||||
logE("Unable to restore playback state.")
|
||||
logE(e.stackTraceToString())
|
||||
return false
|
||||
}
|
||||
|
||||
synchronized(this) {
|
||||
if (state != null && (!isInitialized || force)) {
|
||||
|
@ -397,15 +404,32 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
|
||||
/** Save the current state to the [database]. */
|
||||
suspend fun saveState(database: PlaybackStateDatabase) {
|
||||
suspend fun saveState(database: PlaybackStateDatabase): Boolean {
|
||||
logD("Saving state to DB")
|
||||
|
||||
val state = synchronized(this) { makeStateImpl() }
|
||||
withContext(Dispatchers.IO) { database.write(state) }
|
||||
return try {
|
||||
withContext(Dispatchers.IO) { database.write(state) }
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
logE("Unable to save playback state.")
|
||||
logE(e.stackTraceToString())
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun wipeState(database: PlaybackStateDatabase) {
|
||||
/** Wipe the current state. */
|
||||
suspend fun wipeState(database: PlaybackStateDatabase): Boolean {
|
||||
logD("Wiping state")
|
||||
withContext(Dispatchers.IO) { database.write(null) }
|
||||
|
||||
return try {
|
||||
withContext(Dispatchers.IO) { database.write(null) }
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
logE("Unable to wipe playback state.")
|
||||
logE(e.stackTraceToString())
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/** Sanitize the state with [newLibrary]. */
|
||||
|
|
|
@ -113,13 +113,21 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
|
||||
when (preference.key) {
|
||||
context.getString(R.string.set_key_save_state) -> {
|
||||
playbackModel.savePlaybackState {
|
||||
this.context?.showToast(R.string.lbl_state_saved)
|
||||
playbackModel.savePlaybackState { saved ->
|
||||
if (saved) {
|
||||
this.context?.showToast(R.string.lbl_state_saved)
|
||||
} else {
|
||||
this.context?.showToast(R.string.err_did_not_save)
|
||||
}
|
||||
}
|
||||
}
|
||||
context.getString(R.string.set_key_wipe_state) -> {
|
||||
playbackModel.wipePlaybackState {
|
||||
this.context?.showToast(R.string.lbl_state_wiped)
|
||||
playbackModel.wipePlaybackState { wiped ->
|
||||
if (wiped) {
|
||||
this.context?.showToast(R.string.lbl_state_wiped)
|
||||
} else {
|
||||
this.context?.showToast(R.string.err_did_not_wipe)
|
||||
}
|
||||
}
|
||||
}
|
||||
context.getString(R.string.set_key_restore_state) ->
|
||||
|
|
|
@ -251,7 +251,11 @@
|
|||
<string name="err_no_dirs">No folders</string>
|
||||
<string name="err_bad_dir">This folder is not supported</string>
|
||||
<!-- Referring to playback state -->
|
||||
<string name="err_did_not_restore">No state could be restored</string>
|
||||
<string name="err_did_not_restore">Unable to restore state</string>
|
||||
<!-- Referring to playback state -->
|
||||
<string name="err_did_not_wipe">Unable to clear state</string>
|
||||
<!-- Referring to playback state -->
|
||||
<string name="err_did_not_save">Unable to save state</string>
|
||||
|
||||
<!-- Description Namespace | Accessibility Strings -->
|
||||
|
||||
|
|
Loading…
Reference in a new issue