Release 1.5.1
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>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user