rework fetching trashed assets data on native side

optimize handling trashed assets in local sync service
refactor code
This commit is contained in:
Peter Ombodi 2025-10-08 18:47:42 +03:00
parent cd43564d46
commit 519e428b99
8 changed files with 133 additions and 97 deletions

View file

@ -296,7 +296,7 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface NativeSyncApi { interface NativeSyncApi {
fun shouldFullSync(): Boolean fun shouldFullSync(): Boolean
fun getMediaChanges(isTrashed: Boolean): SyncDelta fun getMediaChanges(): SyncDelta
fun checkpointSync() fun checkpointSync()
fun clearSyncCheckpoint() fun clearSyncCheckpoint()
fun getAssetIdsForAlbum(albumId: String): List<String> fun getAssetIdsForAlbum(albumId: String): List<String>
@ -305,7 +305,7 @@ interface NativeSyncApi {
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset> fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit) fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
fun cancelHashing() fun cancelHashing()
fun getTrashedAssetsForAlbum(albumId: String): List<PlatformAsset> fun getTrashedAssets(albumIds: List<String>, sinceLastCheckpoint: Boolean): Map<String, List<PlatformAsset>>
companion object { companion object {
/** The codec used by NativeSyncApi. */ /** The codec used by NativeSyncApi. */
@ -335,11 +335,9 @@ interface NativeSyncApi {
run { run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue) val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) { if (api != null) {
channel.setMessageHandler { message, reply -> channel.setMessageHandler { _, reply ->
val args = message as List<Any?>
val isTrashedArg = args[0] as Boolean
val wrapped: List<Any?> = try { val wrapped: List<Any?> = try {
listOf(api.getMediaChanges(isTrashedArg)) listOf(api.getMediaChanges())
} catch (exception: Throwable) { } catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception) MessagesPigeonUtils.wrapError(exception)
} }
@ -487,13 +485,14 @@ interface NativeSyncApi {
} }
} }
run { run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$separatedMessageChannelSuffix", codec, taskQueue) val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) { if (api != null) {
channel.setMessageHandler { message, reply -> channel.setMessageHandler { message, reply ->
val args = message as List<Any?> val args = message as List<Any?>
val albumIdArg = args[0] as String val albumIdsArg = args[0] as List<String>
val sinceLastCheckpointArg = args[1] as Boolean
val wrapped: List<Any?> = try { val wrapped: List<Any?> = try {
listOf(api.getTrashedAssetsForAlbum(albumIdArg)) listOf(api.getTrashedAssets(albumIdsArg, sinceLastCheckpointArg))
} catch (exception: Throwable) { } catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception) MessagesPigeonUtils.wrapError(exception)
} }

View file

@ -18,13 +18,14 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na
// No-op for Android 10 and below // No-op for Android 10 and below
} }
override fun getMediaChanges(isTrashed: Boolean): SyncDelta { override fun getMediaChanges(): SyncDelta {
throw IllegalStateException("Method not supported on this Android version.") throw IllegalStateException("Method not supported on this Android version.")
} }
override fun getTrashedAssetsForAlbum( override fun getTrashedAssets(
albumId: String, albumIds: List<String>,
): List<PlatformAsset> { sinceLastCheckpoint: Boolean
): Map<String, List<PlatformAsset>> {
throw IllegalStateException("Method not supported on this Android version.") throw IllegalStateException("Method not supported on this Android version.")
} }
} }

View file

