Files

67 lines
2.8 KiB
Swift

import CryptoKit
import Foundation
public protocol BookChapterRepository: Sendable {
func load(bookURL: String) async throws -> [BookChapter]
func save(_ chapters: [BookChapter], bookURL: String) async throws
func remove(bookURL: String) async throws
}
public actor FileBookChapterRepository: BookChapterRepository {
public static let maximumDocumentBytes = 24 * 1_024 * 1_024
private let directory: URL
public init(directory: URL) { self.directory = directory }
public static func live() -> FileBookChapterRepository {
let directory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
.appendingPathComponent("Legado/Chapters", isDirectory: true)
return FileBookChapterRepository(directory: directory)
}
public func load(bookURL: String) throws -> [BookChapter] {
let url = fileURL(bookURL)
guard FileManager.default.fileExists(atPath: url.path) else { return [] }
let data = try Data(contentsOf: url, options: .mappedIfSafe)
guard data.count <= Self.maximumDocumentBytes else {
try? FileManager.default.removeItem(at: url)
return []
}
return try JSONDecoder().decode([BookChapter].self, from: data).sorted { $0.index < $1.index }
}
public func save(_ chapters: [BookChapter], bookURL: String) throws {
var normalized = chapters
for index in normalized.indices {
normalized[index].bookUrl = bookURL
normalized[index].index = index
}
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
let data = try encoder.encode(normalized)
guard data.count <= Self.maximumDocumentBytes else {
throw CocoaError(.fileWriteOutOfSpace)
}
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
try data.write(to: fileURL(bookURL), options: .atomic)
}
public func remove(bookURL: String) throws {
let url = fileURL(bookURL)
if FileManager.default.fileExists(atPath: url.path) { try FileManager.default.removeItem(at: url) }
}
private func fileURL(_ bookURL: String) -> URL {
let name = SHA256.hash(data: Data(bookURL.utf8)).map { String(format: "%02x", $0) }.joined()
return directory.appendingPathComponent("\(name).json")
}
}
public actor InMemoryBookChapterRepository: BookChapterRepository {
private var values: [String: [BookChapter]]
public init(values: [String: [BookChapter]] = [:]) { self.values = values }
public func load(bookURL: String) -> [BookChapter] { values[bookURL] ?? [] }
public func save(_ chapters: [BookChapter], bookURL: String) { values[bookURL] = chapters }
public func remove(bookURL: String) { values[bookURL] = nil }
}