|
// |
|
// ObjectStorage.swift |
|
// Chet |
|
// |
|
// Created by Soroush Khanlou on 3/8/19. |
|
// Copyright © 2019 Hirsh Group LLC. All rights reserved. |
|
// |
|
|
|
import Foundation |
|
|
|
protocol ObjectStorageProtocol { |
|
|
|
associatedtype Stored |
|
|
|
init(location: StorageLocation) |
|
|
|
func save(object: Stored) |
|
|
|
func fetchObject() -> Stored? |
|
|
|
func deleteObject() |
|
|
|
} |
|
|
|
final class CodableObjectStorage<T: Codable>: ObjectStorageProtocol { |
|
|
|
private let fileManager = FileManager.default |
|
|
|
let storageLocation: StorageLocation |
|
|
|
var filePresenter: NSFilePresenter |
|
|
|
init(location: StorageLocation) { |
|
self.storageLocation = location |
|
self.filePresenter = InnerFilePresenter(presentedItemURL: storageLocation.storageLocation) |
|
} |
|
|
|
func save(object: T) { |
|
guard let storageURL = storageURL else { return } |
|
var error: NSError? |
|
NSFileCoordinator(filePresenter: filePresenter).coordinate(writingItemAt: storageURL, options: .forReplacing, error: &error, byAccessor: { url in |
|
do { |
|
try fileManager.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil) |
|
let encoder = JSONEncoder() |
|
let data = try encoder.encode(object) |
|
try data.write(to: url) |
|
} catch { |
|
print("Error writing the object: \(error)") |
|
} |
|
}) |
|
} |
|
|
|
func fetchObject() -> T? { |
|
var error: NSError? |
|
var result: T? |
|
guard let storageURL = storageURL else { return nil } |
|
NSFileCoordinator(filePresenter: filePresenter).coordinate(readingItemAt: storageURL, options: .withoutChanges, error: &error, byAccessor: { url in |
|
do { |
|
let data = try Data(contentsOf: url) |
|
let decoder = JSONDecoder() |
|
result = try decoder.decode(T.self, from: data) |
|
} catch { |
|
if (error as NSError).code != NSFileReadNoSuchFileError { |
|
print("Error reading the object: \(error)") |
|
} |
|
} |
|
}) |
|
return result |
|
} |
|
|
|
func mutatingObject(default defaultAutoclosure: @autoclosure () -> T, _ block: (inout T) -> Void) { |
|
guard let storageURL = storageURL else { return } |
|
var error: NSError? |
|
NSFileCoordinator(filePresenter: filePresenter).coordinate(writingItemAt: storageURL, options: .forMerging, error: &error, byAccessor: { url in |
|
var result: T |
|
do { |
|
let data = try Data(contentsOf: url) |
|
let decoder = JSONDecoder() |
|
result = try decoder.decode(T.self, from: data) |
|
} catch { |
|
if (error as NSError).code == NSFileReadNoSuchFileError { |
|
result = defaultAutoclosure() |
|
} else { |
|
print("Error fetching for mutation: \(error)") |
|
return |
|
} |
|
} |
|
block(&result) |
|
do { |
|
try fileManager.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil) |
|
let encoder = JSONEncoder() |
|
let data = try encoder.encode(result) |
|
try data.write(to: url) |
|
} catch { |
|
print("Error writing the object for mutation: \(error)") |
|
} |
|
}) |
|
} |
|
|
|
func deleteObject() { |
|
var error: NSError? |
|
guard let storageURL = storageURL else { return } |
|
NSFileCoordinator(filePresenter: filePresenter).coordinate(writingItemAt: storageURL, options: .forDeleting, error: &error, byAccessor: { url in |
|
do { |
|
try fileManager.removeItem(at: url) |
|
} catch { |
|
guard (error as NSError).code != NSFileReadNoSuchFileError else { return } |
|
print("Error deleting object file.") |
|
} |
|
}) |
|
} |
|
|
|
private var storageURL: URL? { |
|
return storageLocation.storageLocation |
|
} |
|
|
|
class InnerFilePresenter: NSObject, NSFilePresenter { |
|
var presentedItemURL: URL? |
|
|
|
var presentedItemOperationQueue: OperationQueue |
|
|
|
init(presentedItemURL: URL?) { |
|
self.presentedItemURL = presentedItemURL |
|
self.presentedItemOperationQueue = OperationQueue() |
|
} |
|
} |
|
} |
|
|
|
final class DisabledObjectStorage<T>: ObjectStorageProtocol { |
|
|
|
init(location: StorageLocation) { |
|
|
|
} |
|
|
|
func save(object: T) { |
|
|
|
} |
|
|
|
func fetchObject() -> T? { |
|
return nil |
|
} |
|
|
|
func deleteObject() { |
|
|
|
} |
|
} |