ad6ae396da
Bug fixes and code quality improvements: - Fix mirror index dirtied on every sync (MirrorRecord.updatedAt in equality) - Fix mirror URL corruption: encode calendar/source IDs before joining with ';' and use percentEncodedPath to prevent double-encoding - Fix cleanup route mutating UI calendar picker selection unnecessarily - Fix --exit flag redundancy (isCLIRun no longer implies termination) - Remove dead SKIP_ALL_DAY_DEFAULT constant - Replace deprecated FileHandle(forWritingAtPath:) with throwing variant - Add EKEventStoreChanged observer for live calendar list refresh - Extract AppLogStore into its own file (AppLogStore.swift) - Add Block.span(start🔚) factory; replace verbose nil-field constructions - Remove redundant MainActor.run{} wrappers inside @MainActor MirrorEngine - Fix SettingsPayload indentation inside ContentView All 45 unit tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
52 lines
2.5 KiB
Swift
52 lines
2.5 KiB
Swift
import Foundation
|
|
|
|
enum AppLogStore {
|
|
private static let queue = DispatchQueue(label: "BusyMirror.log.store")
|
|
private static let maxLogSizeBytes: UInt64 = 1_000_000
|
|
|
|
static let logDirectoryURL: URL = {
|
|
let base = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first
|
|
?? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library", isDirectory: true)
|
|
return base.appendingPathComponent("Logs/BusyMirror", isDirectory: true)
|
|
}()
|
|
|
|
static let logFileURL = logDirectoryURL.appendingPathComponent("BusyMirror.log", isDirectory: false)
|
|
private static let archivedLogFileURL = logDirectoryURL.appendingPathComponent("BusyMirror.previous.log", isDirectory: false)
|
|
static let launchdStdoutURL = logDirectoryURL.appendingPathComponent("launchd.stdout.log", isDirectory: false)
|
|
static let launchdStderrURL = logDirectoryURL.appendingPathComponent("launchd.stderr.log", isDirectory: false)
|
|
|
|
private static let timestampFormatter: ISO8601DateFormatter = {
|
|
let f = ISO8601DateFormatter()
|
|
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
return f
|
|
}()
|
|
|
|
static func append(_ message: String) {
|
|
let line = "[\(timestampFormatter.string(from: Date()))] \(message)\n"
|
|
queue.async {
|
|
let fm = FileManager.default
|
|
do {
|
|
try fm.createDirectory(at: logDirectoryURL, withIntermediateDirectories: true)
|
|
if let attrs = try? fm.attributesOfItem(atPath: logFileURL.path),
|
|
let size = attrs[.size] as? NSNumber,
|
|
size.uint64Value >= maxLogSizeBytes {
|
|
try? fm.removeItem(at: archivedLogFileURL)
|
|
try? fm.moveItem(at: logFileURL, to: archivedLogFileURL)
|
|
}
|
|
if !fm.fileExists(atPath: logFileURL.path) {
|
|
fm.createFile(atPath: logFileURL.path, contents: nil)
|
|
}
|
|
guard let data = line.data(using: .utf8) else { return }
|
|
// Use the throwing initialiser so we don't silently swallow
|
|
// an inaccessible file — the outer catch handles it.
|
|
let handle = try FileHandle(forWritingTo: logFileURL)
|
|
defer { try? handle.close() }
|
|
try handle.seekToEnd()
|
|
try handle.write(contentsOf: data)
|
|
} catch {
|
|
// Logging must never break the app's main behavior.
|
|
}
|
|
}
|
|
}
|
|
}
|