* Rename cmuxterm to cmux across entire codebase - Rename GitHub repos: manaflow-ai/cmuxterm -> manaflow-ai/cmux, manaflow-ai/homebrew-cmuxterm -> manaflow-ai/homebrew-cmux - Rename bundle IDs: com.cmuxterm.app -> com.cmux.app - Rename CLI: CLI/cmuxterm.swift -> CLI/cmux.swift - Rename homebrew submodule: homebrew-cmuxterm -> homebrew-cmux - Update all socket paths: /tmp/cmuxterm*.sock -> /tmp/cmux*.sock - Update all GitHub URLs, DMG names, Sparkle URLs - Update all source files, scripts, tests, docs, CI workflows * Bump version to 1.23.0
162 lines
5.5 KiB
Swift
162 lines
5.5 KiB
Swift
import Foundation
|
|
import AppKit
|
|
|
|
final class UpdateLogStore {
|
|
static let shared = UpdateLogStore()
|
|
|
|
private let queue = DispatchQueue(label: "cmux.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/cmux-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: "cmux.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/cmux-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)
|
|
}
|
|
}
|