kiwix-apple/SwiftUI/Model/Database.swift
ChrisLi 1baa5fc17b
SwiftUI Apps (#454)
* webview controller

* ZimFilesOpened title

* ZimFilesOpened

* GridBasics

* ZimFilesNew

* GridCommon

* ZimFilesDownloads

* downloads

* refactor

* DownloadTaskCell

* ZimFilesDownloads

* CellBackground

* zim file downloads

* opened iconName

* FlavorTag

* grid sizing

* ZimFileGrid

* app icon

* itunes file sharing

* photo permission

* mac about

* About

* about

* app integration

* LibrarySettings

* Capsule

* library setting

* zim file backup setting

* backup

* ZimFileContextMenu

* background fetch

* update library

* BackgroundTasks

* library refresh

* last refresh

* LanguageSelector

* language

* LanguageSelector table

* LibrarySettings macos

* fetchLanguages

* iOS LanguageSelector

* sorting mode

* library setting

* ZimFilesNew

* SettingSection

* about

* navigationTitle

* background task identifier

* setting

* Settings

* SettingSection

* about macos

* library version

* language filter

* rename

* empty view

* grid

* ZimFileDetail

* ZimFileDetailPanel

* window sizing

* opened zim file bottom

* refresh

* refresh

* page zoom command

* library

* page zoom

* split reader files

* command and focus

* focus and commands

* open file in reader

* frame

* refactoring

* refactor

* languages

* remove env object

* refactor library view model

* delete download task

* downloads database op

* service workers

* service worker warning

* hides service worker files

* SearchFilter

* url / UI

* macos reader title

* LibraryViewModel.reopen

* remove search field

* search focus

* search keyboard shortcut

* open multiple files

* animations

* refactor

* search

* sheet

* SheetView library

* sheet view style

* zimfile list

* reorg

* style

* LanguageSelector

* ios setting

* LibrarySettings

* background task

* delete alert

* move

* open in place, open main page

* library refresh refactor

* open bookmark throws

* zim file missing

* locate file

* ZimFileMissingIndicator

* predicates

* observed zim file

* macos build

* icons

* library refactor

* menu refactor

* refactor

* html parser
2022-07-27 21:22:02 -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.perform {
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)
}
}
}