SPB Git

spb/prisme Public MIT

Navigateur iOS intelligent — chaque page comprise localement avant d'être affichée. SwiftUI · WebKit · Foundation Models, 100% on-device.

Swift 96.7% JavaScript 3.3%
3.9 KB · 135 lines swift
Raw Blame History
1//2//  LibraryStore.swift3//  Prisme4//5//  Author: Simon-Pierre Boucher <contact@spboucher.ai>6//78import Foundation9import Observation10import SwiftData1112/// Owns excerpts and structured favourites. Separate store file from the13/// history — each SwiftData container needs its own URL or they fight14/// over the default store.15@MainActor16@Observable17final class LibraryStore {18    private let container: ModelContainer19    /// Bumped on every mutation so observing views refresh their fetches.20    private var revision = 02122    private var context: ModelContext { container.mainContext }2324    init() {25        let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]26        let schema = Schema([Excerpt.self, StructuredFavorite.self])27        do {28            let config = ModelConfiguration(url: base.appendingPathComponent("library.store"))29            container = try ModelContainer(for: schema, configurations: config)30        } catch {31            let memoryOnly = ModelConfiguration(isStoredInMemoryOnly: true)32            container = try! ModelContainer(for: schema, configurations: memoryOnly)33        }34    }3536    // MARK: - Excerpts3738    var excerpts: [Excerpt] {39        _ = revision40        let descriptor = FetchDescriptor<Excerpt>(41            sortBy: [SortDescriptor(\.savedAt, order: .reverse)]42        )43        return (try? context.fetch(descriptor)) ?? []44    }4546    func saveExcerpt(47        _ selection: SelectionExcerpt,48        url: URL?,49        pageTitle: String,50        containerID: UUID51    ) {52        context.insert(Excerpt(53            text: selection.text,54            sourceURLString: url?.absoluteString ?? "",55            sourceTitle: pageTitle,56            sourceHost: url?.host() ?? "",57            sectionTitle: selection.section,58            domPath: selection.domPath,59            containerID: containerID60        ))61        persist()62    }6364    func delete(_ excerpt: Excerpt) {65        context.delete(excerpt)66        persist()67    }6869    // MARK: - Structured favourites7071    var favorites: [StructuredFavorite] {72        _ = revision73        let descriptor = FetchDescriptor<StructuredFavorite>(74            sortBy: [SortDescriptor(\.savedAt, order: .reverse)]75        )76        return (try? context.fetch(descriptor)) ?? []77    }7879    func hasFavorite(for url: URL?) -> Bool {80        guard let urlString = url?.absoluteString else { return false }81        _ = revision82        var descriptor = FetchDescriptor<StructuredFavorite>(83            predicate: #Predicate { $0.urlString == urlString }84        )85        descriptor.fetchLimit = 186        return ((try? context.fetch(descriptor))?.first) != nil87    }8889    func saveFavorite(90        url: URL,91        title: String,92        gist: String,93        kindRaw: String?,94        outlineTitles: [String],95        isGenerated: Bool,96        containerID: UUID97    ) {98        let urlString = url.absoluteString99        var descriptor = FetchDescriptor<StructuredFavorite>(100            predicate: #Predicate { $0.urlString == urlString }101        )102        descriptor.fetchLimit = 1103        if let existing = (try? context.fetch(descriptor))?.first {104            existing.title = title105            existing.gist = gist106            existing.kindRaw = kindRaw107            existing.outlineTitles = outlineTitles108            existing.isGenerated = isGenerated109            existing.savedAt = Date()110        } else {111            context.insert(StructuredFavorite(112                urlString: urlString,113                host: url.host() ?? "",114                title: title,115                gist: gist,116                kindRaw: kindRaw,117                outlineTitles: outlineTitles,118                isGenerated: isGenerated,119                containerID: containerID120            ))121        }122        persist()123    }124125    func delete(_ favorite: StructuredFavorite) {126        context.delete(favorite)127        persist()128    }129130    private func persist() {131        try? context.save()132        revision += 1133    }134}135