Implement database access (with migrations)

This commit is contained in:
2019-01-26 22:58:53 -06:00
parent e19339d808
commit 7cab0a39e5
67 changed files with 1826 additions and 136 deletions

View File

@@ -1,22 +1,35 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import SQLite3
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var bridge: RCTBridge!
static var backend = Backend()
static var backend = Backend(databaseOpener: IosDatabaseOpener(),
fileOpener: IosFileOpener(),
log: IosLog())
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppDelegate.backend.createHabit(name: "Wake up early")
AppDelegate.backend.createHabit(name: "Wash clothes")
AppDelegate.backend.createHabit(name: "Exercise")
AppDelegate.backend.createHabit(name: "Meditate")
AppDelegate.backend.createHabit(name: "Take vitamins")
AppDelegate.backend.createHabit(name: "Write 'the quick brown fox jumps over the lazy dog' daily")
AppDelegate.backend.createHabit(name: "Write journal")
AppDelegate.backend.createHabit(name: "Study French")
let jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index.ios", fallbackResource: nil)
let rootView = RCTRootView(bundleURL: jsCodeLocation, moduleName: "LoopHabitTracker", initialProperties: nil, launchOptions: launchOptions)
rootView?.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)

View File

@@ -1,3 +1,22 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTBridgeModule.h>

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
extension String: Error {}
@objc(CoreModule)
class CoreModule: RCTEventEmitter {
func convert(_ obj: Any?) -> Any? {
if obj is KotlinInt {
return (obj as! KotlinInt).intValue
}
if obj is NSString {
return obj
}
if obj is Dictionary<String, Any> {
return (obj as! Dictionary<String, Any>).mapValues{ convert($0) }
}
if obj is Array<Any> {
return (obj as! Array<Any>).map { convert($0) }
}
return nil
}
@objc
open override func supportedEvents() -> [String] {
return ["onHabitList"]
}
@objc
func requestHabitList() {
DispatchQueue.main.async {
let result = AppDelegate.backend.getHabitList()
self.sendEvent(withName: "onHabitList", body: self.convert(result))
}
}
@objc
func createHabit(_ name: String) {
DispatchQueue.main.async {
AppDelegate.backend.createHabit(name: name)
}
}
@objc
func deleteHabit(_ id: Int32) {
DispatchQueue.main.async {
AppDelegate.backend.deleteHabit(id: id)
}
}
@objc
func updateHabit(_ id: Int32, _ name: String) {
DispatchQueue.main.async {
AppDelegate.backend.updateHabit(id: id, name: name)
}
}
@objc
override static func requiresMainQueueSetup() -> Bool {
return true
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import SQLite3
internal let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
internal let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
class IosPreparedStatement : NSObject, PreparedStatement {
var db: OpaquePointer
var statement: OpaquePointer
init(withStatement statement: OpaquePointer, withDb db: OpaquePointer) {
self.statement = statement
self.db = db
}
func step() -> StepResult {
let result = sqlite3_step(statement)
if result == SQLITE_ROW {
return StepResult.row
} else if result == SQLITE_DONE {
return StepResult.done
} else {
let errMsg = String(cString: sqlite3_errmsg(db)!)
fatalError("Database error: \(errMsg) (\(result))")
}
}
func getInt(index: Int32) -> Int32 {
return sqlite3_column_int(statement, index)
}
func getText(index: Int32) -> String {
return String(cString: sqlite3_column_text(statement, index))
}
func bindInt(index: Int32, value: Int32) {
sqlite3_bind_int(statement, index, value)
}
func bindText(index: Int32, value: String) {
sqlite3_bind_text(statement, index, value, -1, SQLITE_TRANSIENT)
}
func reset() {
sqlite3_reset(statement)
}
override func finalize() {
sqlite3_finalize(statement)
}
}
class IosDatabase : NSObject, Database {
var db: OpaquePointer
init(withDb db: OpaquePointer) {
self.db = db
}
func prepareStatement(sql: String) -> PreparedStatement {
if sql.isEmpty {
fatalError("Provided SQL query is empty")
}
print("Running SQL: \(sql)")
var statement : OpaquePointer?
let result = sqlite3_prepare_v2(db, sql, -1, &statement, nil)
if result == SQLITE_OK {
return IosPreparedStatement(withStatement: statement!, withDb: db)
} else {
let errMsg = String(cString: sqlite3_errmsg(db)!)
fatalError("Database error: \(errMsg)")
}
}
func close() {
sqlite3_close(db)
}
}
class IosDatabaseOpener : NSObject, DatabaseOpener {
func open(file: UserFile) -> Database {
let dbPath = (file as! IosUserFile).path
let version = String(cString: sqlite3_libversion())
print("SQLite \(version)")
print("Opening database: \(dbPath)")
var db: OpaquePointer?
let result = sqlite3_open(dbPath, &db)
if result == SQLITE_OK {
return IosDatabase(withDb: db!)
} else {
fatalError("Error opening database (code \(result))")
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
class IosResourceFile : NSObject, ResourceFile {
var path: String
var fileManager = FileManager.default
init(forPath path: String) {
self.path = path
}
func readLines() -> [String] {
do {
let contents = try String(contentsOfFile: self.path, encoding: .utf8)
return contents.components(separatedBy: CharacterSet.newlines)
} catch {
return [""]
}
}
}
class IosUserFile : NSObject, UserFile {
var path: String
init(forPath path: String) {
self.path = path
}
func delete() {
do {
try FileManager.default.removeItem(atPath: path)
} catch {
}
}
func exists() -> Bool {
return FileManager.default.fileExists(atPath: path)
}
}
class IosFileOpener : NSObject, FileOpener {
func openResourceFile(filename: String) -> ResourceFile {
let path = "\(Bundle.main.resourcePath!)/\(filename)"
return IosResourceFile(forPath: path)
}
func openUserFile(filename: String) -> UserFile {
do {
let root = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path
return IosUserFile(forPath: "\(root)/\(filename)")
} catch {
return IosUserFile(forPath: "invalid")
}
}
}

30
ios/uhabits/IosLog.swift Normal file
View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
class IosLog : NSObject, Log {
func info(msg: String) {
print("[I] \(msg)")
}
func debug(msg: String) {
print("[D] \(msg)")
}
}