68 lines
3.1 KiB
Swift
68 lines
3.1 KiB
Swift
import Foundation
|
|
|
|
final class SentenceExplanationStore {
|
|
static let shared = SentenceExplanationStore()
|
|
|
|
private let root: URL
|
|
|
|
init() {
|
|
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
root = documents.appendingPathComponent("SentenceExplanations", isDirectory: true)
|
|
try? FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
|
|
}
|
|
|
|
func load(mediaId: String, sentenceIndex: Int) -> String? {
|
|
let url = fileURL(mediaId: mediaId, sentenceIndex: sentenceIndex)
|
|
guard FileManager.default.fileExists(atPath: url.path) else { return nil }
|
|
let text = try? String(contentsOf: url, encoding: .utf8)
|
|
let trimmed = text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
return trimmed.isEmpty ? nil : trimmed
|
|
}
|
|
|
|
@discardableResult
|
|
func save(mediaId: String, sentenceIndex: Int, explanation: String) -> Bool {
|
|
let trimmed = explanation.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { return false }
|
|
let url = fileURL(mediaId: mediaId, sentenceIndex: sentenceIndex)
|
|
try? FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
do {
|
|
try trimmed.write(to: url, atomically: true, encoding: .utf8)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
func sync(mediaId: String, sentences: [SentenceBoundary]) {
|
|
guard !sentences.isEmpty else { return }
|
|
let directory = root.appendingPathComponent(Self.sanitize(mediaId), isDirectory: true)
|
|
let revision = Self.revision(for: sentences)
|
|
let marker = directory.appendingPathComponent("revision.txt")
|
|
let previous = (try? String(contentsOf: marker, encoding: .utf8))?
|
|
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
guard previous != revision else { return }
|
|
try? FileManager.default.removeItem(at: directory)
|
|
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
|
try? revision.write(to: marker, atomically: true, encoding: .utf8)
|
|
}
|
|
|
|
private func fileURL(mediaId: String, sentenceIndex: Int) -> URL {
|
|
let directory = root.appendingPathComponent(Self.sanitize(mediaId), isDirectory: true)
|
|
return directory.appendingPathComponent("\(sentenceIndex).txt")
|
|
}
|
|
|
|
static func revision(for sentences: [SentenceBoundary]) -> String {
|
|
sentences.map { sentence in
|
|
let text = (sentence.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return "\(sentence.index)\t\(text)"
|
|
}.joined(separator: "\n")
|
|
}
|
|
|
|
static func sanitize(_ mediaId: String) -> String {
|
|
let allowed = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "._-"))
|
|
let filtered = mediaId.lowercased().unicodeScalars.map { allowed.contains($0) ? Character($0) : "_" }
|
|
let value = String(filtered).trimmingCharacters(in: CharacterSet(charactersIn: "_"))
|
|
return value.isEmpty || value == "." || value == ".." ? "unknown" : value
|
|
}
|
|
}
|