Skip to content

Commit

Permalink
Merge pull request #73 from moneymanagerex/cache_currency
Browse files Browse the repository at this point in the history
refactor currency cache
  • Loading branch information
georgeef authored Sep 30, 2024
2 parents c5efeee + f37dd88 commit 8840bf6
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 154 deletions.
8 changes: 4 additions & 4 deletions MMEX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
A3C142502C8B366400D3CEC0 /* PayeeListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C1424F2C8B366400D3CEC0 /* PayeeListView.swift */; };
A3C142522C8B37E700D3CEC0 /* PayeeDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C142512C8B37E700D3CEC0 /* PayeeDetailView.swift */; };
A3C142542C8B381400D3CEC0 /* PayeeEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C142532C8B381400D3CEC0 /* PayeeEditView.swift */; };
A3C1425F2C8DE70300D3CEC0 /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C1425E2C8DE70300D3CEC0 /* DatabaseManager.swift */; };
A3C1425F2C8DE70300D3CEC0 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C1425E2C8DE70300D3CEC0 /* DataManager.swift */; };
A3C142612C8E9DBF00D3CEC0 /* PayeeAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C142602C8E9DBF00D3CEC0 /* PayeeAddView.swift */; };
A3C142632C8ED8C000D3CEC0 /* AccountAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C142622C8ED8C000D3CEC0 /* AccountAddView.swift */; };
A3C142652C8ED8EA00D3CEC0 /* AccountEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C142642C8ED8EA00D3CEC0 /* AccountEditView.swift */; };
Expand Down Expand Up @@ -196,7 +196,7 @@
A3C1424F2C8B366400D3CEC0 /* PayeeListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayeeListView.swift; sourceTree = "<group>"; };
A3C142512C8B37E700D3CEC0 /* PayeeDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayeeDetailView.swift; sourceTree = "<group>"; };
A3C142532C8B381400D3CEC0 /* PayeeEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayeeEditView.swift; sourceTree = "<group>"; };
A3C1425E2C8DE70300D3CEC0 /* DatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; };
A3C1425E2C8DE70300D3CEC0 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = "<group>"; };
A3C142602C8E9DBF00D3CEC0 /* PayeeAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayeeAddView.swift; sourceTree = "<group>"; };
A3C142622C8ED8C000D3CEC0 /* AccountAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountAddView.swift; sourceTree = "<group>"; };
A3C142642C8ED8EA00D3CEC0 /* AccountEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountEditView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -418,7 +418,7 @@
A3C142412C897BE600D3CEC0 /* Info.plist */,
A3C1422A2C89751500D3CEC0 /* MMEXApp.swift */,
A3C1422C2C89751500D3CEC0 /* ContentView.swift */,
A3C1425E2C8DE70300D3CEC0 /* DatabaseManager.swift */,
A3C1425E2C8DE70300D3CEC0 /* DataManager.swift */,
929EF6702CA3676B0051A3E6 /* MMEXDocument.swift */,
A3C1422E2C89751600D3CEC0 /* Assets.xcassets */,
920823F22CA3D11F00388AB2 /* Resources */,
Expand Down Expand Up @@ -576,7 +576,7 @@
A3C1423E2C89760600D3CEC0 /* AccountListView.swift in Sources */,
A30061782CA3B68300011B1A /* AssetEditView.swift in Sources */,
A37E7D8C2C9AC36800B4ECFC /* LegalView.swift in Sources */,
A3C1425F2C8DE70300D3CEC0 /* DatabaseManager.swift in Sources */,
A3C1425F2C8DE70300D3CEC0 /* DataManager.swift in Sources */,
A3C1424E2C8B335900D3CEC0 /* PayeeData.swift in Sources */,
929EF6612C9FF2FD0051A3E6 /* StockData.swift in Sources */,
A39B1B322C99A084003E5562 /* CurrencyAddView.swift in Sources */,
Expand Down
35 changes: 26 additions & 9 deletions MMEX/DatabaseManager.swift → MMEX/DataManager.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// DatabaseManager.swift
// DataManager.swift
// MMEX
//
// Created by Lisheng Guan on 2024/9/8.
Expand All @@ -13,7 +13,8 @@ class DataManager: ObservableObject {
private(set) var db: Connection?
private(set) var databaseURL: URL?

var currencyFormat: [Int64: CurrencyFormat] = [:]
var currencyData: [Int64: CurrencyData] = [:]
var currencyFormatter: [Int64: CurrencyFormatter] = [:]

init() {
connectToStoredDatabase()
Expand Down Expand Up @@ -46,7 +47,7 @@ extension DataManager {
}

if !isNew {
loadCurrencyFormat()
loadCurrency()
}
}

Expand Down Expand Up @@ -92,7 +93,7 @@ extension DataManager {
return
}
}
loadCurrencyFormat()
loadCurrency()
}

