import Foundation import GRDB import Martin struct RosterVersion: DBStorable { static let databaseTableName = "rosterVersions" var bareJid: String var version: String var id: String { bareJid } } struct Roster: DBStorable { static let databaseTableName = "rosters" var bareJid: String = "" var contactBareJid: String var name: String? var subscription: String var ask: Bool var data: DBRosterData var locallyDeleted: Bool = false var id: String { "\(bareJid)-\(contactBareJid)" } } struct DBRosterData: Codable, DatabaseValueConvertible { let groups: [String] let annotations: [RosterItemAnnotation] public var databaseValue: DatabaseValue { let encoder = JSONEncoder() // swiftlint:disable:next force_try let data = try! encoder.encode(self) return data.databaseValue } public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { guard let data = Data.fromDatabaseValue(dbValue) else { return nil } let decoder = JSONDecoder() // swiftlint:disable:next force_try return try! decoder.decode(Self.self, from: data) } static func == (lhs: DBRosterData, rhs: DBRosterData) -> Bool { lhs.groups == rhs.groups && lhs.annotations == rhs.annotations } } extension RosterItemAnnotation: Equatable { public static func == (lhs: RosterItemAnnotation, rhs: RosterItemAnnotation) -> Bool { lhs.type == rhs.type && lhs.values == rhs.values } } extension Roster: Equatable { static func == (lhs: Roster, rhs: Roster) -> Bool { lhs.bareJid == rhs.bareJid && lhs.contactBareJid == rhs.contactBareJid } } extension Roster { mutating func setLocallyDeleted(_ value: Bool) async throws { locallyDeleted = value let copy = self try? await Database.shared.dbQueue.write { db in try copy.save(db) } } } extension Roster { static var allDeletedLocally: [Roster] { get async { do { let rosters = try await Database.shared.dbQueue.read { db in try Roster .filter(Column("locallyDeleted") == true) .fetchAll(db) } return rosters } catch { return [] } } } static var allActive: [Roster] { get async { do { let rosters = try await Database.shared.dbQueue.read { db in try Roster .filter(Column("locallyDeleted") == false) .fetchAll(db) } return rosters } catch { return [] } } } }