Skip to content

Commit

Permalink
fix: Add validation and restoration to database import (#100)
Browse files Browse the repository at this point in the history
Move export/import functionality out of application class and distribute it appropriately into Activity and Database.

Add validation for the imported database and revert to a temporary backup if anything fails.

Fixes #13.
  • Loading branch information
matthiasemde committed Jul 14, 2024
1 parent 56ab1ec commit a456195
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 90 deletions.
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ android {
keyAlias = System.getenv("SIGNING_KEY_ALIAS")
keyPassword = System.getenv("SIGNING_KEY_PASSWORD")
}
} catch (_: Exception) {
} catch (e: Exception) {
logger.warn("No signing configuration found, using debug key (message: ${e.message})")
}
}

Expand Down
18 changes: 1 addition & 17 deletions app/src/main/java/app/musikus/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.activity.result.ActivityResultLauncher
import dagger.hilt.android.HiltAndroidApp
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
Expand Down Expand Up @@ -83,30 +82,15 @@ class Musikus : Application() {
IO_EXECUTOR.execute(f)
}

var exportLauncher: ActivityResultLauncher<String>? = null
var importLauncher: ActivityResultLauncher<Array<String>>? = null

var noSessionsYet = true
var serviceIsRunning = false

const val USER_PREFERENCES_NAME = "user_preferences"

fun getRandomQuote(context: Context) : CharSequence {
return context.resources.getTextArray(R.array.quotes).random()
}

fun dp(context: Context, dp: Int): Float {
return context.resources.displayMetrics.density * dp
}


fun importDatabase() {
importLauncher?.launch(arrayOf("*/*"))
}


fun exportDatabase() {
exportLauncher?.launch("musikus_backup")
}
}
}
}
54 changes: 47 additions & 7 deletions app/src/main/java/app/musikus/database/MusikusDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright (c) 2022-2024 Matthias Emde
*
* Parts of this software are licensed under the MIT license
*
* Copyright (c) 2022, Javier Carbone, author Matthias Emde
* Additions and modifications, author Michael Prommersberger
* Copyright (c) 2022-2024 Matthias Emde, Michael Prommersberger
*/

package app.musikus.database

import android.app.Application
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
Expand Down Expand Up @@ -46,7 +40,11 @@ import app.musikus.database.entities.SessionModel
import app.musikus.utils.IdProvider
import app.musikus.utils.TimeProvider
import app.musikus.utils.prepopulateDatabase
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
import java.time.Instant
import java.time.ZoneId
Expand All @@ -55,6 +53,8 @@ import java.time.format.DateTimeFormatter
import java.util.UUID
import javax.inject.Provider

const val MIME_TYPE_DATABASE = "application/octet-stream"

@Database(
version = 4,
entities = [
Expand Down Expand Up @@ -92,6 +92,46 @@ abstract class MusikusDatabase : RoomDatabase() {

lateinit var timeProvider: TimeProvider
lateinit var idProvider: IdProvider
lateinit var databaseFile: File

suspend fun validate(): Boolean {
return try {
val isDatabaseEmpty = listOf(
libraryItemDao.getAllAsFlow().first(),
libraryFolderDao.getAllAsFlow().first(),
goalDescriptionDao.getAllAsFlow().first(),
goalInstanceDao.getAllAsFlow().first(),
sessionDao.getAllAsFlow().first(),
sectionDao.getAllAsFlow().first()
).all {
it.isEmpty()
}
!isDatabaseEmpty
} catch (e: Exception) {
Log.e("Database", "Validation failed: ${e.javaClass.simpleName}: ${e.message}")
false
}
}

/**
* ---------------- Datbase Export/Import ----------------
*/

fun export(outputStream: OutputStream) {
// close the database to collect all logs
close()

// copy database file to output stream
databaseFile.inputStream().copyTo(outputStream)
}

fun import(inputStream: InputStream) {
// delete old database
databaseFile.delete()

// copy new database from input stream
inputStream.copyTo(databaseFile.outputStream())
}

companion object {
const val DATABASE_NAME = "musikus-database"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/app/musikus/datastore/UserPreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import app.musikus.utils.LibraryFolderSortMode
import app.musikus.utils.LibraryItemSortMode
import app.musikus.utils.SortDirection

const val USER_PREFERENCES_NAME = "user_preferences"

interface EnumWithLabel {
val label: String
}
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/app/musikus/di/MainModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStoreFile
import app.musikus.Musikus
import app.musikus.database.MusikusDatabase
import app.musikus.datastore.USER_PREFERENCES_NAME
import app.musikus.utils.IdProvider
import app.musikus.utils.IdProviderImpl
import app.musikus.utils.TimeProvider
Expand Down Expand Up @@ -58,7 +58,7 @@ object MainModule {
emptyPreferences()
},
scope = CoroutineScope(IO + SupervisorJob()),
produceFile = { app.preferencesDataStoreFile(Musikus.USER_PREFERENCES_NAME) }
produceFile = { app.preferencesDataStoreFile(USER_PREFERENCES_NAME) }
)
}

Expand All @@ -82,6 +82,7 @@ object MainModule {
).apply {
this.timeProvider = timeProvider
this.idProvider = idProvider
this.databaseFile = app.getDatabasePath(MusikusDatabase.DATABASE_NAME)
}
}
}
Loading

0 comments on commit a456195

Please sign in to comment.