Files
luma/Sources/LumaCore/Models/AddressInsight.swift
Ole André Vadla Ravnås 3f70d259d3 core: Persist pattern placements
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.
2026-10-01 20:49:11 +02:00

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
}
}