mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			293 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			293 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import Foundation
 | |
| import GRDB
 | |
| 
 | |
| // MARK: - Setting
 | |
| 
 | |
| public struct Setting: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
 | |
|     public static var databaseTableName: String { "setting" }
 | |
|     
 | |
|     public typealias Columns = CodingKeys
 | |
|     public enum CodingKeys: String, CodingKey, ColumnExpression {
 | |
|         case key
 | |
|         case value
 | |
|     }
 | |
|     
 | |
|     public var id: String { key }
 | |
|     public var rawValue: Data { value }
 | |
|     
 | |
|     let key: String
 | |
|     let value: Data
 | |
| }
 | |
| 
 | |
| extension Setting {
 | |
|     // MARK: - Numeric Setting
 | |
|     
 | |
|     fileprivate init?<T: Numeric>(key: String, value: T?) {
 | |
|         guard let value: T = value else { return nil }
 | |
|         
 | |
|         var targetValue: T = value
 | |
|         
 | |
|         self.key = key
 | |
|         self.value = Data(bytes: &targetValue, count: MemoryLayout.size(ofValue: targetValue))
 | |
|     }
 | |
|     
 | |
|     fileprivate func value<T: Numeric>(as type: T.Type) -> T? {
 | |
|         // Note: The 'assumingMemoryBound' is essentially going to try to convert
 | |
|         // the memory into the provided type so can result in invalid data being
 | |
|         // returned if the type is incorrect. But it does seem safer than the 'load'
 | |
|         // method which crashed under certain circumstances (an `Int` value of 0)
 | |
|         return value.withUnsafeBytes {
 | |
|             $0.baseAddress?.assumingMemoryBound(to: T.self).pointee
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // MARK: - Bool Setting
 | |
|     
 | |
|     fileprivate init?(key: String, value: Bool?) {
 | |
|         guard let value: Bool = value else { return nil }
 | |
|         
 | |
|         var targetValue: Bool = value
 | |
|         
 | |
|         self.key = key
 | |
|         self.value = Data(bytes: &targetValue, count: MemoryLayout.size(ofValue: targetValue))
 | |
|     }
 | |
|     
 | |
|     public func unsafeValue(as type: Bool.Type) -> Bool? {
 | |
|         // Note: The 'assumingMemoryBound' is essentially going to try to convert
 | |
|         // the memory into the provided type so can result in invalid data being
 | |
|         // returned if the type is incorrect. But it does seem safer than the 'load'
 | |
|         // method which crashed under certain circumstances (an `Int` value of 0)
 | |
|         return value.withUnsafeBytes {
 | |
|             $0.baseAddress?.assumingMemoryBound(to: Bool.self).pointee
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // MARK: - String Setting
 | |
|     
 | |
|     fileprivate init?(key: String, value: String?) {
 | |
|         guard
 | |
|             let value: String = value,
 | |
|             let valueData: Data = value.data(using: .utf8)
 | |
|         else { return nil }
 | |
|         
 | |
|         self.key = key
 | |
|         self.value = valueData
 | |
|     }
 | |
|     
 | |
|     fileprivate func value(as type: String.Type) -> String? {
 | |
|         return String(data: value, encoding: .utf8)
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - Keys
 | |
| 
 | |
| public extension Setting {
 | |
|     struct BoolKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
 | |
|         public let rawValue: String
 | |
|         
 | |
|         public init(_ rawValue: String) { self.rawValue = rawValue }
 | |
|         public init?(rawValue: String) { self.rawValue = rawValue }
 | |
|         public init(stringLiteral value: String) { self.init(value) }
 | |
|         public init(unicodeScalarLiteral value: String) { self.init(value) }
 | |
|         public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
 | |
|     }
 | |
|     
 | |
|     struct DateKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
 | |
|         public let rawValue: String
 | |
|         
 | |
|         public init(_ rawValue: String) { self.rawValue = rawValue }
 | |
|         public init?(rawValue: String) { self.rawValue = rawValue }
 | |
|         public init(stringLiteral value: String) { self.init(value) }
 | |
|         public init(unicodeScalarLiteral value: String) { self.init(value) }
 | |
|         public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
 | |
|     }
 | |
|     
 | |
|     struct DoubleKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
 | |
|         public let rawValue: String
 | |
|         
 | |
|         public init(_ rawValue: String) { self.rawValue = rawValue }
 | |
|         public init?(rawValue: String) { self.rawValue = rawValue }
 | |
|         public init(stringLiteral value: String) { self.init(value) }
 | |
|         public init(unicodeScalarLiteral value: String) { self.init(value) }
 | |
|         public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
 | |
|     }
 | |
|     
 | |
|     struct IntKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
 | |
|         public let rawValue: String
 | |
|         
 | |
|         public init(_ rawValue: String) { self.rawValue = rawValue }
 | |
|         public init?(rawValue: String) { self.rawValue = rawValue }
 | |
|         public init(stringLiteral value: String) { self.init(value) }
 | |
|         public init(unicodeScalarLiteral value: String) { self.init(value) }
 | |
|         public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
 | |
|     }
 | |
|     
 | |
|     struct StringKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
 | |
|         public let rawValue: String
 | |
|         
 | |
|         public init(_ rawValue: String) { self.rawValue = rawValue }
 | |
|         public init?(rawValue: String) { self.rawValue = rawValue }
 | |
|         public init(stringLiteral value: String) { self.init(value) }
 | |
|         public init(unicodeScalarLiteral value: String) { self.init(value) }
 | |
|         public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
 | |
|     }
 | |
|     
 | |
|     struct EnumKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
 | |
|         public let rawValue: String
 | |
|         
 | |
|         public init(_ rawValue: String) { self.rawValue = rawValue }
 | |
|         public init?(rawValue: String) { self.rawValue = rawValue }
 | |
|         public init(stringLiteral value: String) { self.init(value) }
 | |
|         public init(unicodeScalarLiteral value: String) { self.init(value) }
 | |
|         public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
 | |
|     }
 | |
| }
 | |
