mirror of
https://github.com/immich-app/immich.git
synced 2026-04-25 02:29:51 -04:00
debug: add native file logger
This commit is contained in:
parent
e61793fa9d
commit
ef1612a8a7
@ -17,6 +17,7 @@
|
||||
A01DD69B2F7F43B40049AB63 /* ImageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A01DD6982F7F43B40049AB63 /* ImageRequest.swift */; };
|
||||
B21E34AA2E5AFD2B0031FDB9 /* BackgroundWorkerApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34A92E5AFD210031FDB9 /* BackgroundWorkerApiImpl.swift */; };
|
||||
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */; };
|
||||
B21E34B02E5B09190031FDB9 /* FileLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34B12E5B09100031FDB9 /* FileLogger.swift */; };
|
||||
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */; };
|
||||
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */; };
|
||||
B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */; };
|
||||
@ -103,6 +104,7 @@
|
||||
B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
B21E34A92E5AFD210031FDB9 /* BackgroundWorkerApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorkerApiImpl.swift; sourceTree = "<group>"; };
|
||||
B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.swift; sourceTree = "<group>"; };
|
||||
B21E34B12E5B09100031FDB9 /* FileLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogger.swift; sourceTree = "<group>"; };
|
||||
B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connectivity.g.swift; sourceTree = "<group>"; };
|
||||
B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityApiImpl.swift; sourceTree = "<group>"; };
|
||||
B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.g.swift; sourceTree = "<group>"; };
|
||||
@ -304,6 +306,7 @@
|
||||
B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */,
|
||||
B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */,
|
||||
B21E34A92E5AFD210031FDB9 /* BackgroundWorkerApiImpl.swift */,
|
||||
B21E34B12E5B09100031FDB9 /* FileLogger.swift */,
|
||||
);
|
||||
path = Background;
|
||||
sourceTree = "<group>";
|
||||
@ -614,6 +617,7 @@
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
A01DD69B2F7F43B40049AB63 /* ImageRequest.swift in Sources */,
|
||||
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */,
|
||||
B21E34B02E5B09190031FDB9 /* FileLogger.swift in Sources */,
|
||||
FE5499F32F1197D8006016CB /* LocalImages.g.swift in Sources */,
|
||||
FE5499F62F11980E006016CB /* LocalImagesImpl.swift in Sources */,
|
||||
FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */,
|
||||
|
||||
@ -80,29 +80,34 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
||||
* starts the engine, and sets up a timeout timer if specified.
|
||||
*/
|
||||
func run() {
|
||||
FileLogger.log("BackgroundWorker:run Starting Flutter engine for taskType=\(taskType) maxSeconds=\(maxSeconds.map(String.init) ?? "nil")")
|
||||
// Start the Flutter engine with the specified callback as the entry point
|
||||
let isRunning = engine.run(
|
||||
withEntrypoint: "backgroundSyncNativeEntrypoint",
|
||||
libraryURI: "package:immich_mobile/domain/services/background_worker.service.dart"
|
||||
)
|
||||
|
||||
|
||||
// Verify that the Flutter engine started successfully
|
||||
if !isRunning {
|
||||
FileLogger.log("BackgroundWorker:run Flutter engine failed to start, completing with success=false")
|
||||
complete(success: false)
|
||||
return
|
||||
}
|
||||
|
||||
FileLogger.log("BackgroundWorker:run Flutter engine started")
|
||||
|
||||
// Register plugins in the new engine
|
||||
GeneratedPluginRegistrant.register(with: engine)
|
||||
// Register custom plugins
|
||||
AppDelegate.registerPlugins(with: engine, messenger: engine.binaryMessenger)
|
||||
flutterApi = BackgroundWorkerFlutterApi(binaryMessenger: engine.binaryMessenger)
|
||||
BackgroundWorkerBgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: self)
|
||||
|
||||
FileLogger.log("BackgroundWorker:run Plugins registered, waiting for Flutter onInitialized")
|
||||
|
||||
// Set up a timeout timer if maxSeconds was specified to prevent runaway background tasks
|
||||
if maxSeconds != nil {
|
||||
// Schedule a timer to cancel the task after the specified timeout period
|
||||
Timer.scheduledTimer(withTimeInterval: TimeInterval(maxSeconds!), repeats: false) { _ in
|
||||
FileLogger.log("BackgroundWorker:run maxSeconds=\(self.maxSeconds!) timer fired, closing task")
|
||||
self.close()
|
||||
}
|
||||
}
|
||||
@ -114,6 +119,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
||||
* This method acts as a bridge between the native iOS background task system and Flutter.
|
||||
*/
|
||||
func onInitialized() throws {
|
||||
FileLogger.log("BackgroundWorker:onInitialized Flutter ready, calling onIosUpload isRefresh=\(self.taskType == .refresh)")
|
||||
flutterApi?.onIosUpload(isRefresh: self.taskType == .refresh, maxSeconds: maxSeconds.map { Int64($0) }, completion: { result in
|
||||
self.handleHostResult(result: result)
|
||||
})
|
||||
@ -126,16 +132,22 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
||||
*/
|
||||
func close() {
|
||||
if isComplete {
|
||||
FileLogger.log("BackgroundWorker:close Already complete, ignoring close()")
|
||||
return
|
||||
}
|
||||
FileLogger.log("BackgroundWorker:close Cancel requested, signaling Flutter (taskType=\(taskType))")
|
||||
|
||||
flutterApi?.cancel { result in
|
||||
FileLogger.log("BackgroundWorker:close Flutter cancel acknowledged")
|
||||
self.complete(success: false)
|
||||
}
|
||||
|
||||
// Fallback safety mechanism: ensure completion is called within 2 seconds
|
||||
// This prevents the background task from hanging indefinitely if Flutter doesn't respond
|
||||
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
|
||||
if !self.isComplete {
|
||||
FileLogger.log("BackgroundWorker:close 2s fallback fired, Flutter did not acknowledge cancel")
|
||||
}
|
||||
self.complete(success: false)
|
||||
}
|
||||
}
|
||||
@ -149,8 +161,12 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
||||
*/
|
||||
private func handleHostResult(result: Result<Void, PigeonError>) {
|
||||
switch result {
|
||||
case .success(): self.complete(success: true)
|
||||
case .failure(_): self.close()
|
||||
case .success():
|
||||
FileLogger.log("BackgroundWorker:handleHostResult Flutter onIosUpload succeeded (taskType=\(taskType))")
|
||||
self.complete(success: true)
|
||||
case .failure(let error):
|
||||
FileLogger.log("BackgroundWorker:handleHostResult Flutter onIosUpload failed: \(error.localizedDescription) (taskType=\(taskType))")
|
||||
self.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,7 +182,8 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
||||
if(isComplete) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
FileLogger.log("BackgroundWorker:complete Tearing down engine, success=\(success) (taskType=\(taskType))")
|
||||
isComplete = true
|
||||
AppDelegate.cancelPlugins(with: engine)
|
||||
engine.destroyContext()
|
||||
|
||||
@ -5,7 +5,7 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
func enable() throws {
|
||||
BackgroundWorkerApiImpl.scheduleRefreshWorker()
|
||||
BackgroundWorkerApiImpl.scheduleProcessingWorker()
|
||||
print("BackgroundWorkerApiImpl:enable Background worker scheduled")
|
||||
FileLogger.log("BackgroundWorkerApiImpl:enable Background worker scheduled")
|
||||
}
|
||||
|
||||
func configure(settings: BackgroundWorkerSettings) throws {
|
||||
@ -19,7 +19,7 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
func disable() throws {
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID);
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID);
|
||||
print("BackgroundWorkerApiImpl:disableUploadWorker Disabled background workers")
|
||||
FileLogger.log("BackgroundWorkerApiImpl:disableUploadWorker Disabled background workers")
|
||||
}
|
||||
|
||||
private static let refreshTaskID = "app.alextran.immich.background.refreshUpload"
|
||||
@ -30,6 +30,7 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
BGTaskScheduler.shared.register(
|
||||
forTaskWithIdentifier: processingTaskID, using: nil) { task in
|
||||
if task is BGProcessingTask {
|
||||
FileLogger.log("BackgroundWorkerApiImpl:BGProcessingTask Background Processing task received")
|
||||
handleBackgroundProcessing(task: task as! BGProcessingTask)
|
||||
}
|
||||
}
|
||||
@ -37,9 +38,11 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
BGTaskScheduler.shared.register(
|
||||
forTaskWithIdentifier: refreshTaskID, using: nil) { task in
|
||||
if task is BGAppRefreshTask {
|
||||
FileLogger.log("BackgroundWorkerApiImpl:BGAppRefreshTask Background Refresh task received")
|
||||
handleBackgroundRefresh(task: task as! BGAppRefreshTask)
|
||||
}
|
||||
}
|
||||
FileLogger.log("BackgroundWorkerApiImpl:registerBackgroundWorkers Background workers registered")
|
||||
}
|
||||
|
||||
private static func scheduleRefreshWorker() {
|
||||
@ -48,8 +51,9 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(backgroundRefresh)
|
||||
FileLogger.log("BackgroundWorkerApiImpl:scheduleRefreshWorker Scheduled Refresh task")
|
||||
} catch {
|
||||
print("Could not schedule the refresh upload task \(error.localizedDescription)")
|
||||
FileLogger.log("BackgroundWorkerApiImpl:scheduleRefreshWorker Could not schedule the refresh upload task \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,25 +65,32 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(backgroundProcessing)
|
||||
FileLogger.log("BackgroundWorkerApiImpl:scheduleProcessingWorker Scheduled Processing task")
|
||||
} catch {
|
||||
print("Could not schedule the processing upload task \(error.localizedDescription)")
|
||||
FileLogger.log("BackgroundWorkerApiImpl:scheduleProcessingWorker Could not schedule the processing upload task \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
private static func handleBackgroundRefresh(task: BGAppRefreshTask) {
|
||||
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundRefresh Entered, re-queuing next refresh task")
|
||||
scheduleRefreshWorker()
|
||||
// If another task is running, cede the background time back to the OS
|
||||
if taskSemaphore.wait(timeout: .now()) == .success {
|
||||
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundRefresh Starting background worker")
|
||||
// Restrict the refresh task to run only for a maximum of (maxSeconds) seconds
|
||||
runBackgroundWorker(task: task, taskType: .refresh, maxSeconds: 20)
|
||||
} else {
|
||||
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundRefresh Processing task is in progress")
|
||||
task.setTaskCompleted(success: true)
|
||||
}
|
||||
}
|
||||
|
||||
private static func handleBackgroundProcessing(task: BGProcessingTask) {
|
||||
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundProcessing Entered, re-queuing next processing task")
|
||||
scheduleProcessingWorker()
|
||||
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundProcessing Waiting for taskSemaphore")
|
||||
taskSemaphore.wait()
|
||||
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundProcessing Semaphore acquired, starting background worker")
|
||||
// There are no restrictions for processing tasks. Although, the OS could signal expiration at any time
|
||||
runBackgroundWorker(task: task, taskType: .processing, maxSeconds: nil)
|
||||
}
|
||||
@ -105,11 +116,12 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
}
|
||||
|
||||
task.expirationHandler = {
|
||||
FileLogger.log("BackgroundWorkerApiImpl:runBackgroundWorker iOS signaled expiration (taskType=\(taskType)), closing worker")
|
||||
DispatchQueue.main.async {
|
||||
backgroundWorker.close()
|
||||
}
|
||||
isSuccess = false
|
||||
|
||||
|
||||
// Schedule a timer to signal the semaphore after 2 seconds
|
||||
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
|
||||
semaphore.signal()
|
||||
@ -122,6 +134,6 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
|
||||
semaphore.wait()
|
||||
task.setTaskCompleted(success: isSuccess)
|
||||
print("Background task completed with success: \(isSuccess)")
|
||||
FileLogger.log("BackgroundWorkerApiImpl:runBackgroundWorker Background task completed with success: \(isSuccess)")
|
||||
}
|
||||
}
|
||||
|
||||
33
mobile/ios/Runner/Background/FileLogger.swift
Normal file
33
mobile/ios/Runner/Background/FileLogger.swift
Normal file
@ -0,0 +1,33 @@
|
||||
import Foundation
|
||||
|
||||
enum FileLogger {
|
||||
private static let queue = DispatchQueue(label: "app.alextran.immich.FileLogger")
|
||||
private static let isoFormatter: ISO8601DateFormatter = {
|
||||
let f = ISO8601DateFormatter()
|
||||
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
return f
|
||||
}()
|
||||
|
||||
private static var logFileURL: URL? {
|
||||
guard let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
|
||||
else { return nil }
|
||||
return docs.appendingPathComponent("background_log.txt")
|
||||
}
|
||||
|
||||
static func log(_ message: String) {
|
||||
let line = "[\(isoFormatter.string(from: Date()))] \(message)\n"
|
||||
print(line, terminator: "")
|
||||
queue.async {
|
||||
guard let url = logFileURL, let data = line.data(using: .utf8) else { return }
|
||||
if FileManager.default.fileExists(atPath: url.path) {
|
||||
if let handle = try? FileHandle(forWritingTo: url) {
|
||||
defer { try? handle.close() }
|
||||
try? handle.seekToEnd()
|
||||
try? handle.write(contentsOf: data)
|
||||
}
|
||||
} else {
|
||||
try? data.write(to: url, options: .atomic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,7 +115,9 @@
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<string>No</string>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user