@ -1,23 +1,13 @@
package app.alextran.immich.sync package app.alextran.immich.sync
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.RequiresExtension 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 kotlinx.serialization.json.Json
import kotlin.coroutines.cancellation.CancellationException
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.Q)
@RequiresExtension(extension = Build.VERSION_CODES.R, version = 1) @RequiresExtension(extension = Build.VERSION_CODES.R, version = 1)
@ -59,7 +49,7 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
} }
} }
override fun getMediaChanges(isTrashed: Boolean): SyncDelta { override fun getMediaChanges(): SyncDelta {
val genMap = getSavedGenerationMap() val genMap = getSavedGenerationMap()
val currentVolumes = MediaStore.getExternalVolumeNames(ctx) val currentVolumes = MediaStore.getExternalVolumeNames(ctx)
val changed = mutableListOf<PlatformAsset>() val changed = mutableListOf<PlatformAsset>()
@ -83,17 +73,8 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
storedGen.toString(), storedGen.toString(),
storedGen.toString() storedGen.toString()
) )
val cursor = if (isTrashed) {
val queryArgs = Bundle().apply { getAssets(getCursor(volume, selection, selectionArgs)).forEach {
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection)
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs)
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
}
getCursor(volume, queryArgs)
} else {
getCursor(volume, selection, selectionArgs)
}
getAssets(cursor).forEach {
when (it) { when (it) {
is AssetResult.ValidAsset -> { is AssetResult.ValidAsset -> {
changed.add(it.asset) changed.add(it.asset)
@ -108,26 +89,81 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
return SyncDelta(hasChanges, changed, deleted, assetAlbums) return SyncDelta(hasChanges, changed, deleted, assetAlbums)
} }
override fun getTrashedAssetsForAlbum( // override fun getTrashedAssetsForAlbum(
albumId: String // albumId: String
): List<PlatformAsset> { // ): List<PlatformAsset> {
val trashed = mutableListOf<PlatformAsset>() // val trashed = mutableListOf<PlatformAsset>()
// val volumes = MediaStore.getExternalVolumeNames(ctx)
//
// val selection = "$BUCKET_SELECTION AND $MEDIA_SELECTION"
// val selectionArgs = mutableListOf(albumId, *MEDIA_SELECTION_ARGS)
//
// for (volume in volumes) {
// val cursor = getCursor(volume, Bundle().apply {
// putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection)
// putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs.toTypedArray())
// putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
// })
// getAssets(cursor).forEach { res ->
// if (res is AssetResult.ValidAsset) trashed += res.asset
// }
// }
//
// return trashed
// }
override fun getTrashedAssets(
albumIds: List<String>,
sinceLastCheckpoint: Boolean
): Map<String, List<PlatformAsset>> {
if (albumIds.isEmpty()) return emptyMap()
val result = LinkedHashMap<String, MutableList<PlatformAsset>>(albumIds.size)
val volumes = MediaStore.getExternalVolumeNames(ctx) val volumes = MediaStore.getExternalVolumeNames(ctx)
val selection = "$BUCKET_SELECTION AND $MEDIA_SELECTION" val placeholders = albumIds.joinToString(",") { "?" }
val selectionArgs = mutableListOf(albumId, *MEDIA_SELECTION_ARGS) val bucketIn = "(${MediaStore.Files.FileColumns.BUCKET_ID} IN ($placeholders))"
val baseSelection = "$bucketIn AND $MEDIA_SELECTION"
val baseSelectionArgs = ArrayList<String>(albumIds.size + MEDIA_SELECTION_ARGS.size).apply {
addAll(albumIds)
addAll(MEDIA_SELECTION_ARGS)
}
val genMap = if (sinceLastCheckpoint) getSavedGenerationMap() else emptyMap()
for (volume in volumes) { for (volume in volumes) {
val cursor = getCursor(volume, Bundle().apply { var selection = baseSelection
val selectionArgs = ArrayList<String>(baseSelectionArgs.size + if (sinceLastCheckpoint) 2 else 0).apply {
addAll(baseSelectionArgs)
}
if (sinceLastCheckpoint) {
val currentGen = MediaStore.getGeneration(ctx, volume)
val storedGen = genMap[volume] ?: 0L
if (currentGen <= storedGen) {
continue
}
selection += " AND (${MediaStore.MediaColumns.GENERATION_MODIFIED} > ? OR ${MediaStore.MediaColumns.GENERATION_ADDED} > ?)"
selectionArgs += storedGen.toString()
selectionArgs += storedGen.toString()
}
val queryArgs = Bundle().apply {
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection) putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection)
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs.toTypedArray()) putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs.toTypedArray())
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY) putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
}) }
getAssets(cursor).forEach { res ->
if (res is AssetResult.ValidAsset) trashed += res.asset getCursor(volume, queryArgs).use { cursor ->
getAssets(cursor).forEach { res ->
if (res is AssetResult.ValidAsset) {
result.getOrPut(res.albumId) { mutableListOf() }.add(res.asset)
}
}
} }
} }
return trashed return result.mapValues { it.value.toList() }
} }
} }

View file