| 
 | |
| public protocol EnumIntSetting: RawRepresentable where RawValue == Int {}
 | |
| public protocol EnumStringSetting: RawRepresentable where RawValue == String {}
 | |
| 
 | |
| // MARK: - GRDB Interactions
 | |
| 
 | |
| public extension Storage {
 | |
|     subscript(key: Setting.BoolKey) -> Bool {
 | |
|         // Default to false if it doesn't exist
 | |
|         return (read { db in db[key] } ?? false)
 | |
|     }
 | |
|     
 | |
|     subscript(key: Setting.DoubleKey) -> Double? { return read { db in db[key] } }
 | |
|     subscript(key: Setting.IntKey) -> Int? { return read { db in db[key] } }
 | |
|     subscript(key: Setting.StringKey) -> String? { return read { db in db[key] } }
 | |
|     subscript(key: Setting.DateKey) -> Date? { return read { db in db[key] } }
 | |
|     
 | |
|     subscript<T: EnumIntSetting>(key: Setting.EnumKey) -> T? { return read { db in db[key] } }
 | |
|     subscript<T: EnumStringSetting>(key: Setting.EnumKey) -> T? { return read { db in db[key] } }
 | |
| }
 | |
| 
 | |
| public extension Database {
 | |
|     @discardableResult func unsafeSet<T: Numeric>(key: String, value: T?) -> Setting? {
 | |
|         guard let value: T = value else {
 | |
|             _ = try? Setting.filter(id: key).deleteAll(self)
 | |
|             return nil
 | |
|         }
 | |
|         
 | |
|         return try? Setting(key: key, value: value)?.saved(self)
 | |
|     }
 | |
|     
 | |
|     private subscript(key: String) -> Setting? {
 | |
|         get { try? Setting.filter(id: key).fetchOne(self) }
 | |
|         set {
 | |
|             guard let newValue: Setting = newValue else {
 | |
|                 _ = try? Setting.filter(id: key).deleteAll(self)
 | |
|                 return
 | |
|             }
 | |
|             
 | |
|             try? newValue.save(self)
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     subscript(key: Setting.BoolKey) -> Bool {
 | |
|         get {
 | |
|             // Default to false if it doesn't exist
 | |
|             (self[key.rawValue]?.unsafeValue(as: Bool.self) ?? false)
 | |
|         }
 | |
|         set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) }
 | |
|     }
 | |
|     
 | |
|     subscript(key: Setting.DoubleKey) -> Double? {
 | |
|         get { self[key.rawValue]?.value(as: Double.self) }
 | |
|         set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) }
 | |
