cmux/Sources/Update/UpdateLogStore.swift
2026-02-08 16:34:08 -08:00

162 lines
5.5 KiB
Swift

import Foundation
import AppKit
final class UpdateLogStore {
static let shared = UpdateLogStore()
private let queue = DispatchQueue(label: "cmuxterm.update.log")
private var entries: [String] = []
private let maxEntries = 200
private let maxFileSize: UInt64 = 256 * 1024 // 256 KB
private let logURL: URL
private let formatter: ISO8601DateFormatter
private init() {
formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let logsDir = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first
?? FileManager.default.temporaryDirectory
logURL = logsDir.appendingPathComponent("Logs/cmuxterm-update.log")
ensureLogFile()
}
func append(_ message: String) {
let timestamp = formatter.string(from: Date())
let line = "[\(timestamp)] \(message)"
queue.async { [weak self] in
guard let self else { return }
entries.append(line)
if entries.count > maxEntries {
entries.removeFirst(entries.count - maxEntries)
}
appendToFile(line: line)
}
}
func snapshot() -> String {
queue.sync {
entries.joined(separator: "\n")
}
}
func logPath() -> String {
logURL.path
}
private func ensureLogFile() {
let directory = logURL.deletingLastPathComponent()
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
if !FileManager.default.fileExists(atPath: logURL.path) {
try? Data().write(to: logURL)
}
}
private func appendToFile(line: String) {
let data = Data((line + "\n").utf8)
if let handle = try? FileHandle(forWritingTo: logURL) {
let fileSize = handle.seekToEndOfFile()
if fileSize > maxFileSize {
try? handle.close()
truncateLogFile()
if let h2 = try? FileHandle(forWritingTo: logURL) {
h2.seekToEndOfFile()
try? h2.write(contentsOf: data)
try? h2.close()
}
} else {
try? handle.write(contentsOf: data)
try? handle.close()
}
} else {
try? data.write(to: logURL, options: .atomic)
}
}
private func truncateLogFile() {
guard let content = try? String(contentsOf: logURL, encoding: .utf8) else { return }
let lines = content.components(separatedBy: "\n")
let keepCount = lines.count / 2
let kept = lines.suffix(keepCount).joined(separator: "\n")
try? kept.write(to: logURL, atomically: true, encoding: .utf8)
}
}
final class FocusLogStore {
static let shared = FocusLogStore()
private let queue = DispatchQueue(label: "cmuxterm.focus.log")
private var entries: [String] = []
private let maxEntries = 400
private let maxFileSize: UInt64 = 256 * 1024 // 256 KB
private let logURL: URL
private let formatter: ISO8601DateFormatter
private init() {
formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let logsDir = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first
?? FileManager.default.temporaryDirectory
logURL = logsDir.appendingPathComponent("Logs/cmuxterm-focus.log")
ensureLogFile()
}
func append(_ message: String) {
let timestamp = formatter.string(from: Date())
let line = "[\(timestamp)] \(message)"
queue.async { [weak self] in
guard let self else { return }
entries.append(line)
if entries.count > maxEntries {
entries.removeFirst(entries.count - maxEntries)
}
appendToFile(line: line)
}
}
func snapshot() -> String {
queue.sync {
entries.joined(separator: "\n")
}
}
func logPath() -> String {
logURL.path
}
private func ensureLogFile() {
let directory = logURL.deletingLastPathComponent()
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
if !FileManager.default.fileExists(atPath: logURL.path) {
try? Data().write(to: logURL)
}
}
private func appendToFile(line: String) {
let data = Data((line + "\n").utf8)
if let handle = try? FileHandle(forWritingTo: logURL) {
let fileSize = handle.seekToEndOfFile()
if fileSize > maxFileSize {
try? handle.close()
truncateLogFile()
if let h2 = try? FileHandle(forWritingTo: logURL) {
h2.seekToEndOfFile()
try? h2.write(contentsOf: data)
try? h2.close()
}
} else {
try? handle.write(contentsOf: data)
try? handle.close()
}
} else {
try? data.write(to: logURL, options: .atomic)
}
}
private func truncateLogFile() {
guard let content = try? String(contentsOf: logURL, encoding: .utf8) else { return }
let lines = content.components(separatedBy: "\n")
let keepCount = lines.count / 2
let kept = lines.suffix(keepCount).joined(separator: "\n")
try? kept.write(to: logURL, atomically: true, encoding: .utf8)
}
}