// // Database.swift // Befund // Created by Irakli Abetschkhrischwili on 21.05.22. // Copyright © 2022 MVZ Dr. Stein und Kollegen. All rights reserved. import Foundation import SQLite3 extension Core { public class Database { /** * Database context of results object */ public class Results { static let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self) static let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) /** * Creates a database if it still not exist */ public static func CreateDBIfNotExists() -> Bool { var result: Bool = false if(!DatabaseExists()) { let con = CreateConnection() if(con != nil) { let sql = """ create table if not exists results( pgs text primary key not null, pat_hash text null, available integer not null default 0, available_ts datetime null, picked_up bit(1) not null default 0, picked_up_ts datetime null, file_checksum text null, status text not null, modified datetime null, created datetime not null ) """ if(sqlite3_exec(con, sql, nil, nil, nil) == SQLITE_OK) { sqlite3_close(con) result = true } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "CreateDBIfNotExists") } } } return result } /** * Inserts a results in the database */ public static func Create(results: Core.Models.Database.Results) -> Bool { var result: Bool = false if(results.pgs != nil) { if(DatabaseExists()) { let con = CreateConnection() if(con != nil) { var cmd: OpaquePointer? = nil let sql = "insert into results (pgs, pat_hash, available, available_ts, picked_up, picked_up_ts, file_checksum, status, created) values(?, ?, ?, ?, ?, ?, ?, ?, ?)" if(sqlite3_prepare_v2(con, sql, -1, &cmd, nil) == SQLITE_OK) { sqlite3_bind_text(cmd, 1, (results.pgs! as NSString).utf8String, -1, SQLITE_TRANSIENT) if(results.pat_hash != nil) { sqlite3_bind_text(cmd, 2, (results.pat_hash! as NSString).utf8String, -1, SQLITE_TRANSIENT) } else { sqlite3_bind_null(cmd, 2) } sqlite3_bind_int(cmd, 3, (results.available ? 1 : 0)) if(results.available_ts != nil) { sqlite3_bind_text(cmd, 4, (results.available_ts! as NSString).utf8String, -1, SQLITE_TRANSIENT) } else { sqlite3_bind_null(cmd, 4) } sqlite3_bind_int(cmd, 5, (results.picked_up ? 1 : 0)) if(results.picked_up_ts != nil) { sqlite3_bind_text(cmd, 6, (results.picked_up_ts! as NSString).utf8String, -1, SQLITE_TRANSIENT) } else { sqlite3_bind_null(cmd, 6) } if(results.file_checksum != nil) { sqlite3_bind_text(cmd, 7, (results.file_checksum! as NSString).utf8String, -1, SQLITE_TRANSIENT) } else { sqlite3_bind_null(cmd, 7) } sqlite3_bind_text(cmd, 8, (results.status! as NSString).utf8String, -1, SQLITE_TRANSIENT) let now = Date() let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let created = dateFormatter.string(from: now) sqlite3_bind_text(cmd, 9, (created as NSString).utf8String, -1, SQLITE_TRANSIENT) if(sqlite3_step(cmd) != SQLITE_DONE) { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "Create(Results)") } sqlite3_finalize(cmd) } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "Create(Results)") } sqlite3_close(con) result = true } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "Create(Results)") } } } return result } /** * Updates a results in the database */ public static func Update(results: Core.Models.Database.Results) -> Bool { var result: Bool = false if(results.pgs != nil) { if(DatabaseExists()) { let con = CreateConnection() if(con != nil) { var cmd: OpaquePointer? = nil let sql = """ update results set pat_hash = ?, available = ?, available_ts = ?, picked_up = ?, picked_up_ts = ?, file_checksum = ?, status = ?, modified = ? where pgs = ? """ if(sqlite3_prepare_v2(con, sql, -1, &cmd, nil) == SQLITE_OK) { if(results.pat_hash != nil) { sqlite3_bind_text(cmd, 1, (results.pat_hash! as NSString).utf8String, -1, SQLITE_TRANSIENT) } else { sqlite3_bind_null(cmd, 1) } sqlite3_bind_int(cmd, 2, (results.available ? 1 : 0)) if(results.available_ts != nil) { sqlite3_bind_text(cmd, 3, (results.available_ts! as NSString).utf8String, -1, SQLITE_TRANSIENT) } else { sqlite3_bind_null(cmd, 3) } sqlite3_bind_int(cmd, 4, (results.picked_up ? 1 : 0)) if(results.picked_up_ts != nil) { sqlite3_bind_text(cmd, 5, (results.picked_up_ts! as NSString).utf8String, -1, SQLITE_TRANSIENT) } else { sqlite3_bind_null(cmd, 5) } if(results.file_checksum != nil) { sqlite3_bind_text(cmd, 6, (results.file_checksum! as NSString).utf8String, -1, SQLITE_TRANSIENT) } else { sqlite3_bind_null(cmd, 6) } sqlite3_bind_text(cmd, 7, (results.status! as NSString).utf8String, -1, SQLITE_TRANSIENT) let now = Date() let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let modified = dateFormatter.string(from: now) sqlite3_bind_text(cmd, 8, (modified as NSString).utf8String, -1, SQLITE_TRANSIENT) sqlite3_bind_text(cmd, 9, (results.pgs! as NSString).utf8String, -1, SQLITE_TRANSIENT) if(sqlite3_step(cmd) != SQLITE_DONE) { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "Update(Results)") } sqlite3_finalize(cmd) } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "Update(Results)") } sqlite3_close(con) result = true } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "Update(Results)") } } } return result } /** * Deletes a results from the databse */ public static func Delete(results: Core.Models.Database.Results) -> Bool { var result: Bool = false if(results.pgs != nil) { if(DatabaseExists()) { let con = CreateConnection() if(con != nil) { var cmd: OpaquePointer? = nil let sql = "delete from results where pgs = ?" if(sqlite3_prepare_v2(con, sql, -1, &cmd, nil) == SQLITE_OK) { sqlite3_bind_text(cmd, 1, (results.pgs! as NSString).utf8String, -1, SQLITE_TRANSIENT) if(sqlite3_step(cmd) != SQLITE_DONE) { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "Delete(Results)") } else { let filePath = Core.System.GetURLForStorageEncryptedFile(filename: results.pgs!) if(filePath != nil && Core.System.FileExists(atPath: filePath!.path)) { result = Core.System.DeleteFile(atPath: filePath!.path) } } sqlite3_finalize(cmd) } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "Delete(Results)") } sqlite3_close(con) result = true } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "Delete(Results)") } } } return result } /** * Creates or update results in the database */ public static func CreateOrUpdate(results: Core.Models.Database.Results) -> Bool { let dbResults = GetResults(pgs: results.pgs!) if(dbResults != nil) { return Update(results: results) } else { return Create(results: results) } } /** * Returns results by pgs */ public static func GetResults(pgs: String) -> Core.Models.Database.Results? { var result: Core.Models.Database.Results? = nil if(DatabaseExists()) { let con = CreateConnection() if(con != nil) { var cmd: OpaquePointer? = nil let sql = """ select pgs, pat_hash, available, available_ts, picked_up, picked_up_ts, file_checksum, status, modified, created from results where pgs = ? """ if(sqlite3_prepare_v2(con, sql, -1, &cmd, nil) == SQLITE_OK) { sqlite3_bind_text(cmd, 1, (pgs as NSString).utf8String, -1, nil) if(sqlite3_step(cmd) == SQLITE_ROW) { result = Core.Models.Database.Results() result!.pgs = String(cString: sqlite3_column_text(cmd, 0)) let pat_hash = sqlite3_column_text(cmd, 1) if(pat_hash != nil) { result!.pat_hash = String(cString: pat_hash!) } result!.available = (sqlite3_column_int(cmd, 2) == 1) let available_ts = sqlite3_column_text(cmd, 3) if(available_ts != nil) { result!.available_ts = String(cString: available_ts!) } result!.picked_up = (sqlite3_column_int(cmd, 4) == 1) let picked_up_ts = sqlite3_column_text(cmd, 5) if(picked_up_ts != nil) { result!.picked_up_ts = String(cString: picked_up_ts!) } let file_checksum = sqlite3_column_text(cmd, 6) if(file_checksum != nil) { result!.file_checksum = String(cString: file_checksum!) } let status = sqlite3_column_text(cmd, 7) if(status != nil) { result!.status = String(cString: status!) } let modified_str = sqlite3_column_text(cmd, 8) if(modified_str != nil) { result!.modified = String(cString: modified_str!) } let created_str = sqlite3_column_text(cmd, 9) if(created_str != nil) { result!.created = String(cString: created_str!) } } sqlite3_finalize(cmd) } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "GetResults(String)") } sqlite3_close(con) } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "GetResults(String)") } } return result } /** * Returns all results from the database */ public static func GetResults(activeOnly: Bool = true) -> Array? { var result: Array? = nil if(DatabaseExists()) { let con = CreateConnection() if(con != nil) { var cmd: OpaquePointer? = nil let sql = """ select pgs, pat_hash, available, available_ts, picked_up, picked_up_ts, file_checksum, status, modified, created from results order by created desc """ if(sqlite3_prepare_v2(con, sql, -1, &cmd, nil) == SQLITE_OK) { result = Array() while(sqlite3_step(cmd) == SQLITE_ROW) { var results = Core.Models.Database.Results() results.pgs = String(cString: sqlite3_column_text(cmd, 0)) let pat_hash = sqlite3_column_text(cmd, 1) if(pat_hash != nil) { results.pat_hash = String(cString: pat_hash!) } results.available = (sqlite3_column_int(cmd, 2) == 1) let available_ts = sqlite3_column_text(cmd, 3) if(available_ts != nil) { results.available_ts = String(cString: available_ts!) } results.picked_up = (sqlite3_column_int(cmd, 4) == 1) let picked_up_ts = sqlite3_column_text(cmd, 5) if(picked_up_ts != nil) { results.picked_up_ts = String(cString: picked_up_ts!) } let file_checksum = sqlite3_column_text(cmd, 6) if(file_checksum != nil) { results.file_checksum = String(cString: file_checksum!) } let status = sqlite3_column_text(cmd, 7) if(status != nil) { results.status = String(cString: status!) } let modified_str = sqlite3_column_text(cmd, 8) if(modified_str != nil) { results.modified = String(cString: modified_str!) } let created_str = sqlite3_column_text(cmd, 9) if(created_str != nil) { results.created = String(cString: created_str!) } // Show only ready results if(activeOnly) { let statusCode = results.GetStatus() if((statusCode == .COMPLETED || statusCode == .EXPIRED) && (results.picked_up || results.available)) { result!.append(results) } } else { result!.append(results) } } sqlite3_finalize(cmd) } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "GetResults") } sqlite3_close(con) } else { let dbError = String(cString: sqlite3_errmsg(con)) Core.Log.Critical(msg: dbError, namespace: "Core.Database.Results", method: "GetResults") } } return result } /** * Check if database exists */ private static func DatabaseExists() -> Bool { let dbPath = Core.System.ResultsDatabasePath() return (dbPath != nil && Core.System.FileExists(atPath: dbPath!)) } /** * Returns database connection pointer */ private static func CreateConnection() -> OpaquePointer? { var result: OpaquePointer? = nil let dbPath = Core.System.ResultsDatabasePath() if(dbPath != nil && sqlite3_open(dbPath!, &result) == SQLITE_OK) { return result } return result } } } }