From 2d09a405de3e2008c7b305735950419e18beed2f Mon Sep 17 00:00:00 2001 From: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com> Date: Sun, 27 Sep 2026 13:46:33 -0600 Subject: [PATCH] fix: limit idevice log file size --- StikDebug/App/AppBootstrapper.swift | 1 + StikDebug/Device/JITEnableContext.swift | 6 +- .../Services/IdeviceLogFileManager.swift | 66 +++++++++++++++++++ StikDebug/Views/ConsoleLogsView.swift | 6 +- 4 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 StikDebug/Services/IdeviceLogFileManager.swift diff --git a/StikDebug/App/AppBootstrapper.swift b/StikDebug/App/AppBootstrapper.swift index ffb94ac..67b88ba 100644 --- a/StikDebug/App/AppBootstrapper.swift +++ b/StikDebug/App/AppBootstrapper.swift @@ -9,6 +9,7 @@ import UIKit enum AppBootstrapper { static func configure() { + IdeviceLogFileManager.shared.prepareForLogging() registerDefaultSettings() startConfiguredKeepAliveServices() applyDocumentPickerCopyWorkaround() diff --git a/StikDebug/Device/JITEnableContext.swift b/StikDebug/Device/JITEnableContext.swift index b217279..9a37cf2 100644 --- a/StikDebug/Device/JITEnableContext.swift +++ b/StikDebug/Device/JITEnableContext.swift @@ -82,11 +82,7 @@ final class JITEnableContext { var handshakeHandle: OpaquePointer? { handshake } private init() { - let logURL = FileManager.default - .urls(for: .documentDirectory, in: .userDomainMask)[0] - .appendingPathComponent("idevice_log.txt") - - var path = Array(logURL.path.utf8CString) + var path = Array(IdeviceLogFileManager.logURL.path.utf8CString) path.withUnsafeMutableBufferPointer { buffer in _ = idevice_init_logger(Info, Debug, buffer.baseAddress) } diff --git a/StikDebug/Services/IdeviceLogFileManager.swift b/StikDebug/Services/IdeviceLogFileManager.swift new file mode 100644 index 0000000..30bd068 --- /dev/null +++ b/StikDebug/Services/IdeviceLogFileManager.swift @@ -0,0 +1,66 @@ +// +// IdeviceLogFileManager.swift +// StikDebug +// + +import Foundation + +final class IdeviceLogFileManager { + static let shared = IdeviceLogFileManager() + static let logURL = URL.documentsDirectory.appendingPathComponent("idevice_log.txt") + + private static let maximumSize: UInt64 = 20 * 1024 * 1024 + private static let retainedSize: UInt64 = 10 * 1024 * 1024 + private static let checkInterval: TimeInterval = 5 + + private let queue = DispatchQueue(label: "com.stik.ideviceLogFile", qos: .utility) + private var timer: DispatchSourceTimer? + + private init() {} + + func prepareForLogging() { + compactIfNeeded() + + queue.async { + guard self.timer == nil else { return } + + let timer = DispatchSource.makeTimerSource(queue: self.queue) + timer.schedule(deadline: .now() + Self.checkInterval, repeating: Self.checkInterval) + timer.setEventHandler { [weak self] in + self?.compactIfNeeded() + } + self.timer = timer + timer.resume() + } + } + + private func compactIfNeeded() { + let fileManager = FileManager.default + guard let attributes = try? fileManager.attributesOfItem(atPath: Self.logURL.path), + let fileSize = attributes[.size] as? NSNumber, + fileSize.uint64Value > Self.maximumSize, + let handle = try? FileHandle(forUpdating: Self.logURL) else { + return + } + + defer { try? handle.close() } + + do { + let endOffset = try handle.seekToEnd() + let startOffset = endOffset > Self.retainedSize ? endOffset - Self.retainedSize : 0 + try handle.seek(toOffset: startOffset) + var recentData = try handle.readToEnd() ?? Data() + + if startOffset > 0, let newlineIndex = recentData.firstIndex(of: 0x0A) { + recentData.removeSubrange(recentData.startIndex...newlineIndex) + } + + try handle.truncate(atOffset: 0) + try handle.seek(toOffset: 0) + try handle.write(contentsOf: recentData) + try handle.synchronize() + } catch { + return + } + } +} diff --git a/StikDebug/Views/ConsoleLogsView.swift b/StikDebug/Views/ConsoleLogsView.swift index 10db4c4..81510a6 100644 --- a/StikDebug/Views/ConsoleLogsView.swift +++ b/StikDebug/Views/ConsoleLogsView.swift @@ -266,7 +266,7 @@ struct ConsoleLogsView: View { @ViewBuilder private var exportMenuOption: some View { - let logURL: URL = URL.documentsDirectory.appendingPathComponent("idevice_log.txt") + let logURL = IdeviceLogFileManager.logURL if FileManager.default.fileExists(atPath: logURL.path) { ShareLink( item: logURL, @@ -309,7 +309,7 @@ struct ConsoleLogsView: View { guard !isLoadingLogs else { return } isLoadingLogs = true - let logPath = URL.documentsDirectory.appendingPathComponent("idevice_log.txt").path + let logPath = IdeviceLogFileManager.logURL.path guard FileManager.default.fileExists(atPath: logPath) else { await MainActor.run { @@ -368,7 +368,7 @@ struct ConsoleLogsView: View { guard !isLoadingLogs else { return } isLoadingLogs = true - let logPath = URL.documentsDirectory.appendingPathComponent("idevice_log.txt").path + let logPath = IdeviceLogFileManager.logURL.path let previousCount = lastProcessedLineCount guard FileManager.default.fileExists(atPath: logPath) else {