From 64aa7d007b7f3588f9baec4e28c05e126701496e Mon Sep 17 00:00:00 2001 From: dankito Date: Tue, 29 Sep 2020 05:43:08 +0200 Subject: [PATCH] Implemented persisting AppSettings with BankingPersistence --- .../persistence/LuceneBankingPersistence.kt | 2 +- .../banking/persistence/BankingDatabase.kt | 10 +++- .../persistence/RoomBankingPersistence.kt | 37 ++++++++++++++ .../persistence/dao/TanMethodSettingsDao.kt | 13 +++++ .../persistence/model/TanMethodSettings.kt | 21 ++++++++ .../persistence/BankingPersistenceJson.kt | 33 ++++++++++-- .../persistence/BankingPersistenceJsonTest.kt | 4 +- .../persistence/IBankingPersistence.kt | 7 +++ .../persistence/NoOpBankingPersistence.kt | 10 ++++ .../banking/ui/model/settings/AppSettings.kt | 11 ++-- .../ui/model/settings/TanMethodSettings.kt | 5 ++ .../banking/ui/presenter/BankingPresenter.kt | 18 ++++--- .../BankingiOSApp.xcdatamodel/contents | 13 +++++ .../CoreDataBankingPersistence.swift | 43 ++++++++++++++++ .../BankingiOSApp/persistence/Mapper.swift | 50 +++++++++++++++++++ 15 files changed, 257 insertions(+), 20 deletions(-) create mode 100644 persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/dao/TanMethodSettingsDao.kt create mode 100644 persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/model/TanMethodSettings.kt diff --git a/persistence/LuceneBankingPersistence/src/main/kotlin/net/dankito/banking/persistence/LuceneBankingPersistence.kt b/persistence/LuceneBankingPersistence/src/main/kotlin/net/dankito/banking/persistence/LuceneBankingPersistence.kt index b9189937..3063331b 100644 --- a/persistence/LuceneBankingPersistence/src/main/kotlin/net/dankito/banking/persistence/LuceneBankingPersistence.kt +++ b/persistence/LuceneBankingPersistence/src/main/kotlin/net/dankito/banking/persistence/LuceneBankingPersistence.kt @@ -27,7 +27,7 @@ open class LuceneBankingPersistence( protected val indexFolder: File, databaseFolder: File, serializer: ISerializer = JacksonJsonSerializer() -) : BankingPersistenceJson(File(databaseFolder, "accounts.json"), serializer), IBankingPersistence { +) : BankingPersistenceJson(databaseFolder, serializer), IBankingPersistence { companion object { diff --git a/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/BankingDatabase.kt b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/BankingDatabase.kt index c6b29932..4207f244 100644 --- a/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/BankingDatabase.kt +++ b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/BankingDatabase.kt @@ -7,8 +7,11 @@ import net.dankito.banking.persistence.dao.* import net.dankito.banking.persistence.model.* -@Database(entities = [ Bank::class, BankAccount::class, AccountTransaction::class, TanMethod::class, TanMedium::class ], - version = 1, exportSchema = false) +@Database(entities = [ + Bank::class, BankAccount::class, AccountTransaction::class, + TanMethod::class, TanMedium::class, + TanMethodSettings::class +], version = 1, exportSchema = false) @TypeConverters(net.dankito.banking.persistence.TypeConverters::class) abstract class BankingDatabase : RoomDatabase() { @@ -22,4 +25,7 @@ abstract class BankingDatabase : RoomDatabase() { abstract fun tanMediumDao(): TanMediumDao + + abstract fun tanMethodSettingsDao(): TanMethodSettingsDao + } \ No newline at end of file diff --git a/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/RoomBankingPersistence.kt b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/RoomBankingPersistence.kt index 0a4229ac..1d7577bc 100644 --- a/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/RoomBankingPersistence.kt +++ b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/RoomBankingPersistence.kt @@ -10,6 +10,7 @@ import net.dankito.banking.search.TransactionParty import net.dankito.banking.ui.model.IAccountTransaction import net.dankito.banking.ui.model.TypedBankAccount import net.dankito.banking.ui.model.TypedBankData +import net.dankito.banking.ui.model.settings.AppSettings import net.dankito.banking.ui.model.tan.MobilePhoneTanMedium import net.dankito.banking.ui.model.tan.TanGeneratorTanMedium import net.dankito.banking.util.persistence.doSaveUrlToFile @@ -20,6 +21,12 @@ import net.sqlcipher.database.SupportFactory open class RoomBankingPersistence(applicationContext: Context, password: String? = null) : IBankingPersistence, ITransactionPartySearcher { + companion object { + const val FlickerCodeTanMethodSettingsId = 1 + const val QrCodeTanMethodSettingsId = 2 + const val PhotoTanTanMethodSettingsId = 3 + } + protected val db: BankingDatabase init { @@ -149,6 +156,36 @@ open class RoomBankingPersistence(applicationContext: Context, password: String? } + override fun saveOrUpdateAppSettings(appSettings: AppSettings) { + saveOrUpdateTanMethodSettings(appSettings.flickerCodeSettings, FlickerCodeTanMethodSettingsId) + saveOrUpdateTanMethodSettings(appSettings.qrCodeSettings, QrCodeTanMethodSettingsId) + saveOrUpdateTanMethodSettings(appSettings.photoTanSettings, PhotoTanTanMethodSettingsId) + } + + protected open fun saveOrUpdateTanMethodSettings(settings: net.dankito.banking.ui.model.settings.TanMethodSettings?, id: Int) { + settings?.let { + val settingsEntity = TanMethodSettings(id, it.width, it.height, it.space, it.frequency) + + db.tanMethodSettingsDao().saveOrUpdate(settingsEntity) + } + } + + override fun readPersistedAppSettings(): AppSettings? { + val tanMethodSettings = db.tanMethodSettingsDao().getAll() + + val settings = AppSettings() + settings.flickerCodeSettings = findTanMethodSettings(FlickerCodeTanMethodSettingsId, tanMethodSettings) + settings.qrCodeSettings = findTanMethodSettings(QrCodeTanMethodSettingsId, tanMethodSettings) + settings.photoTanSettings = findTanMethodSettings(PhotoTanTanMethodSettingsId, tanMethodSettings) + + return settings + } + + protected open fun findTanMethodSettings(id: Int, settings: List): TanMethodSettings? { + return settings.firstOrNull { it.id == id } + } + + override fun saveUrlToFile(url: String, file: File) { doSaveUrlToFile(url, file) } diff --git a/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/dao/TanMethodSettingsDao.kt b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/dao/TanMethodSettingsDao.kt new file mode 100644 index 00000000..1aaca077 --- /dev/null +++ b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/dao/TanMethodSettingsDao.kt @@ -0,0 +1,13 @@ +package net.dankito.banking.persistence.dao + +import androidx.room.* +import net.dankito.banking.persistence.model.TanMethodSettings + + +@Dao +interface TanMethodSettingsDao : BaseDao { + + @Query("SELECT * FROM TanMethodSettings") + fun getAll(): List + +} \ No newline at end of file diff --git a/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/model/TanMethodSettings.kt b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/model/TanMethodSettings.kt new file mode 100644 index 00000000..40188863 --- /dev/null +++ b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/model/TanMethodSettings.kt @@ -0,0 +1,21 @@ +package net.dankito.banking.persistence.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import net.dankito.banking.persistence.dao.BaseDao +import net.dankito.banking.ui.model.settings.TanMethodSettings + + +@Entity +open class TanMethodSettings( + @PrimaryKey + open var id: Int, + width: Int, + height: Int, + space: Int = -1, + frequency: Int = -1 +) : TanMethodSettings(width, height, space, frequency) { + + internal constructor() : this(BaseDao.IdNotSet.toInt(), 0, 0) // for object deserializers + +} \ No newline at end of file diff --git a/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt index 248aa1a7..ea9c5f5a 100644 --- a/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt +++ b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt @@ -2,18 +2,34 @@ package net.dankito.banking.persistence import net.dankito.banking.persistence.model.BankDataEntity import net.dankito.banking.ui.model.* +import net.dankito.banking.ui.model.settings.AppSettings import net.dankito.utils.multiplatform.File import net.dankito.banking.util.ISerializer import net.dankito.banking.util.persistence.doSaveUrlToFile open class BankingPersistenceJson( - protected val jsonFile: File, + protected val databaseFolder: File, protected val serializer: ISerializer ) : IBankingPersistence { + companion object { + const val BanksJsonFileName = "accounts.json" + + const val AppSettingsJsonFileName = "app_settings.json" + } + + + protected val banksJsonFile: File + + protected val appSettingsJsonFile: File + + init { - jsonFile.absoluteFile.parentFile.mkdirs() + databaseFolder.mkdirs() + + banksJsonFile = File(databaseFolder, BanksJsonFileName) + appSettingsJsonFile = File(databaseFolder, AppSettingsJsonFileName) } @@ -26,7 +42,7 @@ open class BankingPersistenceJson( } override fun readPersistedBanks(): List { - return serializer.deserializeListOr(jsonFile, BankDataEntity::class).map { it as TypedBankData } + return serializer.deserializeListOr(banksJsonFile, BankDataEntity::class).map { it as TypedBankData } } @@ -37,7 +53,16 @@ open class BankingPersistenceJson( protected open fun saveAllBanks(allBanks: List) { - serializer.serializeObject(allBanks, jsonFile) + serializer.serializeObject(allBanks, banksJsonFile) + } + + + override fun saveOrUpdateAppSettings(appSettings: AppSettings) { + serializer.serializeObject(appSettings, appSettingsJsonFile) + } + + override fun readPersistedAppSettings(): AppSettings? { + return serializer.deserializeObject(appSettingsJsonFile, AppSettings::class) } diff --git a/persistence/json/BankingPersistenceJson/src/test/kotlin/net/dankito/banking/persistence/BankingPersistenceJsonTest.kt b/persistence/json/BankingPersistenceJson/src/test/kotlin/net/dankito/banking/persistence/BankingPersistenceJsonTest.kt index 72a1be6c..5b78c323 100644 --- a/persistence/json/BankingPersistenceJson/src/test/kotlin/net/dankito/banking/persistence/BankingPersistenceJsonTest.kt +++ b/persistence/json/BankingPersistenceJson/src/test/kotlin/net/dankito/banking/persistence/BankingPersistenceJsonTest.kt @@ -49,11 +49,11 @@ class BankingPersistenceJsonTest { } - private val file = File(TestDataFolder, "test_accounts.json") + private val file = File(TestDataFolder, BankingPersistenceJson.BanksJsonFileName) private val serializer = JacksonJsonSerializer() - private val underTest = BankingPersistenceJson(file, serializer) + private val underTest = BankingPersistenceJson(TestDataFolder, serializer) @Test diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/persistence/IBankingPersistence.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/persistence/IBankingPersistence.kt index 37bbe5c5..1eeb4352 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/persistence/IBankingPersistence.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/persistence/IBankingPersistence.kt @@ -1,6 +1,7 @@ package net.dankito.banking.persistence import net.dankito.banking.ui.model.* +import net.dankito.banking.ui.model.settings.AppSettings import net.dankito.utils.multiplatform.File @@ -15,6 +16,12 @@ interface IBankingPersistence { fun saveOrUpdateAccountTransactions(account: TypedBankAccount, transactions: List) + + fun saveOrUpdateAppSettings(appSettings: AppSettings) + + fun readPersistedAppSettings(): AppSettings? + + fun saveUrlToFile(url: String, file: File) } \ No newline at end of file diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/persistence/NoOpBankingPersistence.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/persistence/NoOpBankingPersistence.kt index e10d9aed..12e05f3e 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/persistence/NoOpBankingPersistence.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/persistence/NoOpBankingPersistence.kt @@ -1,6 +1,7 @@ package net.dankito.banking.persistence import net.dankito.banking.ui.model.* +import net.dankito.banking.ui.model.settings.AppSettings import net.dankito.utils.multiplatform.File @@ -24,6 +25,15 @@ open class NoOpBankingPersistence : IBankingPersistence { } + override fun saveOrUpdateAppSettings(appSettings: AppSettings) { + + } + + override fun readPersistedAppSettings(): AppSettings? { + return null + } + + override fun saveUrlToFile(url: String, file: File) { } diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/settings/AppSettings.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/settings/AppSettings.kt index 68ab6b99..48d7c8d5 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/settings/AppSettings.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/settings/AppSettings.kt @@ -1,12 +1,17 @@ package net.dankito.banking.ui.model.settings +import net.dankito.utils.multiplatform.UUID + open class AppSettings( - var flickerCodeSettings: TanMethodSettings? = null, - var qrCodeSettings: TanMethodSettings? = null, - var photoTanSettings: TanMethodSettings? = null + open var flickerCodeSettings: TanMethodSettings? = null, + open var qrCodeSettings: TanMethodSettings? = null, + open var photoTanSettings: TanMethodSettings? = null ) { internal constructor() : this(null, null, null) // for object deserializers + + open var technicalId: String = UUID.random() + } \ No newline at end of file diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/settings/TanMethodSettings.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/settings/TanMethodSettings.kt index f183668f..8d3e508d 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/settings/TanMethodSettings.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/settings/TanMethodSettings.kt @@ -1,5 +1,7 @@ package net.dankito.banking.ui.model.settings +import net.dankito.utils.multiplatform.UUID + open class TanMethodSettings( var width: Int, @@ -11,6 +13,9 @@ open class TanMethodSettings( internal constructor() : this(0, 0) // for object deserializers + open var technicalId: String = UUID.random() + + override fun toString(): String { return "Size $width x $height, frequency $frequency" } diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt index e9781d07..904676bb 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt @@ -937,27 +937,29 @@ open class BankingPresenter( } protected open fun persistAppSettings() { + asyncRunner.runAsync { + persistAppSettingsOffUiThread() + } + } + + protected open fun persistAppSettingsOffUiThread() { try { - serializer.serializeObject(appSettings, getAppSettingsFile()) + persister.saveOrUpdateAppSettings(appSettings) } catch (e: Exception) { - log.error(e) { "Could not persist AppSettings to file ${getAppSettingsFile()}" } + log.error(e) { "Could not persist AppSettings" } } } protected open fun readAppSettings() { try { - serializer.deserializeObject(getAppSettingsFile(), AppSettings::class)?.let { + persister.readPersistedAppSettings()?.let { appSettings = it } } catch (e: Exception) { - log.error(e) { "Could not read AppSettings from file ${getAppSettingsFile()}" } + log.error(e) { "Could not read AppSettings" } } } - protected open fun getAppSettingsFile(): File { - return File(dataFolder, "app_settings.json") - } - open fun addBanksChangedListener(listener: (List) -> Unit): Boolean { return banksChangedListeners.add(listener) diff --git a/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents b/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents index c8bc3c1a..5337954e 100644 --- a/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents +++ b/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents @@ -36,6 +36,11 @@ + + + + + @@ -91,11 +96,19 @@ + + + + + + + + \ No newline at end of file diff --git a/ui/BankingiOSApp/BankingiOSApp/persistence/CoreDataBankingPersistence.swift b/ui/BankingiOSApp/BankingiOSApp/persistence/CoreDataBankingPersistence.swift index 9049698e..a0d3d044 100644 --- a/ui/BankingiOSApp/BankingiOSApp/persistence/CoreDataBankingPersistence.swift +++ b/ui/BankingiOSApp/BankingiOSApp/persistence/CoreDataBankingPersistence.swift @@ -104,6 +104,49 @@ class CoreDataBankingPersistence: IBankingPersistence, ITransactionPartySearcher } + func saveOrUpdateAppSettings(appSettings: AppSettings) { + do { + let mapped = mapper.map(appSettings, context) + + if appSettings.technicalId.isCoreDataId == false { // an unpersisted bank (but check should not be necessary) + context.insert(mapped) + } + + try context.save() + + appSettings.technicalId = mapped.objectIDAsString + if let flickerCodeSettingsId = mapped.flickerCodeSettings?.objectIDAsString { + appSettings.flickerCodeSettings?.technicalId = flickerCodeSettingsId + } + if let qrCodeSettingsId = mapped.qrCodeSettings?.objectIDAsString { + appSettings.qrCodeSettings?.technicalId = qrCodeSettingsId + } + if let photoTanSettingsId = mapped.photoTanSettings?.objectIDAsString { + appSettings.photoTanSettings?.technicalId = photoTanSettingsId + } + } catch { + NSLog("Could not save AppSettings \(appSettings): \(error)") + } + } + + func readPersistedAppSettings() -> AppSettings? { + do { + let request: NSFetchRequest = PersistedAppSettings.fetchRequest() + request.returnsObjectsAsFaults = false + + let persistedSettings = try context.fetch(request) + + if let settings = persistedSettings.first { + return mapper.map(settings) + } + } catch { + NSLog("Could not request AppSettings: \(error)") + } + + return nil + } + + func saveUrlToFile(url: String, file: URL) { if let remoteUrl = URL.encoded(url) { if let fileData = try? Data(contentsOf: remoteUrl) { diff --git a/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift b/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift index 8eb9528e..318ef1ba 100644 --- a/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift +++ b/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift @@ -316,6 +316,56 @@ class Mapper { } + func map(_ settings: PersistedAppSettings) -> AppSettings { + let mapped = AppSettings( + flickerCodeSettings: map(settings.flickerCodeSettings), + qrCodeSettings: map(settings.qrCodeSettings), + photoTanSettings: map(settings.photoTanSettings)) + + mapped.technicalId = settings.objectIDAsString + + return mapped + } + + func map(_ settings: AppSettings, _ context: NSManagedObjectContext) -> PersistedAppSettings { + let mapped = context.objectByID(settings.technicalId) ?? PersistedAppSettings(context: context) + + mapped.flickerCodeSettings = map(settings.flickerCodeSettings, context) + mapped.qrCodeSettings = map(settings.qrCodeSettings, context) + mapped.photoTanSettings = map(settings.photoTanSettings, context) + + return mapped + } + + + func map(_ settings: PersistedTanMethodSettings?) -> TanMethodSettings? { + guard let settings = settings else { + return nil + } + + let mapped = TanMethodSettings(width: settings.width, height: settings.height, space: settings.space, frequency: settings.frequency) + + mapped.technicalId = settings.objectIDAsString + + return mapped + } + + func map(_ settings: TanMethodSettings?, _ context: NSManagedObjectContext) -> PersistedTanMethodSettings? { + guard let settings = settings else { + return nil + } + + let mapped = context.objectByID(settings.technicalId) ?? PersistedTanMethodSettings(context: context) + + mapped.width = settings.width + mapped.height = settings.height + mapped.space = settings.space + mapped.frequency = settings.frequency + + return mapped + } + + func map(_ date: Date?) -> CommonDate { if let date = date { return CommonDate(date: date)