async ios

This commit is contained in:
shenlong-tanwen
2025-09-25 03:57:03 +05:30
parent ae595f2947
commit 13abe14142
10 changed files with 150 additions and 75 deletions

View File

@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 54; objectVersion = 77;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@@ -133,11 +133,14 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFileSystemSynchronizedRootGroup section */
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = { B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Sync; path = Sync;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B2D27ABE2E84A0FF004DD55B /* Core */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = Core;
sourceTree = "<group>";
};
F0B57D3D2DF764BD00DC5BCC /* WidgetExtension */ = { F0B57D3D2DF764BD00DC5BCC /* WidgetExtension */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
exceptions = ( exceptions = (
@@ -247,6 +250,7 @@
97C146F01CF9000F007C117D /* Runner */ = { 97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B2D27ABE2E84A0FF004DD55B /* Core */,
B25D37792E72CA15008B6CA7 /* Connectivity */, B25D37792E72CA15008B6CA7 /* Connectivity */,
B21E34A62E5AF9760031FDB9 /* Background */, B21E34A62E5AF9760031FDB9 /* Background */,
B2CF7F8C2DDE4EBB00744BF6 /* Sync */, B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
@@ -332,6 +336,7 @@
); );
fileSystemSynchronizedGroups = ( fileSystemSynchronizedGroups = (
B2CF7F8C2DDE4EBB00744BF6 /* Sync */, B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
B2D27ABE2E84A0FF004DD55B /* Core */,
); );
name = Runner; name = Runner;
productName = Runner; productName = Runner;
@@ -521,10 +526,14 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@@ -553,10 +562,14 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";

View File

@@ -179,11 +179,12 @@ class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Senda
static let shared = BackgroundWorkerPigeonCodec(readerWriter: BackgroundWorkerPigeonCodecReaderWriter()) static let shared = BackgroundWorkerPigeonCodec(readerWriter: BackgroundWorkerPigeonCodecReaderWriter())
} }
/// Generated protocol from Pigeon that represents a handler of messages from Flutter. /// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol BackgroundWorkerFgHostApi { protocol BackgroundWorkerFgHostApi {
func enable() throws func enable(completion: @escaping (Result<Void, Error>) -> Void)
func configure(settings: BackgroundWorkerSettings) throws func configure(settings: BackgroundWorkerSettings, completion: @escaping (Result<Void, Error>) -> Void)
func disable() throws func disable(completion: @escaping (Result<Void, Error>) -> Void)
} }
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -195,11 +196,13 @@ class BackgroundWorkerFgHostApiSetup {
let enableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) let enableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api { if let api = api {
enableChannel.setMessageHandler { _, reply in enableChannel.setMessageHandler { _, reply in
do { api.enable { result in
try api.enable() switch result {
reply(wrapResult(nil)) case .success:
} catch { reply(wrapResult(nil))
reply(wrapError(error)) case .failure(let error):
reply(wrapError(error))
}
} }
} }
} else { } else {
@@ -210,11 +213,13 @@ class BackgroundWorkerFgHostApiSetup {
configureChannel.setMessageHandler { message, reply in configureChannel.setMessageHandler { message, reply in
let args = message as! [Any?] let args = message as! [Any?]
let settingsArg = args[0] as! BackgroundWorkerSettings let settingsArg = args[0] as! BackgroundWorkerSettings
do { api.configure(settings: settingsArg) { result in
try api.configure(settings: settingsArg) switch result {
reply(wrapResult(nil)) case .success:
} catch { reply(wrapResult(nil))
reply(wrapError(error)) case .failure(let error):
reply(wrapError(error))
}
} }
} }
} else { } else {
@@ -223,11 +228,13 @@ class BackgroundWorkerFgHostApiSetup {
let disableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) let disableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api { if let api = api {
disableChannel.setMessageHandler { _, reply in disableChannel.setMessageHandler { _, reply in
do { api.disable { result in
try api.disable() switch result {
reply(wrapResult(nil)) case .success:
} catch { reply(wrapResult(nil))
reply(wrapError(error)) case .failure(let error):
reply(wrapError(error))
}
} }
} }
} else { } else {

View File

@@ -1,23 +1,27 @@
import BackgroundTasks import BackgroundTasks
class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi { class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
func enable(completion: @escaping (Result<Void, any Error>) -> Void) {
func enable() throws { dispatch(completion: completion, block: {
BackgroundWorkerApiImpl.scheduleRefreshWorker() BackgroundWorkerApiImpl.scheduleRefreshWorker()
BackgroundWorkerApiImpl.scheduleProcessingWorker() BackgroundWorkerApiImpl.scheduleProcessingWorker()
print("BackgroundWorkerApiImpl:enable Background worker scheduled") print("BackgroundWorkerApiImpl:enable Background worker scheduled")
});
} }
func configure(settings: BackgroundWorkerSettings) throws { func configure(settings: BackgroundWorkerSettings, completion: @escaping (Result<Void, any Error>) -> Void) {
// Android only // Android only
completion(Result.success(Void()))
} }
func disable() throws { func disable(completion: @escaping (Result<Void, any Error>) -> Void) {
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID); dispatch(completion: completion, block: {
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID); BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID);
print("BackgroundWorkerApiImpl:disableUploadWorker Disabled background workers") BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID);
print("BackgroundWorkerApiImpl:disableUploadWorker Disabled background workers")
});
} }
private static let refreshTaskID = "app.alextran.immich.background.refreshUpload" private static let refreshTaskID = "app.alextran.immich.background.refreshUpload"
private static let processingTaskID = "app.alextran.immich.background.processingUpload" private static let processingTaskID = "app.alextran.immich.background.processingUpload"
private static let taskSemaphore = DispatchSemaphore(value: 1) private static let taskSemaphore = DispatchSemaphore(value: 1)

View File

@@ -94,9 +94,10 @@ class ConnectivityPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable
static let shared = ConnectivityPigeonCodec(readerWriter: ConnectivityPigeonCodecReaderWriter()) static let shared = ConnectivityPigeonCodec(readerWriter: ConnectivityPigeonCodecReaderWriter())
} }
/// Generated protocol from Pigeon that represents a handler of messages from Flutter. /// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol ConnectivityApi { protocol ConnectivityApi {
func getCapabilities() throws -> [NetworkCapability] func getCapabilities(completion: @escaping (Result<[NetworkCapability], Error>) -> Void)
} }
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -115,11 +116,13 @@ class ConnectivityApiSetup {
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api { if let api = api {
getCapabilitiesChannel.setMessageHandler { _, reply in getCapabilitiesChannel.setMessageHandler { _, reply in
do { api.getCapabilities { result in
let result = try api.getCapabilities() switch result {
reply(wrapResult(result)) case .success(let res):
} catch { reply(wrapResult(res))
reply(wrapError(error)) case .failure(let error):
reply(wrapError(error))
}
} }
} }
} else { } else {

View File

@@ -1,6 +1,6 @@
class ConnectivityApiImpl: ConnectivityApi { class ConnectivityApiImpl: ConnectivityApi {
func getCapabilities() throws -> [NetworkCapability] { func getCapabilities(completion: @escaping (Result<[NetworkCapability], any Error>) -> Void) {
[] completion(Result.success([]))
} }
} }

View File

@@ -0,0 +1,12 @@
func dispatch<T>(
qos: DispatchQoS.QoSClass = .default,
completion: @escaping (Result<T, Error>) -> Void,
block: @escaping () throws -> T
) {
DispatchQueue.global(qos: qos).async {
let result = Result { try block() }
DispatchQueue.main.async {
completion(result)
}
}
}

View File

@@ -355,13 +355,13 @@ class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
/// Generated protocol from Pigeon that represents a handler of messages from Flutter. /// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol NativeSyncApi { protocol NativeSyncApi {
func shouldFullSync() throws -> Bool func shouldFullSync() throws -> Bool
func getMediaChanges() throws -> SyncDelta func getMediaChanges(completion: @escaping (Result<SyncDelta, Error>) -> Void)
func checkpointSync() throws func checkpointSync() throws
func clearSyncCheckpoint() throws func clearSyncCheckpoint() throws
func getAssetIdsForAlbum(albumId: String) throws -> [String] func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], Error>) -> Void)
func getAlbums() throws -> [PlatformAlbum] func getAlbums(completion: @escaping (Result<[PlatformAlbum], Error>) -> Void)
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 func getAssetsCountSince(albumId: String, timestamp: Int64, completion: @escaping (Result<Int64, Error>) -> Void)
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?, completion: @escaping (Result<[PlatformAsset], Error>) -> Void)
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
func cancelHashing() throws func cancelHashing() throws
} }
@@ -395,11 +395,13 @@ class NativeSyncApiSetup {
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api { if let api = api {
getMediaChangesChannel.setMessageHandler { _, reply in getMediaChangesChannel.setMessageHandler { _, reply in
do { api.getMediaChanges { result in
let result = try api.getMediaChanges() switch result {
reply(wrapResult(result)) case .success(let res):
} catch { reply(wrapResult(res))
reply(wrapError(error)) case .failure(let error):
reply(wrapError(error))
}
} }
} }
} else { } else {
@@ -438,11 +440,13 @@ class NativeSyncApiSetup {
getAssetIdsForAlbumChannel.setMessageHandler { message, reply in getAssetIdsForAlbumChannel.setMessageHandler { message, reply in
let args = message as! [Any?] let args = message as! [Any?]
let albumIdArg = args[0] as! String let albumIdArg = args[0] as! String
do { api.getAssetIdsForAlbum(albumId: albumIdArg) { result in
let result = try api.getAssetIdsForAlbum(albumId: albumIdArg) switch result {
reply(wrapResult(result)) case .success(let res):
} catch { reply(wrapResult(res))
reply(wrapError(error)) case .failure(let error):
reply(wrapError(error))
}
} }
} }
} else { } else {
@@ -453,11 +457,13 @@ class NativeSyncApiSetup {
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api { if let api = api {
getAlbumsChannel.setMessageHandler { _, reply in getAlbumsChannel.setMessageHandler { _, reply in
do { api.getAlbums { result in
let result = try api.getAlbums() switch result {
reply(wrapResult(result)) case .success(let res):
} catch { reply(wrapResult(res))
reply(wrapError(error)) case .failure(let error):
reply(wrapError(error))
}
} }
} }
} else { } else {
@@ -471,11 +477,13 @@ class NativeSyncApiSetup {
let args = message as! [Any?] let args = message as! [Any?]
let albumIdArg = args[0] as! String let albumIdArg = args[0] as! String
let timestampArg = args[1] as! Int64 let timestampArg = args[1] as! Int64
do { api.getAssetsCountSince(albumId: albumIdArg, timestamp: timestampArg) { result in
let result = try api.getAssetsCountSince(albumId: albumIdArg, timestamp: timestampArg) switch result {
reply(wrapResult(result)) case .success(let res):
} catch { reply(wrapResult(res))
reply(wrapError(error)) case .failure(let error):
reply(wrapError(error))
}
} }
} }
} else { } else {
@@ -489,11 +497,13 @@ class NativeSyncApiSetup {
let args = message as! [Any?] let args = message as! [Any?]
let albumIdArg = args[0] as! String let albumIdArg = args[0] as! String
let updatedTimeCondArg: Int64? = nilOrValue(args[1]) let updatedTimeCondArg: Int64? = nilOrValue(args[1])
do { api.getAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) { result in
let result = try api.getAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) switch result {
reply(wrapResult(result)) case .success(let res):
} catch { reply(wrapResult(res))
reply(wrapError(error)) case .failure(let error):
reply(wrapError(error))
}
} }
} }
} else { } else {

View File

@@ -18,6 +18,7 @@ struct AssetWrapper: Hashable, Equatable {
} }
class NativeSyncApiImpl: NativeSyncApi { class NativeSyncApiImpl: NativeSyncApi {
private let defaults: UserDefaults private let defaults: UserDefaults
private let changeTokenKey = "immich:changeToken" private let changeTokenKey = "immich:changeToken"
private let albumTypes: [PHAssetCollectionType] = [.album, .smartAlbum] private let albumTypes: [PHAssetCollectionType] = [.album, .smartAlbum]
@@ -75,7 +76,12 @@ class NativeSyncApiImpl: NativeSyncApi {
return false return false
} }
func getAlbums() throws -> [PlatformAlbum] {
func getAlbums(completion: @escaping (Result<[PlatformAlbum], any Error>) -> Void) {
dispatch(qos: .userInitiated, completion: completion, block: getAlbums)
}
private func getAlbums() throws -> [PlatformAlbum] {
var albums: [PlatformAlbum] = [] var albums: [PlatformAlbum] = []
albumTypes.forEach { type in albumTypes.forEach { type in
@@ -112,7 +118,11 @@ class NativeSyncApiImpl: NativeSyncApi {
return albums.sorted { $0.id < $1.id } return albums.sorted { $0.id < $1.id }
} }
func getMediaChanges() throws -> SyncDelta { func getMediaChanges(completion: @escaping (Result<SyncDelta, any Error>) -> Void) {
dispatch(qos: .userInitiated, completion: completion, block: getMediaChanges)
}
private func getMediaChanges() throws -> SyncDelta {
guard #available(iOS 16, *) else { guard #available(iOS 16, *) else {
throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil) throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil)
} }
@@ -198,7 +208,11 @@ class NativeSyncApiImpl: NativeSyncApi {
return albumAssets return albumAssets
} }
func getAssetIdsForAlbum(albumId: String) throws -> [String] { func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], any Error>) -> Void) {
dispatch(qos: .userInitiated, completion: completion, block: { try self.getAssetIdsForAlbum(albumId: albumId) })
}
private func getAssetIdsForAlbum(albumId: String) throws -> [String] {
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
guard let album = collections.firstObject else { guard let album = collections.firstObject else {
return [] return []
@@ -214,7 +228,13 @@ class NativeSyncApiImpl: NativeSyncApi {
return ids return ids
} }
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 { func getAssetsCountSince(albumId: String, timestamp: Int64, completion: @escaping (Result<Int64, any Error>) -> Void) {
dispatch(qos: .userInitiated, completion: completion, block: {
try self.getAssetsCountSince(albumId: albumId, timestamp: timestamp)
})
}
private func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 {
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
guard let album = collections.firstObject else { guard let album = collections.firstObject else {
return 0 return 0
@@ -228,7 +248,13 @@ class NativeSyncApiImpl: NativeSyncApi {
return Int64(assets.count) return Int64(assets.count)
} }
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] { func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?, completion: @escaping (Result<[PlatformAsset], any Error>) -> Void) {
dispatch(qos: .userInitiated, completion: completion, block: {
try self.getAssetsForAlbum(albumId: albumId, updatedTimeCond: updatedTimeCond)
})
}
private func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] {
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
guard let album = collections.firstObject else { guard let album = collections.firstObject else {
return [] return []