From 42f99e8039625d1abee4ddccfa300777130f1d04 Mon Sep 17 00:00:00 2001 From: Peter Ombodi Date: Thu, 18 Sep 2025 17:27:35 +0300 Subject: [PATCH] resolve merge conflicts use updated approach for calculating checksums --- .../app/alextran/immich/sync/Messages.g.kt | 90 ++++++++++++- .../alextran/immich/sync/MessagesImpl26.kt | 5 +- .../alextran/immich/sync/MessagesImpl30.kt | 81 +++++++----- .../alextran/immich/sync/MessagesImplBase.kt | 20 +-- mobile/ios/Runner/Sync/Messages.g.swift | 83 +++++++++++- mobile/lib/domain/services/hash.service.dart | 80 ++---------- mobile/lib/platform/native_sync_api.g.dart | 118 +++++++++++++++++- mobile/pigeon/native_sync_api.dart | 3 +- 8 files changed, 364 insertions(+), 116 deletions(-) diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt index 28400c803f..aea177e86e 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt @@ -89,7 +89,8 @@ data class PlatformAsset ( val height: Long? = null, val durationInSeconds: Long, val orientation: Long, - val isFavorite: Boolean + val isFavorite: Boolean, + val size: Long? = null ) { companion object { @@ -104,7 +105,8 @@ data class PlatformAsset ( val durationInSeconds = pigeonVar_list[7] as Long val orientation = pigeonVar_list[8] as Long val isFavorite = pigeonVar_list[9] as Boolean - return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite) + val size = pigeonVar_list[10] as Long? + return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, size) } } fun toList(): List { @@ -119,6 +121,7 @@ data class PlatformAsset ( durationInSeconds, orientation, isFavorite, + size, ) } override fun equals(other: Any?): Boolean { @@ -243,6 +246,40 @@ data class HashResult ( override fun hashCode(): Int = toList().hashCode() } + +/** Generated class from Pigeon that represents data sent in messages. */ +data class TrashedAssetParams ( + val id: String, + val type: Long, + val albumId: String? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): TrashedAssetParams { + val id = pigeonVar_list[0] as String + val type = pigeonVar_list[1] as Long + val albumId = pigeonVar_list[2] as String? + return TrashedAssetParams(id, type, albumId) + } + } + fun toList(): List { + return listOf( + id, + type, + albumId, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is TrashedAssetParams) { + return false + } + if (this === other) { + return true + } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} private open class MessagesPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { @@ -266,6 +303,11 @@ private open class MessagesPigeonCodec : StandardMessageCodec() { HashResult.fromList(it) } } + 133.toByte() -> { + return (readValue(buffer) as? List)?.let { + TrashedAssetParams.fromList(it) + } + } else -> super.readValueOfType(type, buffer) } } @@ -287,6 +329,10 @@ private open class MessagesPigeonCodec : StandardMessageCodec() { stream.write(132) writeValue(stream, value.toList()) } + is TrashedAssetParams -> { + stream.write(133) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -305,6 +351,8 @@ interface NativeSyncApi { fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List fun hashAssets(assetIds: List, allowNetworkAccess: Boolean, callback: (Result>) -> Unit) fun cancelHashing() + fun getTrashedAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List + fun hashTrashedAssets(trashedAssets: List, callback: (Result>) -> Unit) companion object { /** The codec used by NativeSyncApi. */ @@ -483,6 +531,44 @@ interface NativeSyncApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val albumIdArg = args[0] as String + val updatedTimeCondArg = args[1] as Long? + val wrapped: List = try { + listOf(api.getTrashedAssetsForAlbum(albumIdArg, updatedTimeCondArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val trashedAssetsArg = args[0] as List + api.hashTrashedAssets(trashedAssetsArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt index a39344295d..621ca78a28 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt @@ -29,7 +29,10 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na throw IllegalStateException("Method not supported on this Android version.") } - override fun hashTrashedAssets(trashedAssets: List): List { + override fun hashTrashedAssets( + trashedAssets: List, + callback: (Result>) -> Unit + ) { throw IllegalStateException("Method not supported on this Android version.") } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt index 58db33896b..f0e1225fe3 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt @@ -9,10 +9,15 @@ import android.os.Bundle import android.provider.MediaStore import androidx.annotation.RequiresApi import androidx.annotation.RequiresExtension +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.withPermit import kotlinx.serialization.json.Json -import java.io.BufferedInputStream -import java.security.DigestInputStream -import java.security.MessageDigest +import kotlin.coroutines.cancellation.CancellationException @RequiresApi(Build.VERSION_CODES.Q) @RequiresExtension(extension = Build.VERSION_CODES.R, version = 1) @@ -74,20 +79,41 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na return trashed } - override fun hashTrashedAssets(trashedAssets: List): List { - val result = ArrayList(trashedAssets.size) - for (item in trashedAssets) { - val digest = try { - val id = item.id.toLong() - val mediaType = item.type.toInt() - val uri = contentUriForType(id, mediaType) - sha1OfUri(ctx, uri) - } catch (_: Throwable) { - null - } - result.add(digest) + override fun hashTrashedAssets( + trashedAssets: List, + callback: (Result>) -> Unit + ) { + if (trashedAssets.isEmpty()) { + callback(Result.success(emptyList())) + return + } + hashTask?.cancel() + hashTask = CoroutineScope(Dispatchers.IO).launch { + try { + val results = trashedAssets.map { assetParams -> + async { + hashSemaphore.withPermit { + ensureActive() + hashTrashedAsset(assetParams) + } + } + }.awaitAll() + + callback(Result.success(results)) + } catch (e: CancellationException) { + callback( + Result.failure( + FlutterError( + HASHING_CANCELLED_CODE, + "Hashing operation was cancelled", + null + ) + ) + ) + } catch (e: Exception) { + callback(Result.failure(e)) + } } - return result } override fun shouldFullSync(): Boolean = @@ -144,6 +170,13 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na return SyncDelta(hasChanges, changed, deleted, assetAlbums) } + suspend fun hashTrashedAsset(assetParams: TrashedAssetParams): HashResult { + val id = assetParams.id.toLong() + val mediaType = assetParams.type.toInt() + val assetUri = contentUriForType(id, mediaType) + return hashAssetFromUri(assetParams.id, assetUri) + } + private fun contentUriForType(id: Long, mediaType: Int): Uri { val vol = MediaStore.VOLUME_EXTERNAL val base = when (mediaType) { @@ -154,20 +187,4 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na } return ContentUris.withAppendedId(base, id) } - - private fun sha1OfUri(ctx: Context, uri: Uri): ByteArray? { - return try { - val md = MessageDigest.getInstance("SHA-1") - ctx.contentResolver.openInputStream(uri)?.use { input -> - DigestInputStream(BufferedInputStream(input), md).use { dis -> - val buf = ByteArray(DEFAULT_BUFFER_SIZE) - while (dis.read(buf) != -1) { /* pump */ - } - } - } ?: return null - md.digest() - } catch (_: Exception) { - null - } - } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt index 9089af8e8d..7fc51fced6 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.ContentUris import android.content.Context import android.database.Cursor +import android.net.Uri import android.provider.MediaStore import android.util.Base64 import androidx.core.database.getStringOrNull @@ -30,12 +31,12 @@ sealed class AssetResult { open class NativeSyncApiImplBase(context: Context) { private val ctx: Context = context.applicationContext - private var hashTask: Job? = null + internal var hashTask: Job? = null companion object { private const val MAX_CONCURRENT_HASH_OPERATIONS = 16 - private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS) - private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED" + internal val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS) + internal const val HASHING_CANCELLED_CODE = "HASH_CANCELLED" const val MEDIA_SELECTION = "(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)" @@ -274,12 +275,15 @@ open class NativeSyncApiImplBase(context: Context) { } private suspend fun hashAsset(assetId: String): HashResult { - return try { - val assetUri = ContentUris.withAppendedId( - MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), - assetId.toLong() - ) + val assetUri = ContentUris.withAppendedId( + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), + assetId.toLong() + ) + return hashAssetFromUri(assetId, assetUri) + } + protected suspend fun hashAssetFromUri(assetId: String, assetUri: Uri): HashResult { + return try { val digest = MessageDigest.getInstance("SHA-1") ctx.contentResolver.openInputStream(assetUri)?.use { inputStream -> var bytesRead: Int diff --git a/mobile/ios/Runner/Sync/Messages.g.swift b/mobile/ios/Runner/Sync/Messages.g.swift index 305aca5266..8834c74611 100644 --- a/mobile/ios/Runner/Sync/Messages.g.swift +++ b/mobile/ios/Runner/Sync/Messages.g.swift @@ -140,6 +140,7 @@ struct PlatformAsset: Hashable { var durationInSeconds: Int64 var orientation: Int64 var isFavorite: Bool + var size: Int64? = nil // swift-format-ignore: AlwaysUseLowerCamelCase @@ -154,6 +155,7 @@ struct PlatformAsset: Hashable { let durationInSeconds = pigeonVar_list[7] as! Int64 let orientation = pigeonVar_list[8] as! Int64 let isFavorite = pigeonVar_list[9] as! Bool + let size: Int64? = nilOrValue(pigeonVar_list[10]) return PlatformAsset( id: id, @@ -165,7 +167,8 @@ struct PlatformAsset: Hashable { height: height, durationInSeconds: durationInSeconds, orientation: orientation, - isFavorite: isFavorite + isFavorite: isFavorite, + size: size ) } func toList() -> [Any?] { @@ -180,6 +183,7 @@ struct PlatformAsset: Hashable { durationInSeconds, orientation, isFavorite, + size, ] } static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool { @@ -300,6 +304,39 @@ struct HashResult: Hashable { } } +/// Generated class from Pigeon that represents data sent in messages. +struct TrashedAssetParams: Hashable { + var id: String + var type: Int64 + var albumId: String? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> TrashedAssetParams? { + let id = pigeonVar_list[0] as! String + let type = pigeonVar_list[1] as! Int64 + let albumId: String? = nilOrValue(pigeonVar_list[2]) + + return TrashedAssetParams( + id: id, + type: type, + albumId: albumId + ) + } + func toList() -> [Any?] { + return [ + id, + type, + albumId, + ] + } + static func == (lhs: TrashedAssetParams, rhs: TrashedAssetParams) -> Bool { + return deepEqualsMessages(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashMessages(value: toList(), hasher: &hasher) + } +} + private class MessagesPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { @@ -311,6 +348,8 @@ private class MessagesPigeonCodecReader: FlutterStandardReader { return SyncDelta.fromList(self.readValue() as! [Any?]) case 132: return HashResult.fromList(self.readValue() as! [Any?]) + case 133: + return TrashedAssetParams.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) } @@ -331,6 +370,9 @@ private class MessagesPigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? HashResult { super.writeByte(132) super.writeValue(value.toList()) + } else if let value = value as? TrashedAssetParams { + super.writeByte(133) + super.writeValue(value.toList()) } else { super.writeValue(value) } @@ -364,6 +406,8 @@ protocol NativeSyncApi { func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) func cancelHashing() throws + func getTrashedAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] + func hashTrashedAssets(trashedAssets: [TrashedAssetParams], completion: @escaping (Result<[HashResult], Error>) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -532,5 +576,42 @@ class NativeSyncApiSetup { } else { cancelHashingChannel.setMessageHandler(nil) } + let getTrashedAssetsForAlbumChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getTrashedAssetsForAlbumChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let albumIdArg = args[0] as! String + let updatedTimeCondArg: Int64? = nilOrValue(args[1]) + do { + let result = try api.getTrashedAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getTrashedAssetsForAlbumChannel.setMessageHandler(nil) + } + let hashTrashedAssetsChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + hashTrashedAssetsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let trashedAssetsArg = args[0] as! [TrashedAssetParams] + api.hashTrashedAssets(trashedAssets: trashedAssetsArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + hashTrashedAssetsChannel.setMessageHandler(nil) + } } } diff --git a/mobile/lib/domain/services/hash.service.dart b/mobile/lib/domain/services/hash.service.dart index ceda573dfe..d131b55609 100644 --- a/mobile/lib/domain/services/hash.service.dart +++ b/mobile/lib/domain/services/hash.service.dart @@ -31,7 +31,7 @@ class HashService { _localAssetRepository = localAssetRepository, _cancelChecker = cancelChecker, _nativeSyncApi = nativeSyncApi, - _batchSize = batchSize ?? kBatchHashFileLimit; + _batchSize = batchSize ?? kBatchHashFileLimit, _trashSyncService = trashSyncService; bool get isCancelled => _cancelChecker?.call() ?? false; @@ -144,7 +144,6 @@ class HashService { } Future _hashTrashedAssets(LocalAlbum album, Iterable assetsToHash) async { - int bytesProcessed = 0; final toHash = []; for (final asset in assetsToHash) { @@ -160,13 +159,11 @@ class HashService { continue; } - bytesProcessed += asset.size!; toHash.add(asset); - if (toHash.length >= batchFileLimit || bytesProcessed >= batchSizeLimit) { + if (toHash.length == _batchSize) { await _processTrashedBatch(album, toHash); toHash.clear(); - bytesProcessed = 0; } } @@ -181,27 +178,27 @@ class HashService { _log.fine("Hashing ${toHash.length} trashed files"); final params = toHash.map((e) => TrashedAssetParams(id: e.id, type: e.type.index, albumId: album.id)).toList(); - final hashes = await _nativeSyncApi.hashTrashedAssets(params); + final hashResults = await _nativeSyncApi.hashTrashedAssets(params); assert( - hashes.length == toHash.length, - "Trashed Assets, Hashes length does not match toHash length: ${hashes.length} != ${toHash.length}", + hashResults.length == toHash.length, + "Trashed Assets, Hashes length does not match toHash length: ${hashResults.length} != ${toHash.length}", ); final hashed = []; - for (int i = 0; i < hashes.length; i++) { + for (int i = 0; i < hashResults.length; i++) { if (isCancelled) { _log.warning("Hashing cancelled. Stopped processing batch."); return; } - final hash = hashes[i]; + final hashResult = hashResults[i]; final asset = toHash[i]; - if (hash?.length == 20) { - hashed.add(asset.copyWith(checksum: base64.encode(hash!))); + if (hashResult.hash != null) { + hashed.add(asset.copyWith(checksum: hashResult.hash!)); } else { _log.warning( - "Failed to hash trashed file for ${asset.id}: ${asset.name} created at ${asset.createdAt} from album: ${album.name}", + "Failed to hash trashed asset with id: ${hashResult.assetId}, name: ${asset.name}, createdAt: ${asset.createdAt}, from album: ${album.name}. Error: ${hashResult.error ?? "unknown"}", ); } } @@ -209,61 +206,4 @@ class HashService { _log.fine("Hashed ${hashed.length}/${toHash.length} trashed assets"); await _trashSyncService.updateChecksums(hashed); } - - // Future _hashTrashedAssets(LocalAlbum album, Iterable assetsToHash) async { - // final trashedAssets = assetsToHash - // .map((e) => TrashedAssetParams(id: e.id, type: e.type.index, albumId: album.id)) - // .toList(); - // - // final byId = {for (final a in assetsToHash) a.id: a}; - // - // final hashed = []; - // const chunkSize = 10; - // - // for (int i = 0; i < trashedAssets.length; i += chunkSize) { - // if (isCancelled) { - // _log.warning("Hashing cancelled. Stopped processing assets."); - // return; - // } - // final end = (i + chunkSize <= trashedAssets.length) ? i + chunkSize : trashedAssets.length; - // final batch = trashedAssets.sublist(i, end); - // - // List hashes; - // try { - // hashes = await _nativeSyncApi.hashTrashedAssets(batch); - // } catch (e, s) { - // _log.severe("hashTrashedAssets failed for batch [$i..${end - 1}]: $e", e, s); - // continue; - // } - // - // if (hashes.length != batch.length) { - // _log.warning( - // "hashTrashedAssets returned ${hashes.length} hashes for ${batch.length} assets (batch [$i..${end - 1}]).", - // ); - // } - // - // final limit = hashes.length < batch.length ? hashes.length : batch.length; - // for (int j = 0; j < limit; j++) { - // if (isCancelled) { - // _log.warning("Hashing cancelled. Stopped processing assets."); - // return; - // } - // final asset = batch[j]; - // final hash = hashes[j]; - // - // if (hash != null && hash.length == 20) { - // final localAsset = byId[asset.id]; - // if (localAsset != null) { - // hashed.add(localAsset.copyWith(checksum: base64.encode(hash))); - // } - // } else { - // _log.warning("Failed to hash file for ${asset.id} from album: ${album.name}"); - // } - // } - // } - // - // _log.warning("updateHashes for album: ${album.name}, assets: ${hashed.map((e) => '${e.name}-${e.checksum}')}"); - // - // await _trashSyncService.updateHashes(hashed); - // } } diff --git a/mobile/lib/platform/native_sync_api.g.dart b/mobile/lib/platform/native_sync_api.g.dart index 01237f8c19..5b390663cb 100644 --- a/mobile/lib/platform/native_sync_api.g.dart +++ b/mobile/lib/platform/native_sync_api.g.dart @@ -41,6 +41,7 @@ class PlatformAsset { required this.durationInSeconds, required this.orientation, required this.isFavorite, + this.size, }); String id; @@ -63,8 +64,22 @@ class PlatformAsset { bool isFavorite; + int? size; + List _toList() { - return [id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite]; + return [ + id, + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + orientation, + isFavorite, + size, + ]; } Object encode() { @@ -84,6 +99,7 @@ class PlatformAsset { durationInSeconds: result[7]! as int, orientation: result[8]! as int, isFavorite: result[9]! as bool, + size: result[10] as int?, ); } @@ -244,6 +260,45 @@ class HashResult { int get hashCode => Object.hashAll(_toList()); } +class TrashedAssetParams { + TrashedAssetParams({required this.id, required this.type, this.albumId}); + + String id; + + int type; + + String? albumId; + + List _toList() { + return [id, type, albumId]; + } + + Object encode() { + return _toList(); + } + + static TrashedAssetParams decode(Object result) { + result as List; + return TrashedAssetParams(id: result[0]! as String, type: result[1]! as int, albumId: result[2] as String?); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! TrashedAssetParams || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -263,6 +318,9 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is HashResult) { buffer.putUint8(132); writeValue(buffer, value.encode()); + } else if (value is TrashedAssetParams) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -279,6 +337,8 @@ class _PigeonCodec extends StandardMessageCodec { return SyncDelta.decode(readValue(buffer)!); case 132: return HashResult.decode(readValue(buffer)!); + case 133: + return TrashedAssetParams.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -562,4 +622,60 @@ class NativeSyncApi { return; } } + + Future> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond}) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([albumId, updatedTimeCond]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as List?)!.cast(); + } + } + + Future> hashTrashedAssets(List trashedAssets) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([trashedAssets]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as List?)!.cast(); + } + } } diff --git a/mobile/pigeon/native_sync_api.dart b/mobile/pigeon/native_sync_api.dart index 8e9d16bd52..292b22dc9b 100644 --- a/mobile/pigeon/native_sync_api.dart +++ b/mobile/pigeon/native_sync_api.dart @@ -125,6 +125,7 @@ abstract class NativeSyncApi { @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond}); + @async @TaskQueue(type: TaskQueueType.serialBackgroundThread) - List hashTrashedAssets(List trashedAssets); + List hashTrashedAssets(List trashedAssets); }