Store them with the REPL cell and the address insight, as a JSON column each, and bind the decode view to them. Older rows and wire cells without the field decode as none.
128 lines
4.6 KiB
Swift
128 lines
4.6 KiB
Swift
import Foundation
|
|
import GRDB
|
|
|
|
public struct AddressInsight: Codable, Identifiable, Sendable, FetchableRecord, PersistableRecord {
|
|
public static let databaseTableName = "address_insight"
|
|
|
|
public var id: UUID
|
|
public var sessionID: UUID
|
|
public var createdAt: Date
|
|
public var userTitle: String?
|
|
public var kind: Kind
|
|
public var anchor: AddressAnchor
|
|
public var byteCount: Int
|
|
public var placements: [PatternPlacement]
|
|
public var lastResolvedAddress: UInt64?
|
|
public var parentInsightID: UUID?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case sessionID = "session_id"
|
|
case createdAt = "created_at"
|
|
case userTitle = "user_title"
|
|
case kind
|
|
case anchor
|
|
case byteCount = "byte_count"
|
|
case placements
|
|
case lastResolvedAddress = "last_resolved_address"
|
|
case parentInsightID = "parent_insight_id"
|
|
}
|
|
|
|
public init(
|
|
id: UUID = UUID(),
|
|
sessionID: UUID,
|
|
userTitle: String? = nil,
|
|
kind: Kind,
|
|
anchor: AddressAnchor,
|
|
byteCount: Int = 0x200,
|
|
parentInsightID: UUID? = nil
|
|
) {
|
|
self.id = id
|
|
self.sessionID = sessionID
|
|
self.createdAt = Date()
|
|
self.userTitle = userTitle
|
|
self.kind = kind
|
|
self.anchor = anchor
|
|
self.byteCount = byteCount
|
|
self.placements = []
|
|
self.parentInsightID = parentInsightID
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try container.decode(UUID.self, forKey: .id)
|
|
sessionID = try container.decode(UUID.self, forKey: .sessionID)
|
|
createdAt = try container.decode(Date.self, forKey: .createdAt)
|
|
userTitle = try container.decodeIfPresent(String.self, forKey: .userTitle)
|
|
kind = try container.decode(Kind.self, forKey: .kind)
|
|
anchor = try container.decode(AddressAnchor.self, forKey: .anchor)
|
|
byteCount = try container.decode(Int.self, forKey: .byteCount)
|
|
placements = try container.decodeIfPresent([PatternPlacement].self, forKey: .placements) ?? []
|
|
lastResolvedAddress = try container.decodeIfPresent(UInt64.self, forKey: .lastResolvedAddress)
|
|
parentInsightID = try container.decodeIfPresent(UUID.self, forKey: .parentInsightID)
|
|
}
|
|
|
|
public enum Kind: Int, Codable, Sendable, DatabaseValueConvertible {
|
|
case memory
|
|
case disassembly
|
|
}
|
|
|
|
public func encode(to container: inout PersistenceContainer) throws {
|
|
container["id"] = id
|
|
container["session_id"] = sessionID
|
|
container["created_at"] = createdAt
|
|
container["user_title"] = userTitle
|
|
container["kind"] = kind
|
|
container["anchor"] = try Self.blobEncoder.encode(anchor)
|
|
container["byte_count"] = byteCount
|
|
container["placements"] = try Self.blobEncoder.encode(placements)
|
|
container["last_resolved_address"] = lastResolvedAddress.map(Int64.init(bitPattern:))
|
|
container["parent_insight_id"] = parentInsightID
|
|
}
|
|
|
|
public init(row: Row) throws {
|
|
id = row["id"]
|
|
sessionID = row["session_id"]
|
|
createdAt = row["created_at"]
|
|
userTitle = row["user_title"]
|
|
kind = row["kind"]
|
|
anchor = try Self.blobDecoder.decode(AddressAnchor.self, from: row["anchor"])
|
|
byteCount = row["byte_count"]
|
|
let storedPlacements: Data? = row["placements"]
|
|
placements = try storedPlacements.map { try Self.blobDecoder.decode([PatternPlacement].self, from: $0) } ?? []
|
|
let storedAddress: Int64? = row["last_resolved_address"]
|
|
lastResolvedAddress = storedAddress.map(UInt64.init(bitPattern:))
|
|
parentInsightID = row["parent_insight_id"]
|
|
}
|
|
|
|
private static let blobEncoder = JSONEncoder()
|
|
|
|
private static let blobDecoder = JSONDecoder()
|
|
|
|
private static let wireEncoder: JSONEncoder = {
|
|
let e = JSONEncoder()
|
|
e.dateEncodingStrategy = .iso8601
|
|
return e
|
|
}()
|
|
|
|
private static let wireDecoder: JSONDecoder = {
|
|
let d = JSONDecoder()
|
|
d.dateDecodingStrategy = .iso8601
|
|
return d
|
|
}()
|
|
|
|
public func toWireJSON() -> [String: Any]? {
|
|
guard let data = try? Self.wireEncoder.encode(self),
|
|
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
else { return nil }
|
|
return obj
|
|
}
|
|
|
|
public static func fromWireJSON(_ obj: [String: Any]) -> AddressInsight? {
|
|
guard let data = try? JSONSerialization.data(withJSONObject: obj),
|
|
let insight = try? wireDecoder.decode(AddressInsight.self, from: data)
|
|
else { return nil }
|
|
return insight
|
|
}
|
|
}
|