@ -355,7 +355,7 @@ 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(isTrashed: Bool) throws -> SyncDelta func getMediaChanges() throws -> SyncDelta
func checkpointSync() throws func checkpointSync() throws
func clearSyncCheckpoint() throws func clearSyncCheckpoint() throws
func getAssetIdsForAlbum(albumId: String) throws -> [String] func getAssetIdsForAlbum(albumId: String) throws -> [String]
@ -364,7 +364,7 @@ protocol NativeSyncApi {
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
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
func getTrashedAssetsForAlbum(albumId: String) throws -> [PlatformAsset] func getTrashedAssets(albumIds: [String], sinceLastCheckpoint: Bool) throws -> [String: [PlatformAsset]]
} }
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@ -395,11 +395,9 @@ class NativeSyncApiSetup {
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: 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 { message, reply in getMediaChangesChannel.setMessageHandler { _, reply in
let args = message as! [Any?]
let isTrashedArg = args[0] as! Bool
do { do {
let result = try api.getMediaChanges(isTrashed: isTrashedArg) let result = try api.getMediaChanges()
reply(wrapResult(result)) reply(wrapResult(result))
} catch { } catch {
reply(wrapError(error)) reply(wrapError(error))
@ -535,22 +533,23 @@ class NativeSyncApiSetup {
} else { } else {
cancelHashingChannel.setMessageHandler(nil) cancelHashingChannel.setMessageHandler(nil)
} }
let getTrashedAssetsForAlbumChannel = taskQueue == nil let getTrashedAssetsChannel = taskQueue == nil
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api { if let api = api {
getTrashedAssetsForAlbumChannel.setMessageHandler { message, reply in getTrashedAssetsChannel.setMessageHandler { message, reply in
let args = message as! [Any?] let args = message as! [Any?]
let albumIdArg = args[0] as! String let albumIdsArg = args[0] as! [String]
let sinceLastCheckpointArg = args[1] as! Bool
do { do {
let result = try api.getTrashedAssetsForAlbum(albumId: albumIdArg) let result = try api.getTrashedAssets(albumIds: albumIdsArg, sinceLastCheckpoint: sinceLastCheckpointArg)
reply(wrapResult(result)) reply(wrapResult(result))
} catch { } catch {
reply(wrapError(error)) reply(wrapError(error))
} }
} }
} else { } else {
getTrashedAssetsForAlbumChannel.setMessageHandler(nil) getTrashedAssetsChannel.setMessageHandler(nil)
} }
} }
} }

View file

@ -39,9 +39,7 @@ class LocalSyncService {
} }
if (CurrentPlatform.isAndroid) { if (CurrentPlatform.isAndroid) {
final delta = await _nativeSyncApi.getMediaChanges(isTrashed: true); await _syncTrashedAssets(sinceLastCheckpoint: true);
_log.fine("Delta updated in trash: ${delta.updates.length - delta.updates.length}");
await _applyTrashDelta(delta);
} }
final delta = await _nativeSyncApi.getMediaChanges(); final delta = await _nativeSyncApi.getMediaChanges();
@ -98,10 +96,6 @@ class LocalSyncService {
try { try {
final Stopwatch stopwatch = Stopwatch()..start(); final Stopwatch stopwatch = Stopwatch()..start();
if (CurrentPlatform.isAndroid) {
await _syncDeviceTrashSnapshot();
}
final deviceAlbums = await _nativeSyncApi.getAlbums(); final deviceAlbums = await _nativeSyncApi.getAlbums();
final dbAlbums = await _localAlbumRepository.getAll(sortBy: {SortLocalAlbumsBy.id}); final dbAlbums = await _localAlbumRepository.getAll(sortBy: {SortLocalAlbumsBy.id});
@ -113,6 +107,9 @@ class LocalSyncService {
onlyFirst: removeAlbum, onlyFirst: removeAlbum,
onlySecond: addAlbum, onlySecond: addAlbum,
); );
if (CurrentPlatform.isAndroid) {
await _syncTrashedAssets(sinceLastCheckpoint: false);
}
await _nativeSyncApi.checkpointSync(); await _nativeSyncApi.checkpointSync();
stopwatch.stop(); stopwatch.stop();
@ -293,39 +290,37 @@ class LocalSyncService {
return a.name == b.name && a.assetCount == b.assetCount && a.updatedAt.isAtSameMomentAs(b.updatedAt); return a.name == b.name && a.assetCount == b.assetCount && a.updatedAt.isAtSameMomentAs(b.updatedAt);
} }
Future<void> _applyTrashDelta(SyncDelta delta) async { Future<void> _syncTrashedAssets({required bool sinceLastCheckpoint}) async {
final trashUpdates = delta.updates;
if (trashUpdates.isEmpty) {
return Future.value();
}
final trashedAssets = delta.toTrashedAssets();
_log.info("updateLocalTrashChanges trashedAssets: ${trashedAssets.map((e) => e.asset.id)}");
await _trashedLocalAssetRepository.applyDelta(trashedAssets);
await _applyRemoteRestoreToLocal();
}
Future<void> _syncDeviceTrashSnapshot() async {
final backupAlbums = await _localAlbumRepository.getBackupAlbums(); final backupAlbums = await _localAlbumRepository.getBackupAlbums();
if (backupAlbums.isEmpty) { if (backupAlbums.isEmpty) {
_log.info("syncDeviceTrashSnapshot, No backup albums found"); _log.info("syncTrashedAssets, No local backup albums found");
return; return;
} }
for (final album in backupAlbums) { final albumIds = backupAlbums.map((e) => e.id).toList();
_log.info("syncDeviceTrashSnapshot prepare, album: ${album.id}/${album.name}"); final trashedAssetMap = await _nativeSyncApi.getTrashedAssets(
final trashedPlatformAssets = await _nativeSyncApi.getTrashedAssetsForAlbum(album.id); albumIds: albumIds,
final trashedAssets = trashedPlatformAssets.toTrashedAssets(album.id); sinceLastCheckpoint: sinceLastCheckpoint,
await _trashedLocalAssetRepository.applyTrashSnapshot(trashedAssets); );
if (trashedAssetMap.isEmpty) {
_log.info("syncTrashedAssets, No trashed assets found ${sinceLastCheckpoint ? "since Last Checkpoint" : ""}");
}
final trashedAssets = trashedAssetMap.cast<String, List<Object?>>().entries.expand(
(entry) => entry.value.cast<PlatformAsset>().toTrashedAssets(entry.key),
);
_log.fine("syncTrashedAssets, trashedAssets: ${trashedAssets.map((e) => e.asset.id)}");
if (sinceLastCheckpoint) {
await _trashedLocalAssetRepository.applyDelta(trashedAssets);
} else {
await _trashedLocalAssetRepository.applySnapshot(trashedAssets);
} }
await _applyRemoteRestoreToLocal();
}
Future<void> _applyRemoteRestoreToLocal() async {
final remoteAssetsToRestore = await _trashedLocalAssetRepository.getToRestore(); final remoteAssetsToRestore = await _trashedLocalAssetRepository.getToRestore();
if (remoteAssetsToRestore.isNotEmpty) { if (remoteAssetsToRestore.isNotEmpty) {
final restoredIds = await _localFilesManager.restoreAssetsFromTrash(remoteAssetsToRestore); final restoredIds = await _localFilesManager.restoreAssetsFromTrash(remoteAssetsToRestore);
await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds);
} else { } else {
_log.info("No remote assets found for restoration"); _log.info("syncTrashedAssets, No remote assets found for restoration");
} }
} }
} }

