Save automatically, improve mocks
This commit is contained in:
@ -2,7 +2,47 @@ import Foundation
|
||||
|
||||
extension Content {
|
||||
|
||||
func saveToDisk() -> Bool {
|
||||
func needsSave() {
|
||||
setModificationTimestamp()
|
||||
|
||||
if saveState == .isSaved {
|
||||
update(saveState: saveState)
|
||||
}
|
||||
// Wait a few seconds for a save, to allow additional changes
|
||||
// Reduces the number of saves
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
|
||||
self.saveIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
func saveIfNeeded() {
|
||||
guard saveState != .isSaved else {
|
||||
return
|
||||
}
|
||||
if Date.now.timeIntervalSince(lastModification) < 5 {
|
||||
// Additional modification made
|
||||
// Wait for next scheduled invocation of saveIfNeeded()
|
||||
// if the overall unsaved time is not too long
|
||||
if Date.now.timeIntervalSince(lastSave) < 30 {
|
||||
//print("Waiting while modifying")
|
||||
return
|
||||
}
|
||||
print("Saving after 30 seconds of modifications")
|
||||
}
|
||||
saveUnconditionally()
|
||||
}
|
||||
|
||||
func saveUnconditionally() {
|
||||
guard saveToDisk() else {
|
||||
update(saveState: .failedToSave)
|
||||
// TODO: Try to save again
|
||||
return
|
||||
}
|
||||
update(saveState: .isSaved)
|
||||
setLastSaveTimestamp()
|
||||
}
|
||||
|
||||
private func saveToDisk() -> Bool {
|
||||
guard didLoadContent else { return false }
|
||||
guard storage.contentScope != nil else {
|
||||
print("Storage not initialized, not saving content")
|
||||
@ -10,12 +50,28 @@ extension Content {
|
||||
}
|
||||
|
||||
var failedSaves = 0
|
||||
failedSaves += pages.count { !storage.save(pageMetadata: $0.data, for: $0.id) }
|
||||
failedSaves += posts.count { !storage.save(post: $0.data, for: $0.id) }
|
||||
failedSaves += tags.count { !storage.save(tagMetadata: $0.data, for: $0.id) }
|
||||
failedSaves.increment(!storage.save(settings: settings.data(tagOverview: tagOverview)))
|
||||
failedSaves += files.count { !storage.save(fileInfo: $0.data, for: $0.id) }
|
||||
var saves = 0
|
||||
let pageSaves = saveChanged(pages)
|
||||
failedSaves += pageSaves.unsaved
|
||||
saves += pageSaves.saved
|
||||
|
||||
let postSaves = saveChanged(posts)
|
||||
failedSaves += postSaves.unsaved
|
||||
saves += postSaves.saved
|
||||
|
||||
let tagSaves = saveChanged(tags)
|
||||
failedSaves += tagSaves.unsaved
|
||||
saves += tagSaves.saved
|
||||
|
||||
failedSaves.increment(!storage.save(settings: settings.data(tagOverview: tagOverview)))
|
||||
|
||||
let fileSaves = saveChanged(files)
|
||||
failedSaves += fileSaves.unsaved
|
||||
saves += fileSaves.saved
|
||||
|
||||
if saves > 0 {
|
||||
print("Saved \(saves) changed items")
|
||||
}
|
||||
if failedSaves > 0 {
|
||||
print("Save partially failed with \(failedSaves) errors")
|
||||
return false
|
||||
@ -39,4 +95,22 @@ extension Content {
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
private func saveChanged<S>(_ items: S) -> (saved: Int, unsaved: Int, unchanged: Int) where S: Sequence, S.Element: StorageItem {
|
||||
var failed = 0
|
||||
var saved = 0
|
||||
var unchanged = 0
|
||||
for item in items {
|
||||
guard let wasSaved = item.saveIfNeeded() else {
|
||||
unchanged += 1
|
||||
continue
|
||||
}
|
||||
if wasSaved {
|
||||
saved += 1
|
||||
} else {
|
||||
failed += 1
|
||||
}
|
||||
}
|
||||
return (saved, failed, unchanged)
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ final class Content: ObservableObject {
|
||||
var storage: Storage
|
||||
|
||||
@Published
|
||||
var settings: Settings
|
||||
var settings: Settings!
|
||||
|
||||
@Published
|
||||
var posts: [Post]
|
||||
@ -31,6 +31,9 @@ final class Content: ObservableObject {
|
||||
@Published
|
||||
var results: GenerationResults
|
||||
|
||||
@Published
|
||||
var storageErrors: [StorageError] = []
|
||||
|
||||
@Published
|
||||
var generationStatus: String = "Ready to generate"
|
||||
|
||||
@ -40,28 +43,12 @@ final class Content: ObservableObject {
|
||||
@Published
|
||||
private(set) var shouldGenerateWebsite = false
|
||||
|
||||
@Published
|
||||
private(set) var saveState: SaveState = .isSaved
|
||||
|
||||
let imageGenerator: ImageGenerator
|
||||
|
||||
init(settings: Settings,
|
||||
posts: [Post],
|
||||
pages: [Page],
|
||||
tags: [Tag],
|
||||
files: [FileResource],
|
||||
tagOverview: Tag?) {
|
||||
self.settings = settings
|
||||
self.posts = posts
|
||||
self.pages = pages
|
||||
self.tags = tags
|
||||
self.files = files
|
||||
self.tagOverview = tagOverview
|
||||
self.results = .init()
|
||||
|
||||
let storage = Storage()
|
||||
self.storage = storage
|
||||
self.imageGenerator = ImageGenerator(
|
||||
storage: storage,
|
||||
settings: settings)
|
||||
}
|
||||
var errorCallback: ((StorageError) -> Void)?
|
||||
|
||||
init() {
|
||||
let settings = Settings.default
|
||||
@ -78,6 +65,10 @@ final class Content: ObservableObject {
|
||||
self.imageGenerator = ImageGenerator(
|
||||
storage: storage,
|
||||
settings: settings)
|
||||
storage.errorNotification = { [weak self] error in
|
||||
self?.storageErrors.append(error)
|
||||
}
|
||||
settings.content = self
|
||||
}
|
||||
|
||||
private func clear() {
|
||||
@ -112,7 +103,7 @@ final class Content: ObservableObject {
|
||||
pages.insert(page, at: 0)
|
||||
}
|
||||
|
||||
func update(contentPath: URL, callback: @escaping ([String]) -> ()) {
|
||||
func update(contentPath: URL, callback: @escaping () -> ()) {
|
||||
guard storage.save(contentPath: contentPath) else {
|
||||
return
|
||||
}
|
||||
@ -139,19 +130,15 @@ final class Content: ObservableObject {
|
||||
files.first { $0.absoluteUrl == withOutputPath }
|
||||
}
|
||||
|
||||
private let errorPrinter = ErrorPrinter()
|
||||
|
||||
func loadFromDisk(callback: @escaping (_ errors: [String]) -> ()) {
|
||||
defer {
|
||||
storage.contentScope?.delegate = errorPrinter
|
||||
}
|
||||
func loadFromDisk(callback: @escaping () -> ()) {
|
||||
DispatchQueue.global().async {
|
||||
let loader = ModelLoader(content: self, storage: self.storage)
|
||||
let result = loader.load()
|
||||
guard result.errors.isEmpty else {
|
||||
DispatchQueue.main.async {
|
||||
self.didLoadContent = false
|
||||
callback(result.errors.sorted())
|
||||
self.storageErrors.append(contentsOf: result.errors)
|
||||
callback()
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -164,7 +151,7 @@ final class Content: ObservableObject {
|
||||
self.settings = result.settings
|
||||
self.tagOverview = result.tagOverview
|
||||
self.didLoadContent = true
|
||||
callback([])
|
||||
callback()
|
||||
self.generateMissingVideoThumbnails()
|
||||
}
|
||||
}
|
||||
@ -183,4 +170,22 @@ final class Content: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Saving
|
||||
|
||||
private(set) var lastSave: Date = .now
|
||||
|
||||
private(set) var lastModification: Date = .now
|
||||
|
||||
func update(saveState: SaveState) {
|
||||
self.saveState = saveState
|
||||
}
|
||||
|
||||
func setModificationTimestamp() {
|
||||
self.lastModification = .now
|
||||
}
|
||||
|
||||
func setLastSaveTimestamp() {
|
||||
self.lastSave = .now
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ extension ContentLabel {
|
||||
self.init(icon: icon, value: data.value)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let icon: String
|
||||
let value: String
|
||||
}
|
||||
|
@ -51,6 +51,8 @@ final class FileResource: Item, LocalizedItem {
|
||||
@Published
|
||||
var fileSize: Int? = nil
|
||||
|
||||
var savedData: Data?
|
||||
|
||||
init(content: Content,
|
||||
id: String,
|
||||
isExternallyStored: Bool,
|
||||
@ -78,7 +80,7 @@ final class FileResource: Item, LocalizedItem {
|
||||
/**
|
||||
Only for bundle images
|
||||
*/
|
||||
init(resourceImage: String, type: FileType) {
|
||||
init(content: Content, resourceImage: String, type: FileType) {
|
||||
self.type = type
|
||||
self.english = "A test image included in the bundle"
|
||||
self.german = "Ein Testbild aus dem Bundle"
|
||||
@ -89,7 +91,7 @@ final class FileResource: Item, LocalizedItem {
|
||||
self.customOutputPath = nil
|
||||
self.addedDate = Date.now
|
||||
self.modifiedDate = Date.now
|
||||
super.init(content: .mock, id: resourceImage) // TODO: Add images to mock
|
||||
super.init(content: content, id: resourceImage)
|
||||
}
|
||||
|
||||
// MARK: Text
|
||||
@ -349,7 +351,7 @@ extension FileResource: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
extension FileResource {
|
||||
extension FileResource: StorageItem {
|
||||
|
||||
convenience init(content: Content, id: String, data: FileResource.Data, isExternalFile: Bool) {
|
||||
self.init(
|
||||
@ -364,6 +366,7 @@ extension FileResource {
|
||||
customOutputPath: data.customOutputPath,
|
||||
addedDate: data.addedDate,
|
||||
modifiedDate: data.modifiedDate)
|
||||
savedData = data
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
@ -379,7 +382,7 @@ extension FileResource {
|
||||
}
|
||||
|
||||
/// This struct holds metadata about a file resource that is stored in the content folder.
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let englishDescription: String?
|
||||
let germanDescription: String?
|
||||
let generatedImages: [String]?
|
||||
@ -389,4 +392,8 @@ extension FileResource {
|
||||
let addedDate: Date
|
||||
let modifiedDate: Date
|
||||
}
|
||||
|
||||
func saveToDisk(_ data: Data) -> Bool {
|
||||
content.storage.save(fileResource: data, for: id)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class Item: ObservableObject, Identifiable {
|
||||
class Item: ObservableContentItem, Identifiable {
|
||||
|
||||
unowned let content: Content
|
||||
|
||||
@ -11,17 +12,25 @@ class Item: ObservableObject, Identifiable {
|
||||
@Published
|
||||
var id: String
|
||||
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(content: Content, id: String) {
|
||||
self.content = content
|
||||
self.id = id
|
||||
|
||||
observeChanges()
|
||||
}
|
||||
|
||||
// MARK: Change observation
|
||||
|
||||
func didChange() {
|
||||
DispatchQueue.main.async {
|
||||
self.changeToggle.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Paths
|
||||
|
||||
func makeCleanAbsolutePath(_ path: String) -> String {
|
||||
"/" + makeCleanRelativePath(path)
|
||||
}
|
||||
|
@ -9,3 +9,7 @@ struct ItemId {
|
||||
extension ItemId: Codable {
|
||||
|
||||
}
|
||||
|
||||
extension ItemId: Equatable {
|
||||
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ final class LinkPreview: ObservableObject {
|
||||
extension LinkPreview {
|
||||
|
||||
/// The object to serialize a link preview for storage
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let title: String?
|
||||
let description: String?
|
||||
let image: String?
|
||||
|
@ -29,7 +29,7 @@ final class LoadingContext {
|
||||
tags: tags.values.sorted(),
|
||||
files: files.values.sorted { $0.id },
|
||||
tagOverview: tagOverview,
|
||||
errors: errors.sorted())
|
||||
errors: errors.sorted().map { StorageError(message: $0) })
|
||||
}
|
||||
|
||||
func error(_ message: String) {
|
||||
|
@ -13,5 +13,5 @@ struct LoadingResult {
|
||||
|
||||
let tagOverview: Tag?
|
||||
|
||||
let errors: [String]
|
||||
let errors: [StorageError]
|
||||
}
|
||||
|
@ -1,17 +1,4 @@
|
||||
|
||||
final class LoadingErrorHandler: SecurityBookmarkErrorDelegate {
|
||||
|
||||
let context: LoadingContext
|
||||
|
||||
init(context: LoadingContext) {
|
||||
self.context = context
|
||||
}
|
||||
|
||||
func securityBookmark(error: String) {
|
||||
context.error("\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
final class ModelLoader {
|
||||
|
||||
let content: Content
|
||||
@ -20,14 +7,10 @@ final class ModelLoader {
|
||||
|
||||
let context: LoadingContext
|
||||
|
||||
let errorHandler: LoadingErrorHandler
|
||||
|
||||
init(content: Content, storage: Storage) {
|
||||
self.content = content
|
||||
self.storage = storage
|
||||
self.context = .init(content: content)
|
||||
self.errorHandler = .init(context: context)
|
||||
storage.contentScope?.delegate = errorHandler
|
||||
}
|
||||
|
||||
func load() -> LoadingResult {
|
||||
|
@ -80,7 +80,7 @@ extension LocalizedPage {
|
||||
}
|
||||
|
||||
/// The structure to store the metadata of a localized page
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let url: String
|
||||
let title: String
|
||||
let linkPreview: LinkPreview.Data
|
||||
|
@ -108,7 +108,7 @@ extension LocalizedPost {
|
||||
}
|
||||
|
||||
/// The structure to store the metadata of a localized post
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let images: [String]
|
||||
let labels: [ContentLabel.Data]?
|
||||
let title: String?
|
||||
|
@ -54,7 +54,7 @@ extension LocalizedTag {
|
||||
originalUrl: data.originalUrl)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let urlComponent: String
|
||||
let name: String
|
||||
let linkPreview: LinkPreview.Data
|
||||
|
@ -44,6 +44,8 @@ final class Page: Item, DateItem, LocalizedItem {
|
||||
@Published
|
||||
var requiredFiles: [FileResource]
|
||||
|
||||
var savedData: Data?
|
||||
|
||||
init(content: Content,
|
||||
id: String,
|
||||
externalLink: String?,
|
||||
@ -186,7 +188,7 @@ final class Page: Item, DateItem, LocalizedItem {
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
extension Page {
|
||||
extension Page: StorageItem {
|
||||
|
||||
convenience init(context: LoadingContext, id: String, data: Data) {
|
||||
self.init(
|
||||
@ -202,10 +204,11 @@ extension Page {
|
||||
english: .init(context: context, data: data.english),
|
||||
tags: data.tags.compactMap(context.tag),
|
||||
requiredFiles: data.requiredFiles?.compactMap(context.file) ?? [])
|
||||
savedData = data
|
||||
}
|
||||
|
||||
/// The structure to store the metadata of a page on disk
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let isDraft: Bool
|
||||
let externalLink: String?
|
||||
let tags: [String]
|
||||
@ -232,4 +235,8 @@ extension Page {
|
||||
english: english.data,
|
||||
requiredFiles: requiredFiles.nonEmpty?.map { $0.id }.sorted())
|
||||
}
|
||||
|
||||
func saveToDisk(_ data: Data) -> Bool {
|
||||
content.storage.save(page: data, for: id)
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,10 @@ final class Post: Item, DateItem, LocalizedItem {
|
||||
super.init(content: content, id: id)
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
var savedData: Data?
|
||||
|
||||
// MARK: Tags
|
||||
|
||||
func usedTags() -> [Tag] {
|
||||
@ -173,7 +177,7 @@ final class Post: Item, DateItem, LocalizedItem {
|
||||
}
|
||||
}
|
||||
|
||||
extension Post {
|
||||
extension Post: StorageItem {
|
||||
|
||||
convenience init(context: LoadingContext, id: String, data: Data) {
|
||||
self.init(
|
||||
@ -187,9 +191,10 @@ extension Post {
|
||||
german: .init(context: context, data: data.german),
|
||||
english: .init(context: context, data: data.english),
|
||||
linkedPage: data.linkedPageId.map(context.page))
|
||||
savedData = data
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let isDraft: Bool
|
||||
let createdDate: Date
|
||||
let startDate: Date
|
||||
@ -211,4 +216,8 @@ extension Post {
|
||||
english: english.data,
|
||||
linkedPageId: linkedPage?.id)
|
||||
}
|
||||
|
||||
func saveToDisk(_ data: Data) -> Bool {
|
||||
content.storage.save(post: data, for: id)
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ extension AudioPlayerSettings {
|
||||
english: english.data)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let playlistCoverImageSize: Int
|
||||
let smallCoverImageSize: Int
|
||||
let audioPlayerJsFile: String?
|
||||
|
@ -34,7 +34,7 @@ extension GeneralSettings {
|
||||
linkPreviewImageHeight: linkPreviewImageHeight)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let url: String
|
||||
let linkPreviewImageWidth: Int
|
||||
let linkPreviewImageHeight: Int
|
||||
|
@ -22,7 +22,7 @@ extension LocalizedAudioPlayerSettings {
|
||||
.init(playlistText: playlistText)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let playlistText: String
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ extension LocalizedNavigationSettings {
|
||||
self.init(rootUrl: data.rootUrl)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let rootUrl: String
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ extension LocalizedPageSettings {
|
||||
emptyPageText: emptyPageText)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let emptyPageTitle: String
|
||||
let emptyPageText: String
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ extension LocalizedPostSettings {
|
||||
linkPreview: linkPreview.data)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let feedUrlPrefix: String
|
||||
let defaultPageLinkText: String
|
||||
let linkPreview: LinkPreview.Data
|
||||
|
@ -32,7 +32,7 @@ extension NavigationSettings {
|
||||
english: LocalizedNavigationSettings(data: data.english))
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let navigationItems: [ItemId]
|
||||
let german: LocalizedNavigationSettings.Data
|
||||
let english: LocalizedNavigationSettings.Data
|
||||
|
@ -104,7 +104,7 @@ extension PageSettings {
|
||||
english: english.data)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let contentWidth: Int
|
||||
let largeImageWidth: Int
|
||||
let pageLinkImageSize: Int
|
||||
|
@ -64,7 +64,7 @@ extension PathSettings {
|
||||
tagsOutputFolderPath: tagsOutputFolderPath)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let assetsOutputFolderPath: String
|
||||
let pagesOutputFolderPath: String
|
||||
let imagesOutputFolderPath: String
|
||||
|
@ -79,7 +79,7 @@ extension PostSettings {
|
||||
english: english.data)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let postsPerPage: Int
|
||||
let contentWidth: Int
|
||||
let swiperCssFile: String?
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
final class Settings: ObservableObject {
|
||||
final class Settings: ChangeObservableItem {
|
||||
|
||||
@Published
|
||||
var general: GeneralSettings
|
||||
@ -21,6 +22,10 @@ final class Settings: ObservableObject {
|
||||
@Published
|
||||
var audioPlayer: AudioPlayerSettings
|
||||
|
||||
weak var content: Content?
|
||||
|
||||
var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
init(general: GeneralSettings,
|
||||
paths: PathSettings,
|
||||
navigation: NavigationSettings,
|
||||
@ -40,6 +45,10 @@ final class Settings: ObservableObject {
|
||||
posts.remove(file)
|
||||
audioPlayer.remove(file)
|
||||
}
|
||||
|
||||
func needsSaving() {
|
||||
content?.needsSave()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
@ -54,6 +63,7 @@ extension Settings {
|
||||
posts: .init(context: context, data: data.posts),
|
||||
pages: .init(context: context, data: data.pages),
|
||||
audioPlayer: .init(context: context, data: data.audioPlayer))
|
||||
content = context.content
|
||||
}
|
||||
|
||||
func data(tagOverview: Tag?) -> Data {
|
||||
@ -67,7 +77,7 @@ extension Settings {
|
||||
tagOverview: tagOverview?.data)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
let general: GeneralSettings.Data
|
||||
let paths: PathSettings.Data
|
||||
let navigation: NavigationSettings.Data
|
||||
@ -76,6 +86,10 @@ extension Settings {
|
||||
let audioPlayer: AudioPlayerSettings.Data
|
||||
let tagOverview: Tag.Data?
|
||||
}
|
||||
|
||||
func saveToDisk(_ data: Data) -> Bool {
|
||||
content?.storage.save(settings: data) ?? false
|
||||
}
|
||||
}
|
||||
|
||||
extension Settings {
|
||||
|
37
CHDataManagement/Model/StorageError.swift
Normal file
37
CHDataManagement/Model/StorageError.swift
Normal file
@ -0,0 +1,37 @@
|
||||
import Foundation
|
||||
|
||||
struct StorageError {
|
||||
|
||||
let date: Date
|
||||
|
||||
let message: String
|
||||
|
||||
init(date: Date = .now, message: String) {
|
||||
self.date = date
|
||||
self.message = message
|
||||
}
|
||||
}
|
||||
|
||||
extension StorageError: Identifiable {
|
||||
|
||||
var id: String {
|
||||
date.description + message
|
||||
}
|
||||
}
|
||||
|
||||
extension StorageError: Comparable {
|
||||
|
||||
static func < (lhs: StorageError, rhs: StorageError) -> Bool {
|
||||
guard lhs.date == rhs.date else {
|
||||
return lhs.date < rhs.date
|
||||
}
|
||||
return lhs.message < rhs.message
|
||||
}
|
||||
}
|
||||
|
||||
extension StorageError: ExpressibleByStringLiteral {
|
||||
|
||||
init(stringLiteral value: StringLiteralType) {
|
||||
self.init(message: value)
|
||||
}
|
||||
}
|
@ -13,6 +13,8 @@ class Tag: Item, LocalizedItem {
|
||||
@Published
|
||||
var english: LocalizedTag
|
||||
|
||||
var savedData: Data?
|
||||
|
||||
override init(content: Content, id: String) {
|
||||
self.isVisible = true
|
||||
self.english = .init(content: content, urlComponent: id, name: id)
|
||||
@ -77,7 +79,7 @@ class Tag: Item, LocalizedItem {
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
extension Tag {
|
||||
extension Tag: StorageItem {
|
||||
|
||||
convenience init(context: LoadingContext, id: String, data: Data) {
|
||||
self.init(
|
||||
@ -86,9 +88,10 @@ extension Tag {
|
||||
isVisible: data.isVisible ?? true,
|
||||
german: .init(context: context, data: data.german),
|
||||
english: .init(context: context, data: data.english))
|
||||
savedData = data
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
struct Data: Codable, Equatable {
|
||||
// Defaults to true if unset
|
||||
let isVisible: Bool?
|
||||
let german: LocalizedTag.Data
|
||||
@ -101,4 +104,8 @@ extension Tag {
|
||||
german: german.data,
|
||||
english: english.data)
|
||||
}
|
||||
|
||||
func saveToDisk(_ data: Data) -> Bool {
|
||||
content.storage.save(tag: data, for: id)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user