/// Closes the current database connection and resets related states.
Expand All @@ -101,7 +102,7 @@ extension DataManager {
isDatabaseConnected = false
db = nil
databaseURL = nil
currencyFormat = [:]
closeCurrency()
print("Database connection closed.")
}

Expand All @@ -125,12 +126,28 @@ extension DataManager {
}

extension DataManager {
func loadCurrencyFormat() {
currencyFormat = CurrencyRepository(db)?.dictRefFormat() ?? [:]
func loadCurrency() {
let repository = CurrencyRepository(db)
DispatchQueue.global(qos: .background).async {
let data = repository?.dictUsed() ?? [:]
DispatchQueue.main.async {
self.currencyData = data
self.currencyFormatter = data.mapValues { currency in
currency.formatter
}
}
}

}

func updateCurrency(id: Int64, data: CurrencyData) {
currencyData[id] = data
currencyFormatter[id] = data.formatter
}

func updateCurrencyFormat(id: Int64, value: CurrencyFormat) {
currencyFormat[id] = value
func closeCurrency() {
currencyData = [:]
currencyFormatter = [:]
}
}

Expand Down
61 changes: 27 additions & 34 deletions MMEX/Models/CurrencyData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ enum CurrencyType: String, EnumCollateNoCase {
static let defaultValue = Self.fiat
}

struct CurrencyData: ExportableEntity, CurrencyFormatProtocol {
struct CurrencyData: ExportableEntity {
var id : Int64 = 0
var name : String = ""
var prefixSymbol : String = ""
Expand All @@ -37,42 +37,35 @@ extension CurrencyData: DataProtocol {
}
}

// TODO: remove CurrencyFormat; use CurrencyData instead
protocol CurrencyFormatProtocol {
var name : String { get }
var prefixSymbol : String { get }
var suffixSymbol : String { get }
var decimalPoint : String { get }
var groupSeparator : String { get }
var scale : Int { get }
var baseConvRate : Double { get }
}
typealias CurrencyFormatter = NumberFormatter

struct CurrencyFormat: CurrencyFormatProtocol {
let name : String
let prefixSymbol : String
let suffixSymbol : String
let decimalPoint : String
let groupSeparator : String
let scale : Int
let baseConvRate : Double
extension CurrencyData {
var formatter: CurrencyFormatter {
let nf = NumberFormatter()
nf.numberStyle = .decimal
nf.positivePrefix = self.prefixSymbol
nf.negativePrefix = self.prefixSymbol
nf.positiveSuffix = self.suffixSymbol
nf.negativeSuffix = self.suffixSymbol
nf.decimalSeparator = self.decimalPoint
nf.groupingSeparator = self.groupSeparator
let frac = self.scale > 0 ? Int(log10(Double(self.scale))) : 0
nf.minimumFractionDigits = frac
nf.maximumFractionDigits = frac
return nf
}
}

extension CurrencyFormatProtocol {
var toCurrencyFormat: CurrencyFormat { CurrencyFormat(
name : self.name,
prefixSymbol : self.prefixSymbol,
suffixSymbol : self.suffixSymbol,
decimalPoint : self.decimalPoint,
groupSeparator : self.groupSeparator,
scale : self.scale,
baseConvRate : self.baseConvRate
) }
extension Double {
func formatted(by formatter: CurrencyFormatter? = nil) -> String {
formatter?.string(from: NSNumber(value: self)) ??
String(format: "%.2f", self)
}
}

extension CurrencyFormatProtocol {
extension CurrencyData {
/// A `NumberFormatter` configured specifically for the currency.
var formatter: NumberFormatter {
var formatterOld: NumberFormatter {
let nf = NumberFormatter()
nf.numberStyle = .currency

Expand All @@ -87,9 +80,9 @@ extension CurrencyFormatProtocol {
}

/// Format a given amount using the currency's `NumberFormatter`.
func format(amount: Double) -> String {
func formatOld(amount: Double) -> String {
print("DEBUG: CurrencyFormatProtocol.format: name=\(name), scale=\(scale)")
return switch formatter.string(from: NSNumber(value: amount)) {
return switch formatterOld.string(from: NSNumber(value: amount)) {
case .some(let s): s + self.suffixSymbol
case .none: "\(amount)"
}
Expand All @@ -100,7 +93,7 @@ extension CurrencyFormatProtocol {
func formatAsBaseCurrency(amount: Double, baseCurrencyRate: Double?) -> String {
let baseAmount = amount * (baseCurrencyRate ?? self.baseConvRate)
// TODO: use the formatter of the base currency
return formatter.string(from: NSNumber(value: baseAmount)) ?? "\(baseAmount)"
return formatterOld.string(from: NSNumber(value: baseAmount)) ?? "\(baseAmount)"
}
}

Expand Down
66 changes: 10 additions & 56 deletions MMEX/Repositories/CurrencyRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,31 +104,6 @@ struct CurrencyRepository: RepositoryProtocol {
col_type <- data.type.rawValue
]
}

static func selectFormat(from table: SQLite.Table) -> SQLite.Table {
return table.select(
col_id,
col_name,
col_prefixSymbol,
col_suffixSymbol,
col_decimalPoint,
col_groupSeparator,
col_scale,
cast_baseConvRate
)
}

static func fetchFormat(_ row: SQLite.Row) -> CurrencyFormat {
return CurrencyFormat(
name : row[col_name],
prefixSymbol : row[col_prefixSymbol] ?? "",
suffixSymbol : row[col_suffixSymbol] ?? "",
decimalPoint : row[col_decimalPoint] ?? "",
groupSeparator : row[col_groupSeparator] ?? "",
scale : row[col_scale] ?? 0,
baseConvRate : row[cast_baseConvRate] ?? 0.0
)
}
}

extension CurrencyRepository {
Expand Down Expand Up @@ -160,39 +135,18 @@ extension CurrencyRepository {
}
}

// TODO: re-write in a more readable way (get the ids first, then pluck each currency)
// load all referred currency formats, indexed by currencyId
func dictRefFormat() -> [Int64: CurrencyFormat] {
print("DEBUG: CurrencyRepository.dictRefFormat()")
typealias C = CurrencyRepository
// load used currencies, indexed by id
func dictUsed() -> [Int64: CurrencyData] {
typealias A = AccountRepository
typealias E = AssetRepository
let query = "select" +
" \(C.col_id)," +
" \(C.col_name)," +
" \(C.col_prefixSymbol)," +
" \(C.col_suffixSymbol)," +
" \(C.col_decimalPoint)," +
" \(C.col_groupSeparator)," +
" \(C.col_scale)," +
" \(C.cast_baseConvRate)" +
" from \(C.repositoryName)" +
" where exists (" +
"select 1 from \(A.repositoryName) where \(A.table[A.col_currencyId]) == \(C.table[C.col_id])" +
" union " +
"select 1 from \(E.repositoryName) where \(E.table[E.col_currencyId]) == \(C.table[C.col_id])" +
")"
return Repository(db).dict(
query: query
) { row in CurrencyFormat(
name : row[1] as? String ?? "",
prefixSymbol : row[2] as? String ?? "",
suffixSymbol : row[3] as? String ?? "",
decimalPoint : row[4] as? String ?? "",
groupSeparator : row[5] as? String ?? "",
scale : Int(row[6] as? Int64 ?? 0),
baseConvRate : row[7] as? Double ?? 0.0
) }
let cond = "EXISTS (" + A.table.select(1)
.where(A.table[A.col_currencyId] == Self.table[Self.col_id])
.union(E.table.select(1)
.where(E.table[E.col_currencyId] == Self.table[Self.col_id])
).expression.description + ")"
return dict(from: Self.table
.filter(SQLite.Expression<Bool>(literal: cond))
)
}

// load currency of an account
Expand Down
16 changes: 13 additions & 3 deletions MMEX/Repositories/RepositoryProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ extension RepositoryProtocol {
with result: (SQLite.Row) -> Result = Self.fetchData
) -> Result? {
do {
if let row = try db.pluck(Self.selectData(from: table)) {
let query = Self.selectData(from: table)
print("DEBUG: RepositoryProtocol.pluck(): \(query.expression.description)")
if let row = try db.pluck(query) {
let data = result(row)
print("Successfull pluck of \(key) from \(Self.repositoryName)")
return data
Expand Down Expand Up @@ -59,7 +61,9 @@ extension RepositoryProtocol {
) -> [Result] {
do {
var data: [Result] = []
for row in try db.prepare(Self.selectData(from: table)) {
let query = Self.selectData(from: table)
print("DEBUG: RepositoryProtocol.select(): \(query.expression.description)")
for row in try db.prepare(query) {
data.append(result(row))
}
print("Successfull select from \(Self.repositoryName): \(data.count)")
Expand All @@ -76,7 +80,9 @@ extension RepositoryProtocol {
) -> [Int64: Result] {
do {
var dict: [Int64: Result] = [:]
for row in try db.prepare(Self.selectData(from: table)) {
let query = Self.selectData(from: table)
print("DEBUG: RepositoryProtocol.dict(): \(query.expression.description)")
for row in try db.prepare(query) {
dict[row[Self.col_id]] = result(row)
}
print("Successfull dictionary from \(Self.repositoryName): \(dict.count)")
Expand All @@ -91,6 +97,7 @@ extension RepositoryProtocol {
do {
let query = Self.table
.insert(Self.itemSetters(data))
print("DEBUG: RepositoryProtocol.insert(): \(query.expression.description)")
let rowid = try db.run(query)
data.id = rowid
print("Successfull insert in \(RepositoryData.dataName): \(data.shortDesc())")
Expand All @@ -107,6 +114,7 @@ extension RepositoryProtocol {
let query = Self.table
.filter(Self.col_id == data.id)
.update(Self.itemSetters(data))
print("DEBUG: RepositoryProtocol.update(): \(query.expression.description)")
try db.run(query)
print("Successfull update in \(RepositoryData.dataName): \(data.shortDesc())")
return true
Expand All @@ -122,6 +130,7 @@ extension RepositoryProtocol {
let query = Self.table
.filter(Self.col_id == data.id)
.delete()
print("DEBUG: RepositoryProtocol.delete(): \(query.expression.description)")
try db.run(query)
print("Successfull delete in \(RepositoryData.dataName): \(data.shortDesc())")
return true
Expand All @@ -134,6 +143,7 @@ extension RepositoryProtocol {
func deleteAll() -> Bool {
do {
let query = Self.table.delete()
print("DEBUG: RepositoryProtocol.deleteAll(): \(query.expression.description)")
try db.run(query)
print("Successfull delete all in \(RepositoryData.dataName)")
return true
Expand Down
16 changes: 7 additions & 9 deletions MMEX/Views/Accounts/AccountDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,19 @@ struct AccountDetailView: View {
}
// TODO link to currency details
Section(header: Text("Currency")) {
if let currency = dataManager.currencyFormat[account.currencyId] {
if let currency = dataManager.currencyData[account.currencyId] {
Text(currency.name)
} else {
Text("Loading currency...")
Text("Unknown currency!")
}
}
Section(header: Text("Initial Date")) {
Text(account.initialDate)
}
Section(header: Text("Initial Balance")) {
if let currency = dataManager.currencyFormat[account.currencyId] {
Text(currency.format(amount: account.initialBal))
} else {
Text("\(account.initialBal)")
}
Text(account.initialBal.formatted(
by: dataManager.currencyFormatter[account.currencyId]
))
}
Section(header: Text("Notes")) {
Text(account.notes) // Display notes if they are not nil
Expand Down Expand Up @@ -117,8 +115,8 @@ struct AccountDetailView: View {
guard let repository = dataManager.accountRepository else { return }
if repository.update(account) {
// Successfully updated
if dataManager.currencyFormat[account.currencyId] == nil {
dataManager.loadCurrencyFormat()
if dataManager.currencyData[account.currencyId] == nil {
dataManager.loadCurrency()
}
} else {
// Handle failure
Expand Down
Loading

0 comments on commit 8840bf6

Please sign in to comment.