View file

@ -59,7 +59,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
/// Applies resulted snapshot of trashed assets: /// Applies resulted snapshot of trashed assets:
/// - upserts incoming rows /// - upserts incoming rows
/// - deletes rows that are not present in the snapshot /// - deletes rows that are not present in the snapshot
Future<void> applyTrashSnapshot(Iterable<TrashedAsset> trashedAssets) async { Future<void> applySnapshot(Iterable<TrashedAsset> trashedAssets) async {
if (trashedAssets.isEmpty) { if (trashedAssets.isEmpty) {
await _db.delete(_db.trashedLocalAssetEntity).go(); await _db.delete(_db.trashedLocalAssetEntity).go();
return; return;

View file

@ -326,7 +326,7 @@ class NativeSyncApi {
} }
} }
Future<SyncDelta> getMediaChanges({bool isTrashed = false}) async { Future<SyncDelta> getMediaChanges() async {
final String pigeonVar_channelName = final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$pigeonVar_messageChannelSuffix'; 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
@ -334,7 +334,7 @@ class NativeSyncApi {
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[isTrashed]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?; final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) { if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName); throw _createConnectionError(pigeonVar_channelName);
@ -563,15 +563,18 @@ class NativeSyncApi {
} }
} }
Future<List<PlatformAsset>> getTrashedAssetsForAlbum(String albumId) async { Future<Map<String, List<PlatformAsset>>> getTrashedAssets({
required List<String> albumIds,
required bool sinceLastCheckpoint,
}) async {
final String pigeonVar_channelName = final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$pigeonVar_messageChannelSuffix'; 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[albumId]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[albumIds, sinceLastCheckpoint]);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?; final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) { if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName); throw _createConnectionError(pigeonVar_channelName);
@ -587,7 +590,7 @@ class NativeSyncApi {
message: 'Host platform returned null value for non-null return value.', message: 'Host platform returned null value for non-null return value.',
); );
} else { } else {
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<PlatformAsset>(); return (pigeonVar_replyList[0] as Map<Object?, Object?>?)!.cast<String, List<PlatformAsset>>();
} }
} }
} }

View file

@ -88,7 +88,7 @@ abstract class NativeSyncApi {
bool shouldFullSync(); bool shouldFullSync();
@TaskQueue(type: TaskQueueType.serialBackgroundThread) @TaskQueue(type: TaskQueueType.serialBackgroundThread)
SyncDelta getMediaChanges({bool isTrashed = false}); SyncDelta getMediaChanges();
void checkpointSync(); void checkpointSync();
@ -113,5 +113,8 @@ abstract class NativeSyncApi {
void cancelHashing(); void cancelHashing();
@TaskQueue(type: TaskQueueType.serialBackgroundThread) @TaskQueue(type: TaskQueueType.serialBackgroundThread)
List<PlatformAsset> getTrashedAssetsForAlbum(String albumId); Map<String, List<PlatformAsset>> getTrashedAssets({
required List<String> albumIds,
required bool sinceLastCheckpoint,
});
} }