kiwix-apple/SwiftUI/Model/Database.swift
ChrisLi cfac53f941
iOS 15 + macOS 12 (#461)
* setup

* RootView setup

* open url

* ReadingView

* ReadingView

* webview delegate

* webview outlint

* sidebar

* buttons

* bar buttons

* reader view model

* reader view model

* ReadingView iOS

* NavigationTitleSubtitle

* map

* introspect

* content group

* setting navigation

* WebViewCoordinator

* webview state

* outline

* root view iOS macos

* ios buttons

* OutlineMenu

* OutlineMenu

* outlint tree

* webview gesture

* bookmark button

* merge notification

* BookmarkMultiButton

* ArticleCell

* ArticleCell

* data model

* iOS bookmarks

* bookmark loading

* search

* RandomArticle

* MainArticleMenuButton

* MoreActionMenu

* swiftui4

* BarSetupModifier

* RootView_SwiftUI4

* NavigationItem

* purge & renaming

* bookmarks

* welcome no content

* welcome no content

* file importer

* macos library hookup

* library iOS setup

* Library

* Library setup

* buttons

* library setup

* CategoryList

* predicates

* grid

* settings view

* RootView_SwiftUI4

* RootView

* root view

* RootView_iOS

* ios reading view model

* rootview

* onchange

* ios bars

* reading view iOS 16 button

* bookmark sort

* mvoe

* project setting

* focus

* NavigationItemButtons

* patches

* PageZoomButtons

* FocusedSceneValue modifier

* pagezoom observer

* search view

* SearchView

* dismiss search

* min detail view size

* search result

* SearchView

* resrch

* dismiss search on url change

* search view

* ios search setup

* search

* macos search

* ios search

* remove old search

* use searchbar

* searching

* ios memory leak

* WebViewConfiguration

* macOS webview

* refactor

* SearchBar setup

* search bar setup

* SearchBar setup

* searchbar

* ReadingView

* webview ref

* outline

* sheet

* toolbar title outline

* compact reading view

* settings

* recent search

* reading view

* reading view

* SearchView

* several small changes

* shortcuts

* commands buttons

* macos command

* navigation action

* patch

* revert

* webview retention

* navigation focused scene value

* ipad keyboard shortcut

* macos app min fheight

* ios sheet action

* macOS 13 build

* purge

* move

* move

* indent

* settings

* file import

* rename

* GridCommon

* library views

* purge

* remove LibraryTopic

* LibraryView_iOS

* move

* coredata context

* add url binding

* ZimFileSelection

* pass url down

* ZimFileDetail

* load main page

* revert

* macos 12 compile

* reading view empty title

* view model

* onboarding view

* root view iOS 16

* ios search

* root view

* OutlineTree

* search overlay

* library view

* library refresh views

* view model

* view model

* LibraryViewModel

* zim file detail

* zim file detail styling

* zim file detail

* library detail & refresh

* ZimFileContextMenu

* directory monitor

* LibraryOperations

* LibraryOperations

* reopen

* migration

* zim file migration

* bookmark migration

* comments

* open zim file via bookmark

* welcome view

* hide bookmark section when empty

* bookmark context menu setup

* bookmark add / delete view model

* skip notification

* refactor view modifier

* remove old code

* url loading

* refactor navigation delegate

* safari sheet

* safari view sheet

* iOS font size

* max width welcome view

* onboarding view

* library view

* FileImportButton

* FileImportButton

* dismiss sheet when loading main page

* settings misc

* category auto refresh

* ios app version
2022-09-21 22:14:10 -04:00

112 lines
4.7 KiB
Swift

//
// Database.swift
// Kiwix
//
// Created by Chris Li on 12/23/21.
// Copyright © 2022 Chris Li. All rights reserved.
//
import CoreData
class Database {
static let shared = Database()
private var notificationToken: NSObjectProtocol?
private var token: NSPersistentHistoryToken?
private var tokenURL = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("token.data")
private init() {
notificationToken = NotificationCenter.default.addObserver(
forName: .NSPersistentStoreRemoteChange, object: nil, queue: nil) { notification in
try? self.mergeChanges()
}
token = {
guard let data = UserDefaults.standard.data(forKey: "PersistentHistoryToken") else { return nil }
return try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSPersistentHistoryToken.self, from: data)
}()
}
deinit {
if let token = notificationToken {
NotificationCenter.default.removeObserver(token)
}
}
/// A persistent container to set up the Core Data stack.
lazy var container: NSPersistentContainer = {
/// - Tag: persistentContainer
let container = NSPersistentContainer(name: "DataModel")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
container.loadPersistentStores { storeDescription, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
// This sample refreshes UI by consuming store changes via persistent history tracking.
/// - Tag: viewContextMergeParentChanges
container.viewContext.automaticallyMergesChangesFromParent = false
container.viewContext.name = "viewContext"
/// - Tag: viewContextMergePolicy
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.undoManager = nil
container.viewContext.shouldDeleteInaccessibleFaults = true
return container
}()
/// Save image data to zim files.
func saveImageData(url: URL, completion: @escaping (Data) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let mimeType = response.mimeType,
mimeType.contains("image"),
let data = data else { return }
let context = self.container.newBackgroundContext()
context.perform {
let predicate = NSPredicate(format: "faviconURL == %@", url as CVarArg)
let request = ZimFile.fetchRequest(predicate: predicate)
guard let zimFile = try? context.fetch(request).first else { return }
zimFile.faviconData = data
try? context.save()
}
completion(data)
}.resume()
}
/// Merge changes performed on batch requests to view context.
private func mergeChanges() throws {
let context = container.newBackgroundContext()
context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
context.undoManager = nil
context.perform {
// fetch and merge changes
let fetchRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: self.token)
guard let result = try? context.execute(fetchRequest) as? NSPersistentHistoryResult,
let transactions = result.result as? [NSPersistentHistoryTransaction] else { return }
self.container.viewContext.performAndWait {
transactions.forEach { transaction in
self.container.viewContext.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
self.token = transaction.token
}
}
// update token
guard let token = transactions.last?.token else { return }
let data = try? NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: true)
UserDefaults.standard.set(data, forKey: "PersistentHistoryToken")
// purge history
let sevenDaysAgo = Date(timeIntervalSinceNow: -3600 * 24 * 7)
let purgeRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: sevenDaysAgo)
_ = try? context.execute(purgeRequest)
}
}
}