|     }
 | |
|     
 | |
|     subscript(key: Setting.IntKey) -> Int? {
 | |
|         get { self[key.rawValue]?.value(as: Int.self) }
 | |
|         set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) }
 | |
|     }
 | |
|     
 | |
|     subscript(key: Setting.StringKey) -> String? {
 | |
|         get { self[key.rawValue]?.value(as: String.self) }
 | |
|         set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) }
 | |
|     }
 | |
|     
 | |
|     subscript<T: EnumIntSetting>(key: Setting.EnumKey) -> T? {
 | |
|         get {
 | |
|             guard let rawValue: Int = self[key.rawValue]?.value(as: Int.self) else {
 | |
|                 return nil
 | |
|             }
 | |
|             
 | |
|             return T(rawValue: rawValue)
 | |
|         }
 | |
|         set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue?.rawValue) }
 | |
|     }
 | |
|     
 | |
|     subscript<T: EnumStringSetting>(key: Setting.EnumKey) -> T? {
 | |
|         get {
 | |
|             guard let rawValue: String = self[key.rawValue]?.value(as: String.self) else {
 | |
|                 return nil
 | |
|             }
 | |
|             
 | |
|             return T(rawValue: rawValue)
 | |
|         }
 | |
|         set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue?.rawValue) }
 | |
|     }
 | |
|     
 | |
|     /// Value will be stored as a timestamp in seconds since 1970
 | |
|     subscript(key: Setting.DateKey) -> Date? {
 | |
|         get {
 | |
|             let timestamp: TimeInterval? = self[key.rawValue]?.value(as: TimeInterval.self)
 | |
|             
 | |
|             return timestamp.map { Date(timeIntervalSince1970: $0) }
 | |
|         }
 | |
|         set {
 | |
|             self[key.rawValue] = Setting(
 | |
|                 key: key.rawValue,
 | |
|                 value: newValue.map { $0.timeIntervalSince1970 }
 | |
|             )
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     func setting(key: Setting.BoolKey, to newValue: Bool) -> Setting? {
 | |
|         let result: Setting? = Setting(key: key.rawValue, value: newValue)
 | |
|         self[key.rawValue] = result
 | |
|         return result
 | |
|     }
 | |
|     
 | |
|     func setting(key: Setting.DoubleKey, to newValue: Double?) -> Setting? {
 | |
|         let result: Setting? = Setting(key: key.rawValue, value: newValue)
 | |
|         self[key.rawValue] = result
 | |
|         return result
 | |
|     }
 | |
|     
 | |
|     func setting(key: Setting.IntKey, to newValue: Int?) -> Setting? {
 | |
|         let result: Setting? = Setting(key: key.rawValue, value: newValue)
 | |
|         self[key.rawValue] = result
 | |
|         return result
 | |
|     }
 | |
|     
 | |
|     func setting(key: Setting.StringKey, to newValue: String?) -> Setting? {
 | |
|         let result: Setting? = Setting(key: key.rawValue, value: newValue)
 | |
|         self[key.rawValue] = result
 | |
|         return result
 | |
|     }
 | |
|     
 | |
|     func setting<T: EnumIntSetting>(key: Setting.EnumKey, to newValue: T?) -> Setting? {
 | |
|         let result: Setting? = Setting(key: key.rawValue, value: newValue?.rawValue)
 | |
|         self[key.rawValue] = result
 | |
|         return result
 | |
|     }
 | |
|     
 | |
|     func setting<T: EnumStringSetting>(key: Setting.EnumKey, to newValue: T?) -> Setting? {
 | |
|         let result: Setting? = Setting(key: key.rawValue, value: newValue?.rawValue)
 | |
|         self[key.rawValue] = result
 | |
|         return result
 | |
|     }
 | |
|     
 | |
|     /// Value will be stored as a timestamp in seconds since 1970
 | |
|     func setting(key: Setting.DateKey, to newValue: Date?) -> Setting? {
 | |
|         let result: Setting? = Setting(key: key.rawValue, value: newValue.map { $0.timeIntervalSince1970 })
 | |
|         self[key.rawValue] = result
 | |
|         return result
 | |
|     }
 | |
| }
 |