Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Repository #59

Merged
merged 4 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions MMEX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
9208242A2CA60FD900388AB2 /* FieldContentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920824292CA60FD900388AB2 /* FieldContentData.swift */; };
9208242C2CA615B400388AB2 /* FieldRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9208242B2CA615B400388AB2 /* FieldRepository.swift */; };
9208242E2CA617FB00388AB2 /* FieldContentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9208242D2CA617FB00388AB2 /* FieldContentRepository.swift */; };
920824322CA6E32800388AB2 /* RepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920824312CA6E32800388AB2 /* RepositoryProtocol.swift */; };
929EF65F2C9FF2DE0051A3E6 /* AssetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF65E2C9FF2DE0051A3E6 /* AssetData.swift */; };
929EF6612C9FF2FD0051A3E6 /* StockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF6602C9FF2FD0051A3E6 /* StockData.swift */; };
929EF6632C9FF3ED0051A3E6 /* AssetRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF6622C9FF3ED0051A3E6 /* AssetRepository.swift */; };
Expand Down Expand Up @@ -138,6 +139,7 @@
920824292CA60FD900388AB2 /* FieldContentData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldContentData.swift; sourceTree = "<group>"; };
9208242B2CA615B400388AB2 /* FieldRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldRepository.swift; sourceTree = "<group>"; };
9208242D2CA617FB00388AB2 /* FieldContentRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldContentRepository.swift; sourceTree = "<group>"; };
920824312CA6E32800388AB2 /* RepositoryProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryProtocol.swift; sourceTree = "<group>"; };
929EF65E2C9FF2DE0051A3E6 /* AssetData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetData.swift; sourceTree = "<group>"; };
929EF6602C9FF2FD0051A3E6 /* StockData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StockData.swift; sourceTree = "<group>"; };
929EF6622C9FF3ED0051A3E6 /* AssetRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetRepository.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -343,6 +345,7 @@
isa = PBXGroup;
children = (
929EF6682CA034770051A3E6 /* Repository.swift */,
920824312CA6E32800388AB2 /* RepositoryProtocol.swift */,
A37E7D952C9B219400B4ECFC /* InfotableRepository.swift */,
A3363EEA2C93BF62004696C7 /* CurrencyRepository.swift */,
920823F72CA498B500388AB2 /* CurrencyHistoryRepository.swift */,
Expand Down Expand Up @@ -630,6 +633,7 @@
A3363EE92C9326A1004696C7 /* CategoryListView.swift in Sources */,
A3C142A02C9025D600D3CEC0 /* CategoryData.swift in Sources */,
A3C142A42C9033E300D3CEC0 /* SettingsView.swift in Sources */,
920824322CA6E32800388AB2 /* RepositoryProtocol.swift in Sources */,
A3363EE52C9323D2004696C7 /* CategoryDetailView.swift in Sources */,
929EF66F2CA0BC2D0051A3E6 /* ScheduledRepository.swift in Sources */,
920824182CA4CF4300388AB2 /* AttachmentData.swift in Sources */,
Expand Down
29 changes: 10 additions & 19 deletions MMEX/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,11 @@ struct ContentView: View {
private func handleFileImport(_ result: Result<[URL], Error>) {
switch result {
case .success(let urls):
if let selectedURL = urls.first {
if selectedURL.startAccessingSecurityScopedResource() {
dataManager.openDatabase(at: selectedURL)
UserDefaults.standard.set(selectedURL.path, forKey: "SelectedFilePath")
selectedURL.stopAccessingSecurityScopedResource()
} else {
print("Unable to access file at URL: \(selectedURL)")
}
if let url = urls.first {
dataManager.openDatabase(at: url)
guard dataManager.isDatabaseConnected else { return }
print("Successfully opened database: \(url)")
UserDefaults.standard.set(url.path, forKey: "SelectedFilePath")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
selectedTab = 0
}
Expand All @@ -188,21 +185,15 @@ struct ContentView: View {
private func handleFileExport(_ result: Result<URL, Error>) {
switch result {
case .success(let url):
print("Successfully created new document: \(url)")
if url.startAccessingSecurityScopedResource() {
dataManager.openDatabase(at: url)
UserDefaults.standard.set(url.path, forKey: "SelectedFilePath")
url.stopAccessingSecurityScopedResource()
} else {
print("Unable to access file at URL: \(url)")
}
let repository = dataManager.repository
repository.create(sampleData: isSampleDocument)
dataManager.createDatabase(at: url, sampleData: isSampleDocument)
guard dataManager.isDatabaseConnected else { return }
print("Successfully created database: \(url)")
UserDefaults.standard.set(url.path, forKey: "SelectedFilePath")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
selectedTab = 0
}
case .failure(let error):
print("Failed to create a new document: \(error.localizedDescription)")
print("Failed to pick a document: \(error.localizedDescription)")
}
}
}
Expand Down
159 changes: 90 additions & 69 deletions MMEX/DatabaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,104 +12,125 @@ class DataManager: ObservableObject {
@Published var isDatabaseConnected = false
private(set) var db: Connection?
private(set) var databaseURL: URL?

init() {
connectToStoredDatabase()
}

func openDatabase(at databaseURL: URL) {
self.databaseURL = databaseURL
if databaseURL.startAccessingSecurityScopedResource() {
defer { databaseURL.stopAccessingSecurityScopedResource() }
}

extension DataManager {
func openDatabase(at url: URL) {
if url.startAccessingSecurityScopedResource() {
defer { url.stopAccessingSecurityScopedResource() }
do {
db = try Connection(databaseURL.path)
print("Connected to database: \(databaseURL.path)")
setJournalModeDelete(connection: db!)
isDatabaseConnected = true
db = try Connection(url.path)
print("Successfully connected to database: \(url.path)")
} catch {
print("Failed to connect to database: \(error)")
db = nil
isDatabaseConnected = false
print("Failed to connect to database: \(error)")
}
} else {
db = nil
print("Failed to access security-scoped resource: \(url.path)")
}

if let db {
isDatabaseConnected = true
databaseURL = url
_ = Repository(db).setPragma(name: "journal_mode", value: "MEMORY")
} else {
isDatabaseConnected = false
print("Failed to access security-scoped resource: \(databaseURL.path)")
databaseURL = nil
}
}

func setJournalModeDelete(connection: Connection) {
do {
try connection.execute("PRAGMA journal_mode = MEMORY;")
print("Journal mode set to MEMORY")
} catch {
print("Failed to set journal mode: \(error)")
}
}

/// Method to connect to a previously stored database path if available
private func connectToStoredDatabase() {
if let storedPath = UserDefaults.standard.string(forKey: "SelectedFilePath") {
let storedURL = URL(fileURLWithPath: storedPath)
openDatabase(at: storedURL)
} else {
guard let storedPath = UserDefaults.standard.string(forKey: "SelectedFilePath") else {
print("No stored database path found.")
return
}
let storedURL = URL(fileURLWithPath: storedPath)
openDatabase(at: storedURL)
}

func setTempStoreDirectory(connection: Connection) {
// Get the path to the app's sandbox Caches directory
let fileManager = FileManager.default
if let cacheDir = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first?.path {
let tempStoreSQL = "PRAGMA temp_store_directory = '\(cacheDir)';"

do {
try connection.execute(tempStoreSQL)
print("Temporary store directory set to \(cacheDir)")
} catch {
print("Failed to set temp store directory: \(error)")

func createDatabase(at url: URL, sampleData: Bool) {
openDatabase(at: url)
guard let db else { return }

guard let tables = Bundle.main.url(forResource: "tables.sql", withExtension: "") else {
print("Cannot find tables.sql")
closeDatabase()
return
}

if tables.startAccessingSecurityScopedResource() {
defer { tables.stopAccessingSecurityScopedResource() }
let repository = Repository(db)
repository.setUserVersion (19)
guard repository.execute(url: tables) else {
closeDatabase()
return
}
} else {
print("Failed to access security-scoped resource: \(tables.path)")
closeDatabase()
return
}
}

func getSchemaVersion() -> Int32? {
if let db {
return db.userVersion

if sampleData {
let repository = Repository(db)
guard repository.insertSampleData() else {
print("Failed to create sample database")
closeDatabase()
return
}
}
return nil
}

/// Closes the current database connection and resets related states.
func closeDatabase() {
// Nullify the connection and reset the state
db = nil
isDatabaseConnected = false
db = nil
databaseURL = nil
print("Database connection closed.")
}
}

extension DataManager {
func setTempStoreDirectory(db: Connection) {
// Get the path to the app's sandbox Caches directory
let fileManager = FileManager.default
if let cacheDir = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first?.path {
_ = Repository(db).setPragma(name: "temp_store_directory", value: "'\(cacheDir)'")
}
}
}

var repository : Repository { Repository(db: db) }
var infotableRepository : InfotableRepository { InfotableRepository(db: db) }
var currencyRepository : CurrencyRepository { CurrencyRepository(db: db) }
var currencyHistoryRepository : CurrencyRepository { CurrencyRepository(db: db) }
var accountRepository : AccountRepository { AccountRepository(db: db) }
var assetRepository : AssetRepository { AssetRepository(db: db) }
var stockRepository : StockRepository { StockRepository(db: db) }
var stockHistoryRepository : StockRepository { StockRepository(db: db) }
var categoryRepository : CategoryRepository { CategoryRepository(db: db) }
var payeeRepository : PayeeRepository { PayeeRepository(db: db) }
var transactionRepository : TransactionRepository { TransactionRepository(db: db) }
var transactionSplitRepository : TransactionSplitRepository { TransactionSplitRepository(db: db) }
var transactionLinkRepository : TransactionLinkRepository { TransactionLinkRepository(db: db) }
var transactionShareRepository : TransactionShareRepository { TransactionShareRepository(db: db) }
var scheduledRepository : ScheduledRepository { ScheduledRepository(db: db) }
var scheduledSplitRepository : ScheduledSplitRepository { ScheduledSplitRepository(db: db) }
var tagRepository : TagRepository { TagRepository(db: db) }
var tagLinkRepository : TagLinkRepository { TagLinkRepository(db: db) }
var fieldRepository : FieldRepository { FieldRepository(db: db) }
var fieldContentRepository : FieldContentRepository { FieldContentRepository(db: db) }
var attachmentRepository : AttachmentRepository { AttachmentRepository(db: db) }
var budgetYearRepository : BudgetYearRepository { BudgetYearRepository(db: db) }
var budgetTableRepository : BudgetTableRepository { BudgetTableRepository(db: db) }
var reportRepository : ReportRepository { ReportRepository(db: db) }
extension DataManager {
var repository : Repository? { Repository(db) }
var infotableRepository : InfotableRepository? { InfotableRepository(db) }
var currencyRepository : CurrencyRepository? { CurrencyRepository(db) }
var currencyHistoryRepository : CurrencyRepository? { CurrencyRepository(db) }
var accountRepository : AccountRepository? { AccountRepository(db) }
var assetRepository : AssetRepository? { AssetRepository(db) }
var stockRepository : StockRepository? { StockRepository(db) }
var stockHistoryRepository : StockRepository? { StockRepository(db) }
var categoryRepository : CategoryRepository? { CategoryRepository(db) }
var payeeRepository : PayeeRepository? { PayeeRepository(db) }
var transactionRepository : TransactionRepository? { TransactionRepository(db) }
var transactionSplitRepository : TransactionSplitRepository? { TransactionSplitRepository(db) }
var transactionLinkRepository : TransactionLinkRepository? { TransactionLinkRepository(db) }
var transactionShareRepository : TransactionShareRepository? { TransactionShareRepository(db) }
var scheduledRepository : ScheduledRepository? { ScheduledRepository(db) }
var scheduledSplitRepository : ScheduledSplitRepository? { ScheduledSplitRepository(db) }
var tagRepository : TagRepository? { TagRepository(db) }
var tagLinkRepository : TagLinkRepository? { TagLinkRepository(db) }
var fieldRepository : FieldRepository? { FieldRepository(db) }
var fieldContentRepository : FieldContentRepository? { FieldContentRepository(db) }
var attachmentRepository : AttachmentRepository? { AttachmentRepository(db) }
var budgetYearRepository : BudgetYearRepository? { BudgetYearRepository(db) }
var budgetTableRepository : BudgetTableRepository? { BudgetTableRepository(db) }
var reportRepository : ReportRepository? { ReportRepository(db) }
}
22 changes: 12 additions & 10 deletions MMEX/Repositories/AccountRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
import Foundation
import SQLite

class AccountRepository: RepositoryProtocol {
struct AccountRepository: RepositoryProtocol {
typealias RepositoryData = AccountData

let db: Connection?
init(db: Connection?) {
let db: Connection
init(_ db: Connection) {
self.db = db
}
init?(_ db: Connection?) {
guard let db else { return nil }
self.db = db
}

Expand Down Expand Up @@ -73,7 +77,7 @@ class AccountRepository: RepositoryProtocol {
static let cast_interestRate = cast(col_interestRate) as SQLite.Expression<Double?>
static let cast_minimumPayment = cast(col_minimumPayment) as SQLite.Expression<Double?>

static func selectQuery(from table: SQLite.Table) -> SQLite.Table {
static func selectData(from table: SQLite.Table) -> SQLite.Table {
return table.select(
col_id,
col_name,
Expand All @@ -99,7 +103,7 @@ class AccountRepository: RepositoryProtocol {
)
}

static func selectData(_ row: SQLite.Row) -> AccountData {
static func fetchData(_ row: SQLite.Row) -> AccountData {
return AccountData(
id : row[col_id],
name : row[col_name],
Expand Down Expand Up @@ -173,18 +177,16 @@ extension AccountRepository {
// load all accounts and their currency
func loadWithCurrency() -> [AccountWithCurrency] {
// TODO via join?
guard let db else {return []}

// Create a lookup dictionary for currencies by currencyId
let currencies = CurrencyRepository(db: db).load();
let currencies = CurrencyRepository(db).load();
let currencyDict = Dictionary(uniqueKeysWithValues: currencies.map { ($0.id, $0) })

do {
var data: [AccountWithCurrency] = []
for row in try db.prepare(Self.selectQuery(from: Self.table
for row in try db.prepare(Self.selectData(from: Self.table
.order(Self.col_name)
)) {
let account = Self.selectData(row)
let account = Self.fetchData(row)
data.append(AccountWithCurrency(
data: account,
currency: currencyDict[account.currencyId]
Expand Down
14 changes: 9 additions & 5 deletions MMEX/Repositories/AssetRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
import Foundation
import SQLite

class AssetRepository: RepositoryProtocol {
struct AssetRepository: RepositoryProtocol {
typealias RepositoryData = AssetData

let db: Connection?
init(db: Connection?) {
let db: Connection
init(_ db: Connection) {
self.db = db
}
init?(_ db: Connection?) {
guard let db else { return nil }
self.db = db
}

Expand Down Expand Up @@ -50,7 +54,7 @@ class AssetRepository: RepositoryProtocol {
static let cast_value = cast(col_value) as SQLite.Expression<Double?>
static let cast_changeRate = cast(col_changeRate) as SQLite.Expression<Double?>

static func selectQuery(from table: SQLite.Table) -> SQLite.Table {
static func selectData(from table: SQLite.Table) -> SQLite.Table {
return table.select(
col_id,
col_type,
Expand All @@ -66,7 +70,7 @@ class AssetRepository: RepositoryProtocol {
)
}

static func selectData(_ row: SQLite.Row) -> AssetData {
static func fetchData(_ row: SQLite.Row) -> AssetData {
return AssetData(
id : row[col_id],
type : AssetType(collateNoCase: row[col_type]),
Expand Down
Loading