trashed_local_asset table mirror of local_asset table structure

trashed_local_asset<->local_asset transfer data on move to trash or restore
refactor code
This commit is contained in:
Peter Ombodi 2025-09-24 16:58:56 +03:00
parent b15056deb9
commit bec1b30554
19 changed files with 1191 additions and 620 deletions

View file

@ -90,8 +90,8 @@ data class PlatformAsset (
val durationInSeconds: Long, val durationInSeconds: Long,
val orientation: Long, val orientation: Long,
val isFavorite: Boolean, val isFavorite: Boolean,
val isTrashed: Boolean, val isTrashed: Boolean? = null,
val size: Long? = null val volume: String? = null
) )
{ {
companion object { companion object {
@ -106,9 +106,9 @@ data class PlatformAsset (
val durationInSeconds = pigeonVar_list[7] as Long val durationInSeconds = pigeonVar_list[7] as Long
val orientation = pigeonVar_list[8] as Long val orientation = pigeonVar_list[8] as Long
val isFavorite = pigeonVar_list[9] as Boolean val isFavorite = pigeonVar_list[9] as Boolean
val isTrashed = pigeonVar_list[10] as Boolean val isTrashed = pigeonVar_list[10] as Boolean?
val size = pigeonVar_list[11] as Long? val volume = pigeonVar_list[11] as String?
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, isTrashed, size) return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, isTrashed, volume)
} }
} }
fun toList(): List<Any?> { fun toList(): List<Any?> {
@ -124,7 +124,7 @@ data class PlatformAsset (
orientation, orientation,
isFavorite, isFavorite,
isTrashed, isTrashed,
size, volume,
) )
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -345,7 +345,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(): SyncDelta fun getMediaChanges(isTrashed: Boolean): SyncDelta
fun checkpointSync() fun checkpointSync()
fun clearSyncCheckpoint() fun clearSyncCheckpoint()
fun getAssetIdsForAlbum(albumId: String): List<String> fun getAssetIdsForAlbum(albumId: String): List<String>
@ -354,7 +354,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, updatedTimeCond: Long?): List<PlatformAsset> fun getTrashedAssetsForAlbum(albumId: String): List<PlatformAsset>
fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>, callback: (Result<List<HashResult>>) -> Unit) fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>, callback: (Result<List<HashResult>>) -> Unit)
companion object { companion object {
@ -385,9 +385,11 @@ 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 { _, reply -> channel.setMessageHandler { message, 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()) listOf(api.getMediaChanges(isTrashedArg))
} catch (exception: Throwable) { } catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception) MessagesPigeonUtils.wrapError(exception)
} }
@ -540,9 +542,8 @@ interface NativeSyncApi {
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 albumIdArg = args[0] as String
val updatedTimeCondArg = args[1] as Long?
val wrapped: List<Any?> = try { val wrapped: List<Any?> = try {
listOf(api.getTrashedAssetsForAlbum(albumIdArg, updatedTimeCondArg)) listOf(api.getTrashedAssetsForAlbum(albumIdArg))
} catch (exception: Throwable) { } catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception) MessagesPigeonUtils.wrapError(exception)
} }

View file

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

View file

@ -45,21 +45,90 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
} }
} }
override fun shouldFullSync(): Boolean =
MediaStore.getVersion(ctx) != prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null)
override fun checkpointSync() {
val genMap = MediaStore.getExternalVolumeNames(ctx)
.associateWith { MediaStore.getGeneration(ctx, it) }
prefs.edit().apply {
putString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, MediaStore.getVersion(ctx))
putString(SHARED_PREF_MEDIA_STORE_GEN_KEY, Json.encodeToString(genMap))
apply()
}
}
override fun getMediaChanges(isTrashed: Boolean): SyncDelta {
val genMap = getSavedGenerationMap()
val currentVolumes = MediaStore.getExternalVolumeNames(ctx)
val changed = mutableListOf<PlatformAsset>()
val deleted = mutableListOf<String>()
val assetAlbums = mutableMapOf<String, List<String>>()
var hasChanges = genMap.keys != currentVolumes
for (volume in currentVolumes) {
val currentGen = MediaStore.getGeneration(ctx, volume)
val storedGen = genMap[volume] ?: 0
if (currentGen <= storedGen) {
continue
}
hasChanges = true
val selection =
"$MEDIA_SELECTION AND (${MediaStore.MediaColumns.GENERATION_MODIFIED} > ? OR ${MediaStore.MediaColumns.GENERATION_ADDED} > ?)"
val selectionArgs = arrayOf(
*MEDIA_SELECTION_ARGS,
storedGen.toString(),
storedGen.toString()
)
if (isTrashed) {
val uri = MediaStore.Files.getContentUri(volume)
val queryArgs = Bundle().apply {
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection)
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs)
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
}
ctx.contentResolver.query(uri, ASSET_PROJECTION, queryArgs, null).use { cursor ->
getAssets(cursor).forEach {
when (it) {
is AssetResult.ValidAsset -> {
changed.add(it.asset)
assetAlbums[it.asset.id] = listOf(it.albumId)
}
is AssetResult.InvalidAsset -> deleted.add(it.assetId)
}
}
}
} else {
getAssets(getCursor(volume, selection, selectionArgs)).forEach {
when (it) {
is AssetResult.ValidAsset -> {
changed.add(it.asset)
assetAlbums[it.asset.id] = listOf(it.albumId)
}
is AssetResult.InvalidAsset -> deleted.add(it.assetId)
}
}
}
}
// Unmounted volumes are handled in dart when the album is removed
return SyncDelta(hasChanges, changed, deleted, assetAlbums)
}
override fun getTrashedAssetsForAlbum( override fun getTrashedAssetsForAlbum(
albumId: String, albumId: String
updatedTimeCond: Long?
): List<PlatformAsset> { ): List<PlatformAsset> {
val trashed = mutableListOf<PlatformAsset>() val trashed = mutableListOf<PlatformAsset>()
val volumes = MediaStore.getExternalVolumeNames(ctx) val volumes = MediaStore.getExternalVolumeNames(ctx)
var selection = "$BUCKET_SELECTION AND $MEDIA_SELECTION" val selection = "$BUCKET_SELECTION AND $MEDIA_SELECTION"
val selectionArgs = mutableListOf(albumId, *MEDIA_SELECTION_ARGS) val selectionArgs = mutableListOf(albumId, *MEDIA_SELECTION_ARGS)
if (updatedTimeCond != null) {
selection += " AND (${MediaStore.Files.FileColumns.DATE_MODIFIED} > ? OR ${MediaStore.Files.FileColumns.DATE_ADDED} > ?)"
selectionArgs.addAll(listOf(updatedTimeCond.toString(), updatedTimeCond.toString()))
}
for (volume in volumes) { for (volume in volumes) {
val uri = MediaStore.Files.getContentUri(volume) val uri = MediaStore.Files.getContentUri(volume)
val queryArgs = Bundle().apply { val queryArgs = Bundle().apply {
@ -114,68 +183,6 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
} }
} }
override fun shouldFullSync(): Boolean =
MediaStore.getVersion(ctx) != prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null)
override fun checkpointSync() {
val genMap = MediaStore.getExternalVolumeNames(ctx)
.associateWith { MediaStore.getGeneration(ctx, it) }
prefs.edit().apply {
putString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, MediaStore.getVersion(ctx))
putString(SHARED_PREF_MEDIA_STORE_GEN_KEY, Json.encodeToString(genMap))
apply()
}
}
override fun getMediaChanges(): SyncDelta {
val genMap = getSavedGenerationMap()
val currentVolumes = MediaStore.getExternalVolumeNames(ctx)
val changed = mutableListOf<PlatformAsset>()
val deleted = mutableListOf<String>()
val assetAlbums = mutableMapOf<String, List<String>>()
var hasChanges = genMap.keys != currentVolumes
for (volume in currentVolumes) {
val currentGen = MediaStore.getGeneration(ctx, volume)
val storedGen = genMap[volume] ?: 0
if (currentGen <= storedGen) {
continue
}
hasChanges = true
val selection =
"$MEDIA_SELECTION AND (${MediaStore.MediaColumns.GENERATION_MODIFIED} > ? OR ${MediaStore.MediaColumns.GENERATION_ADDED} > ?)"
val selectionArgs = arrayOf(
*MEDIA_SELECTION_ARGS,
storedGen.toString(),
storedGen.toString()
)
val uri = MediaStore.Files.getContentUri(volume)
val queryArgs = Bundle().apply {
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection)
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs)
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_INCLUDE)
}
ctx.contentResolver.query(uri, ASSET_PROJECTION, queryArgs, null).use { cursor ->
getAssets(cursor).forEach {
when (it) {
is AssetResult.ValidAsset -> {
changed.add(it.asset)
assetAlbums[it.asset.id] = listOf(it.albumId)
}
is AssetResult.InvalidAsset -> deleted.add(it.assetId)
}
}
}
}
// Unmounted volumes are handled in dart when the album is removed
return SyncDelta(hasChanges, changed, deleted, assetAlbums)
}
suspend fun hashTrashedAsset(assetParams: TrashedAssetParams): HashResult { suspend fun hashTrashedAsset(assetParams: TrashedAssetParams): HashResult {
val id = assetParams.id.toLong() val id = assetParams.id.toLong()
val mediaType = assetParams.type.toInt() val mediaType = assetParams.type.toInt()

View file

@ -58,13 +58,12 @@ open class NativeSyncApiImplBase(context: Context) {
add(MediaStore.MediaColumns.HEIGHT) add(MediaStore.MediaColumns.HEIGHT)
add(MediaStore.MediaColumns.DURATION) add(MediaStore.MediaColumns.DURATION)
add(MediaStore.MediaColumns.ORIENTATION) add(MediaStore.MediaColumns.ORIENTATION)
// IS_FAVORITE is only available on Android 11 and above // only available on Android 11 and above
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
add(MediaStore.MediaColumns.IS_FAVORITE) add(MediaStore.MediaColumns.IS_FAVORITE)
// IS_TRASHED available on Android 11+
add(MediaStore.MediaColumns.IS_TRASHED) add(MediaStore.MediaColumns.IS_TRASHED)
add(MediaStore.MediaColumns.VOLUME_NAME)
} }
add(MediaStore.MediaColumns.SIZE)
}.toTypedArray() }.toTypedArray()
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024 const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
@ -102,7 +101,7 @@ open class NativeSyncApiImplBase(context: Context) {
c.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION) c.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION)
val favoriteColumn = c.getColumnIndex(MediaStore.MediaColumns.IS_FAVORITE) val favoriteColumn = c.getColumnIndex(MediaStore.MediaColumns.IS_FAVORITE)
val trashedColumn = c.getColumnIndex(MediaStore.MediaColumns.IS_TRASHED) val trashedColumn = c.getColumnIndex(MediaStore.MediaColumns.IS_TRASHED)
val sizeColumn = c.getColumnIndex(MediaStore.MediaColumns.SIZE) val volumeColumn = c.getColumnIndex(MediaStore.MediaColumns.VOLUME_NAME)
while (c.moveToNext()) { while (c.moveToNext()) {
val id = c.getLong(idColumn).toString() val id = c.getLong(idColumn).toString()
@ -132,8 +131,8 @@ open class NativeSyncApiImplBase(context: Context) {
val bucketId = c.getString(bucketIdColumn) val bucketId = c.getString(bucketIdColumn)
val orientation = c.getInt(orientationColumn) val orientation = c.getInt(orientationColumn)
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0 val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0
val isTrashed = if (trashedColumn == -1) false else c.getInt(trashedColumn) != 0 val isTrashed = if (trashedColumn == -1) null else c.getInt(trashedColumn) != 0
val size = c.getLong(sizeColumn) val volume = if (volumeColumn == -1) null else c.getString(volumeColumn)
val asset = PlatformAsset( val asset = PlatformAsset(
id, id,
name, name,
@ -146,7 +145,7 @@ open class NativeSyncApiImplBase(context: Context) {
orientation.toLong(), orientation.toLong(),
isFavorite, isFavorite,
isTrashed, isTrashed,
size volume,
) )
yield(AssetResult.ValidAsset(asset, bucketId)) yield(AssetResult.ValidAsset(asset, bucketId))
} }

File diff suppressed because one or more lines are too long

View file

@ -140,8 +140,8 @@ struct PlatformAsset: Hashable {
var durationInSeconds: Int64 var durationInSeconds: Int64
var orientation: Int64 var orientation: Int64
var isFavorite: Bool var isFavorite: Bool
var isTrashed: Bool var isTrashed: Bool? = nil
var size: Int64? = nil var volume: String? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase // swift-format-ignore: AlwaysUseLowerCamelCase
@ -156,8 +156,8 @@ struct PlatformAsset: Hashable {
let durationInSeconds = pigeonVar_list[7] as! Int64 let durationInSeconds = pigeonVar_list[7] as! Int64
let orientation = pigeonVar_list[8] as! Int64 let orientation = pigeonVar_list[8] as! Int64
let isFavorite = pigeonVar_list[9] as! Bool let isFavorite = pigeonVar_list[9] as! Bool
let isTrashed = pigeonVar_list[10] as! Bool let isTrashed: Bool? = nilOrValue(pigeonVar_list[10])
let size: Int64? = nilOrValue(pigeonVar_list[11]) let volume: String? = nilOrValue(pigeonVar_list[11])
return PlatformAsset( return PlatformAsset(
id: id, id: id,
@ -171,7 +171,7 @@ struct PlatformAsset: Hashable {
orientation: orientation, orientation: orientation,
isFavorite: isFavorite, isFavorite: isFavorite,
isTrashed: isTrashed, isTrashed: isTrashed,
size: size volume: volume
) )
} }
func toList() -> [Any?] { func toList() -> [Any?] {
@ -187,7 +187,7 @@ struct PlatformAsset: Hashable {
orientation, orientation,
isFavorite, isFavorite,
isTrashed, isTrashed,
size, volume,
] ]
} }
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool { static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
@ -401,7 +401,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() throws -> SyncDelta func getMediaChanges(isTrashed: Bool) 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]
@ -410,7 +410,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, updatedTimeCond: Int64?) throws -> [PlatformAsset] func getTrashedAssetsForAlbum(albumId: String) throws -> [PlatformAsset]
func hashTrashedAssets(trashedAssets: [TrashedAssetParams], completion: @escaping (Result<[HashResult], Error>) -> Void) func hashTrashedAssets(trashedAssets: [TrashedAssetParams], completion: @escaping (Result<[HashResult], Error>) -> Void)
} }
@ -442,9 +442,11 @@ 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 { _, reply in getMediaChangesChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let isTrashedArg = args[0] as! Bool
do { do {
let result = try api.getMediaChanges() let result = try api.getMediaChanges(isTrashed: isTrashedArg)
reply(wrapResult(result)) reply(wrapResult(result))
} catch { } catch {
reply(wrapError(error)) reply(wrapError(error))
@ -587,9 +589,8 @@ class NativeSyncApiSetup {
getTrashedAssetsForAlbumChannel.setMessageHandler { message, reply in getTrashedAssetsForAlbumChannel.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
let updatedTimeCondArg: Int64? = nilOrValue(args[1])
do { do {
let result = try api.getTrashedAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) let result = try api.getTrashedAssetsForAlbum(albumId: albumIdArg)
reply(wrapResult(result)) reply(wrapResult(result))
} catch { } catch {
reply(wrapError(error)) reply(wrapError(error))

View file

@ -3,43 +3,43 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
class TrashedAsset { class TrashedAsset {
final String id; final String id;
final String name; final String name;
final String? volume;
final String albumId; final String albumId;
final String? checksum; final String? checksum;
final AssetType type; final AssetType type;
final DateTime createdAt; final DateTime createdAt;
final DateTime updatedAt; final DateTime updatedAt;
final int? size;
const TrashedAsset({ const TrashedAsset({
required this.id, required this.id,
required this.name, required this.name,
required this.checksum,
required this.albumId, required this.albumId,
required this.type, required this.type,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
this.size, this.volume,
this.checksum,
}); });
TrashedAsset copyWith({ TrashedAsset copyWith({
String? id, String? id,
String? name, String? name,
String? volume,
String? albumId, String? albumId,
String? checksum, String? checksum,
AssetType? type, AssetType? type,
DateTime? createdAt, DateTime? createdAt,
DateTime? updatedAt, DateTime? updatedAt,
int? size,
}) { }) {
return TrashedAsset( return TrashedAsset(
id: id ?? this.id, id: id ?? this.id,
name: name ?? this.name, name: name ?? this.name,
volume: volume ?? this.volume,
albumId: albumId ?? this.albumId, albumId: albumId ?? this.albumId,
checksum: checksum ?? this.checksum, checksum: checksum ?? this.checksum,
type: type ?? this.type, type: type ?? this.type,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
size: size ?? this.size,
); );
} }
@ -48,12 +48,12 @@ class TrashedAsset {
return 'TrashedAsset(' return 'TrashedAsset('
'id: $id, ' 'id: $id, '
'name: $name, ' 'name: $name, '
'volume: $volume, '
'albumId: $albumId, ' 'albumId: $albumId, '
'checksum: $checksum, ' 'checksum: $checksum, '
'type: $type, ' 'type: $type, '
'createdAt: $createdAt, ' 'createdAt: $createdAt, '
'updatedAt: $updatedAt, ' 'updatedAt: $updatedAt, '
'size: ${size ?? "<NA>"}'
')'; ')';
} }
@ -64,13 +64,13 @@ class TrashedAsset {
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
id == other.id && id == other.id &&
name == other.name && name == other.name &&
volume == other.volume &&
albumId == other.albumId && albumId == other.albumId &&
checksum == other.checksum && checksum == other.checksum &&
type == other.type && type == other.type &&
createdAt == other.createdAt && createdAt == other.createdAt &&
updatedAt == other.updatedAt && updatedAt == other.updatedAt;
size == other.size;
@override @override
int get hashCode => Object.hash(id, name, albumId, checksum, type, createdAt, updatedAt, size); int get hashCode => Object.hash(id, name, volume, albumId, checksum, type, createdAt, updatedAt);
} }

View file

@ -152,13 +152,6 @@ class HashService {
return; return;
} }
if (asset.size == null) {
_log.warning(
"Cannot get size for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt} from album: ${album.name}",
);
continue;
}
toHash.add(asset); toHash.add(asset);
if (toHash.length == _batchSize) { if (toHash.length == _batchSize) {

View file

@ -40,14 +40,13 @@ class LocalSyncService {
return; return;
} }
final updates = delta.updates.where((e) => !e.isTrashed); _log.fine("Delta updated: ${delta.updates.length}");
_log.fine("Delta updated assets: ${updates.length}");
_log.fine("Delta deleted: ${delta.deletes.length}"); _log.fine("Delta deleted: ${delta.deletes.length}");
final deviceAlbums = await _nativeSyncApi.getAlbums(); final deviceAlbums = await _nativeSyncApi.getAlbums();
await _localAlbumRepository.updateAll(deviceAlbums.toLocalAlbums()); await _localAlbumRepository.updateAll(deviceAlbums.toLocalAlbums());
await _localAlbumRepository.processDelta( await _localAlbumRepository.processDelta(
updates: updates.toLocalAssets(), updates: delta.updates.toLocalAssets(),
deletes: delta.deletes, deletes: delta.deletes,
assetAlbums: delta.assetAlbums, assetAlbums: delta.assetAlbums,
); );
@ -77,7 +76,8 @@ class LocalSyncService {
} }
} }
if (_trashSyncService.isAutoSyncMode) { if (_trashSyncService.isAutoSyncMode) {
_log.fine("Delta updated trashed: ${delta.updates.length - updates.length}"); final delta = await _nativeSyncApi.getMediaChanges(isTrashed: true);
_log.fine("Delta updated in trash: ${delta.updates.length - delta.updates.length}");
await _trashSyncService.applyTrashDelta(delta); await _trashSyncService.applyTrashDelta(delta);
} }
await _nativeSyncApi.checkpointSync(); await _nativeSyncApi.checkpointSync();

View file

@ -88,7 +88,7 @@ class SyncStreamService {
case SyncEntityType.assetV1: case SyncEntityType.assetV1:
final remoteSyncAssets = data.cast<SyncAssetV1>(); final remoteSyncAssets = data.cast<SyncAssetV1>();
if (_trashSyncService.isAutoSyncMode) { if (_trashSyncService.isAutoSyncMode) {
await _trashSyncService.handleRemoteChanges( await _trashSyncService.handleRemoteTrashed(
remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum), remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum),
); );
} }

View file

@ -56,15 +56,15 @@ class TrashSyncService {
} }
for (final album in backupAlbums) { for (final album in backupAlbums) {
_logger.info("deviceTrashedAssets prepare, album: ${album.id}/${album.name}"); _logger.info("deviceTrashedAssets prepare, album: ${album.id}/${album.name}");
final deviceTrashedAssets = await _nativeSyncApi.getTrashedAssetsForAlbum(album.id); final trashedPlatformAssets = await _nativeSyncApi.getTrashedAssetsForAlbum(album.id);
await _trashedLocalAssetRepository.applyTrashSnapshot(deviceTrashedAssets.toTrashedAssets(album.id), album.id); final trashedAssets = trashedPlatformAssets.toTrashedAssets(album.id);
await _trashedLocalAssetRepository.applyTrashSnapshot(trashedAssets, album.id);
} }
// todo find for more suitable place
await applyRemoteRestoreToLocal(); await applyRemoteRestoreToLocal();
} }
Future<void> applyTrashDelta(SyncDelta delta) async { Future<void> applyTrashDelta(SyncDelta delta) async {
final trashUpdates = delta.updates.where((e) => e.isTrashed); final trashUpdates = delta.updates;
if (trashUpdates.isEmpty) { if (trashUpdates.isEmpty) {
return Future.value(); return Future.value();
} }
@ -77,11 +77,10 @@ class TrashSyncService {
} }
_logger.info("updateLocalTrashChanges trashedAssets: ${trashedAssets.map((e) => e.id)}"); _logger.info("updateLocalTrashChanges trashedAssets: ${trashedAssets.map((e) => e.id)}");
await _trashedLocalAssetRepository.insertTrashDelta(trashedAssets); await _trashedLocalAssetRepository.insertTrashDelta(trashedAssets);
// todo find for more suitable place
await applyRemoteRestoreToLocal(); await applyRemoteRestoreToLocal();
} }
Future<void> handleRemoteChanges(Iterable<String> checksums) async { Future<void> handleRemoteTrashed(Iterable<String> checksums) async {
if (checksums.isEmpty) { if (checksums.isEmpty) {
return Future.value(); return Future.value();
} else { } else {
@ -95,7 +94,7 @@ class TrashSyncService {
_logger.info("Moving to trash ${mediaUrls.join(", ")} assets"); _logger.info("Moving to trash ${mediaUrls.join(", ")} assets");
final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
if (result) { if (result) {
await _localAssetRepository.trash(localAssetsToTrash); await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
} }
} else { } else {
_logger.info("No assets found in backup-enabled albums for assets: $checksums"); _logger.info("No assets found in backup-enabled albums for assets: $checksums");
@ -111,12 +110,7 @@ class TrashSyncService {
_logger.info("Restoring from trash, localId: ${asset.id}, remoteId: ${asset.checksum}"); _logger.info("Restoring from trash, localId: ${asset.id}, remoteId: ${asset.checksum}");
await _localFilesManager.restoreFromTrashById(asset.id, asset.type.index); await _localFilesManager.restoreFromTrashById(asset.id, asset.type.index);
} }
// todo 19/09/2025 await _trashedLocalAssetRepository.restoreLocalAssets(remoteAssetsToRestore.map((e) => e.id));
// 1. keeping full mirror of local asset table struct + size into trashedLocalAssetEntity could help to restore assets here
// 2. now when hash calculating doing without taking into account size of files, size field may be redundant
// todo It`s necessary? could cause race with deletion in applyTrashSnapshot? 18/09/2025
await _trashedLocalAssetRepository.delete(remoteAssetsToRestore.map((e) => e.id));
} else { } else {
_logger.info("No remote assets found for restoration"); _logger.info("No remote assets found for restoration");
} }
@ -131,7 +125,7 @@ extension on PlatformAsset {
type: AssetType.values.elementAtOrNull(type) ?? AssetType.other, type: AssetType.values.elementAtOrNull(type) ?? AssetType.other,
createdAt: tryFromSecondsSinceEpoch(createdAt) ?? DateTime.now(), createdAt: tryFromSecondsSinceEpoch(createdAt) ?? DateTime.now(),
updatedAt: tryFromSecondsSinceEpoch(updatedAt) ?? DateTime.now(), updatedAt: tryFromSecondsSinceEpoch(updatedAt) ?? DateTime.now(),
size: size, volume: volume,
albumId: albumId, albumId: albumId,
); );
} }

View file

@ -1,42 +1,39 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/asset/trashed_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/trashed_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)')
class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin { class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
const TrashedLocalAssetEntity(); const TrashedLocalAssetEntity();
TextColumn get id => text()(); TextColumn get id => text()();
TextColumn get albumId => text()(); TextColumn get albumId => text()();
TextColumn get volume => text().nullable()();
TextColumn get checksum => text().nullable()(); TextColumn get checksum => text().nullable()();
TextColumn get name => text()(); BoolColumn get isFavorite => boolean().withDefault(const Constant(false))();
IntColumn get type => intEnum<AssetType>()(); IntColumn get orientation => integer().withDefault(const Constant(0))();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
IntColumn get size => integer().nullable()();
@override @override
Set<Column> get primaryKey => {id}; Set<Column> get primaryKey => {id, albumId};
} }
extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityData { extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityData {
TrashedAsset toDto(String albumId) => TrashedAsset( TrashedAsset toDto() => TrashedAsset(
id: id, id: id,
name: name, name: name,
volume: volume,
albumId: albumId, albumId: albumId,
checksum: checksum, checksum: checksum,
type: type, type: type,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
size: size,
); );
} }

File diff suppressed because it is too large Load diff

View file

@ -5038,16 +5038,21 @@ final class Schema12 extends i0.VersionedSchema {
entityName: 'trashed_local_asset_entity', entityName: 'trashed_local_asset_entity',
withoutRowId: true, withoutRowId: true,
isStrict: true, isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'], tableConstraints: ['PRIMARY KEY(id, album_id)'],
columns: [ columns: [
_column_0,
_column_95,
_column_22,
_column_1, _column_1,
_column_8, _column_8,
_column_9, _column_9,
_column_5, _column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_95,
_column_96, _column_96,
_column_22,
_column_14,
_column_23,
], ],
attachedDatabase: database, attachedDatabase: database,
), ),
@ -5065,12 +5070,6 @@ final class Schema12 extends i0.VersionedSchema {
class Shape23 extends i0.VersionedTable { class Shape23 extends i0.VersionedTable {
Shape23({required super.source, required super.alias}) : super.aliased(); Shape23({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get albumId =>
columnsByName['album_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get checksum =>
columnsByName['checksum']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get name => i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>; columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get type => i1.GeneratedColumn<int> get type =>
@ -5079,8 +5078,24 @@ class Shape23 extends i0.VersionedTable {
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>; columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get updatedAt => i1.GeneratedColumn<DateTime> get updatedAt =>
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>; columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<int> get size => i1.GeneratedColumn<int> get width =>
columnsByName['size']! as i1.GeneratedColumn<int>; columnsByName['width']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get height =>
columnsByName['height']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get durationInSeconds =>
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get albumId =>
columnsByName['album_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get volume =>
columnsByName['volume']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get checksum =>
columnsByName['checksum']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get isFavorite =>
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<int> get orientation =>
columnsByName['orientation']! as i1.GeneratedColumn<int>;
} }
i1.GeneratedColumn<String> _column_95(String aliasedName) => i1.GeneratedColumn<String> _column_95(String aliasedName) =>
@ -5090,12 +5105,12 @@ i1.GeneratedColumn<String> _column_95(String aliasedName) =>
false, false,
type: i1.DriftSqlType.string, type: i1.DriftSqlType.string,
); );
i1.GeneratedColumn<int> _column_96(String aliasedName) => i1.GeneratedColumn<String> _column_96(String aliasedName) =>
i1.GeneratedColumn<int>( i1.GeneratedColumn<String>(
'size', 'volume',
aliasedName, aliasedName,
true, true,
type: i1.DriftSqlType.int, type: i1.DriftSqlType.string,
); );
i0.MigrationStepWithVersion migrationSteps({ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2, required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,

View file

@ -5,10 +5,9 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
typedef AssetsByAlbums = Map<String, List<LocalAsset>>; typedef AlbumId = String;
class DriftLocalAssetRepository extends DriftDatabaseRepository { class DriftLocalAssetRepository extends DriftDatabaseRepository {
final Drift _db; final Drift _db;
@ -68,41 +67,6 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
}); });
} }
Future<void> trash(AssetsByAlbums assetsByAlbums) async {
if (assetsByAlbums.isEmpty) {
return;
}
final companions = <TrashedLocalAssetEntityCompanion>[];
final idToDelete = <String>{};
assetsByAlbums.forEach((albumId, assets) {
for (final asset in assets) {
idToDelete.add(asset.id);
companions.add(
TrashedLocalAssetEntityCompanion(
id: Value(asset.id),
name: Value(asset.name),
albumId: Value(albumId),
checksum: asset.checksum == null ? const Value.absent() : Value(asset.checksum),
type: Value(asset.type),
),
);
}
});
await _db.transaction(() async {
for (final slice in companions.slices(200)) {
await _db.batch((batch) {
batch.insertAllOnConflictUpdate(_db.trashedLocalAssetEntity, slice);
});
}
for (final slice in idToDelete.slices(800)) {
await (_db.delete(_db.localAssetEntity)..where((e) => e.id.isIn(slice))).go();
}
});
}
Future<LocalAsset?> getById(String id) { Future<LocalAsset?> getById(String id) {
final query = _db.localAssetEntity.select()..where((lae) => lae.id.equals(id)); final query = _db.localAssetEntity.select()..where((lae) => lae.id.equals(id));
@ -136,7 +100,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
return query.map((localAlbum) => localAlbum.toDto()).get(); return query.map((localAlbum) => localAlbum.toDto()).get();
} }
Future<AssetsByAlbums> getBackupSelectedAssetsByAlbum(Iterable<String> checksums) async { Future<Map<AlbumId, List<LocalAsset>>> getBackupSelectedAssetsByAlbum(Iterable<String> checksums) async {
if (checksums.isEmpty) { if (checksums.isEmpty) {
return {}; return {};
} }

View file

@ -1,10 +1,13 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/asset/trashed_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/trashed_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository { class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
final Drift _db; final Drift _db;
@ -29,10 +32,10 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
Future<Iterable<TrashedAsset>> getToHash(String albumId) { Future<Iterable<TrashedAsset>> getToHash(String albumId) {
final query = _db.trashedLocalAssetEntity.select()..where((r) => r.albumId.equals(albumId) & r.checksum.isNull()); final query = _db.trashedLocalAssetEntity.select()..where((r) => r.albumId.equals(albumId) & r.checksum.isNull());
return query.map((row) => row.toDto(albumId)).get(); return query.map((row) => row.toDto()).get();
} }
Future<List<TrashedAsset>> getToRestore() async { Future<Iterable<TrashedAsset>> getToRestore() async {
final trashed = _db.trashedLocalAssetEntity; final trashed = _db.trashedLocalAssetEntity;
final remote = _db.remoteAssetEntity; final remote = _db.remoteAssetEntity;
final album = _db.localAlbumEntity; final album = _db.localAlbumEntity;
@ -45,10 +48,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
innerJoin(remote, remote.checksum.equalsExp(trashed.checksum)), innerJoin(remote, remote.checksum.equalsExp(trashed.checksum)),
])..where(trashed.albumId.isInQuery(selectedAlbumIds) & remote.deletedAt.isNull())).get(); ])..where(trashed.albumId.isInQuery(selectedAlbumIds) & remote.deletedAt.isNull())).get();
return rows.map((result) { return rows.map((result) => result.readTable(trashed).toDto());
final assetData = result.readTable(trashed);
return assetData.toDto(assetData.albumId);
}).toList();
} }
/// Applies resulted snapshot of trashed assets: /// Applies resulted snapshot of trashed assets:
@ -67,12 +67,12 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
(a) => TrashedLocalAssetEntityCompanion.insert( (a) => TrashedLocalAssetEntityCompanion.insert(
id: a.id, id: a.id,
albumId: albumId, albumId: albumId,
volume: a.volume == null ? const Value.absent() : Value(a.volume),
checksum: a.checksum == null ? const Value.absent() : Value(a.checksum), checksum: a.checksum == null ? const Value.absent() : Value(a.checksum),
name: a.name, name: a.name,
type: a.type, type: a.type,
createdAt: Value(a.createdAt), createdAt: Value(a.createdAt),
updatedAt: Value(a.updatedAt), updatedAt: Value(a.updatedAt),
size: a.size == null ? const Value.absent() : Value(a.size),
), ),
); );
@ -102,11 +102,11 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
final companions = trashUpdates.map( final companions = trashUpdates.map(
(a) => TrashedLocalAssetEntityCompanion.insert( (a) => TrashedLocalAssetEntityCompanion.insert(
id: a.id, id: a.id,
volume: a.volume == null ? const Value.absent() : Value(a.volume),
albumId: a.albumId, albumId: a.albumId,
name: a.name, name: a.name,
type: a.type, type: a.type,
checksum: a.checksum == null ? const Value.absent() : Value(a.checksum), checksum: a.checksum == null ? const Value.absent() : Value(a.checksum),
size: a.size == null ? const Value.absent() : Value(a.size),
createdAt: Value(a.createdAt), createdAt: Value(a.createdAt),
), ),
); );
@ -132,14 +132,80 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
.map((row) => row.read<int>(t.id.count()) ?? 0); .map((row) => row.read<int>(t.id.count()) ?? 0);
} }
Future<void> delete(Iterable<String> ids) { Future<void> trashLocalAsset(Map<AlbumId, List<LocalAsset>> assetsByAlbums) async {
if (ids.isEmpty) { if (assetsByAlbums.isEmpty) {
return Future.value(); return;
} }
return _db.batch((batch) {
for (final slice in ids.slices(32000)) { final companions = <TrashedLocalAssetEntityCompanion>[];
batch.deleteWhere(_db.trashedLocalAssetEntity, (e) => e.id.isIn(slice)); final idToDelete = <String>{};
assetsByAlbums.forEach((albumId, assets) {
for (final asset in assets) {
idToDelete.add(asset.id);
companions.add(
TrashedLocalAssetEntityCompanion(
id: Value(asset.id),
name: Value(asset.name),
albumId: Value(albumId),
checksum: asset.checksum == null ? const Value.absent() : Value(asset.checksum),
type: Value(asset.type),
width: Value(asset.width),
height: Value(asset.height),
durationInSeconds: Value(asset.durationInSeconds),
isFavorite: Value(asset.isFavorite),
orientation: Value(asset.orientation),
),
);
}
});
await _db.transaction(() async {
for (final slice in companions.slices(200)) {
await _db.batch((batch) {
batch.insertAllOnConflictUpdate(_db.trashedLocalAssetEntity, slice);
});
}
for (final slice in idToDelete.slices(800)) {
await (_db.delete(_db.localAssetEntity)..where((e) => e.id.isIn(slice))).go();
} }
}); });
} }
Future<void> restoreLocalAssets(Iterable<String> ids) async {
if (ids.isEmpty) {
return;
}
final trashedAssets = await (_db.select(_db.trashedLocalAssetEntity)..where((tbl) => tbl.id.isIn(ids))).get();
if (trashedAssets.isEmpty) {
return;
}
final localAssets = trashedAssets.map((e) {
return LocalAssetEntityCompanion.insert(
id: e.id,
name: e.name,
type: e.type,
createdAt: Value(e.createdAt),
updatedAt: Value(e.updatedAt),
width: Value(e.width),
height: Value(e.height),
durationInSeconds: Value(e.durationInSeconds),
checksum: Value(e.checksum),
isFavorite: Value(e.isFavorite),
orientation: Value(e.orientation),
);
}).toList();
await _db.transaction(() async {
await _db.batch((batch) {
batch.insertAllOnConflictUpdate(_db.localAssetEntity, localAssets);
for (final slice in ids.slices(32000)) {
batch.deleteWhere(_db.trashedLocalAssetEntity, (tbl) => tbl.id.isIn(slice));
}
});
});
}
} }

View file

@ -41,8 +41,8 @@ class PlatformAsset {
required this.durationInSeconds, required this.durationInSeconds,
required this.orientation, required this.orientation,
required this.isFavorite, required this.isFavorite,
required this.isTrashed, this.isTrashed,
this.size, this.volume,
}); });
String id; String id;
@ -65,9 +65,9 @@ class PlatformAsset {
bool isFavorite; bool isFavorite;
bool isTrashed; bool? isTrashed;
int? size; String? volume;
List<Object?> _toList() { List<Object?> _toList() {
return <Object?>[ return <Object?>[
@ -82,7 +82,7 @@ class PlatformAsset {
orientation, orientation,
isFavorite, isFavorite,
isTrashed, isTrashed,
size, volume,
]; ];
} }
@ -103,8 +103,8 @@ class PlatformAsset {
durationInSeconds: result[7]! as int, durationInSeconds: result[7]! as int,
orientation: result[8]! as int, orientation: result[8]! as int,
isFavorite: result[9]! as bool, isFavorite: result[9]! as bool,
isTrashed: result[10]! as bool, isTrashed: result[10] as bool?,
size: result[11] as int?, volume: result[11] as String?,
); );
} }
@ -391,7 +391,7 @@ class NativeSyncApi {
} }
} }
Future<SyncDelta> getMediaChanges() async { Future<SyncDelta> getMediaChanges({bool isTrashed = false}) 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?>(
@ -399,7 +399,7 @@ class NativeSyncApi {
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[isTrashed]);
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);
@ -628,7 +628,7 @@ class NativeSyncApi {
} }
} }
Future<List<PlatformAsset>> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond}) async { Future<List<PlatformAsset>> getTrashedAssetsForAlbum(String albumId) async {
final String pigeonVar_channelName = final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$pigeonVar_messageChannelSuffix'; 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
@ -636,7 +636,7 @@ class NativeSyncApi {
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[albumId, updatedTimeCond]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[albumId]);
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);

View file

@ -24,8 +24,8 @@ class PlatformAsset {
final int durationInSeconds; final int durationInSeconds;
final int orientation; final int orientation;
final bool isFavorite; final bool isFavorite;
final bool isTrashed; final bool? isTrashed;
final int? size; final String? volume;
const PlatformAsset({ const PlatformAsset({
required this.id, required this.id,
@ -38,8 +38,8 @@ class PlatformAsset {
this.durationInSeconds = 0, this.durationInSeconds = 0,
this.orientation = 0, this.orientation = 0,
this.isFavorite = false, this.isFavorite = false,
this.isTrashed = false, this.isTrashed,
this.size, this.volume,
}); });
} }
@ -100,7 +100,7 @@ abstract class NativeSyncApi {
bool shouldFullSync(); bool shouldFullSync();
@TaskQueue(type: TaskQueueType.serialBackgroundThread) @TaskQueue(type: TaskQueueType.serialBackgroundThread)
SyncDelta getMediaChanges(); SyncDelta getMediaChanges({bool isTrashed = false});
void checkpointSync(); void checkpointSync();
@ -125,7 +125,7 @@ abstract class NativeSyncApi {
void cancelHashing(); void cancelHashing();
@TaskQueue(type: TaskQueueType.serialBackgroundThread) @TaskQueue(type: TaskQueueType.serialBackgroundThread)
List<PlatformAsset> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond}); List<PlatformAsset> getTrashedAssetsForAlbum(String albumId);
@async @async
@TaskQueue(type: TaskQueueType.serialBackgroundThread) @TaskQueue(type: TaskQueueType.serialBackgroundThread)

View file

@ -7119,27 +7119,6 @@ class TrashedLocalAssetEntity extends Table
final GeneratedDatabase attachedDatabase; final GeneratedDatabase attachedDatabase;
final String? _alias; final String? _alias;
TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<String> id = GeneratedColumn<String>(
'id',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<String> albumId = GeneratedColumn<String>(
'album_id',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<String> checksum = GeneratedColumn<String>(
'checksum',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
late final GeneratedColumn<String> name = GeneratedColumn<String>( late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', 'name',
aliasedName, aliasedName,
@ -7170,23 +7149,89 @@ class TrashedLocalAssetEntity extends Table
requiredDuringInsert: false, requiredDuringInsert: false,
defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), defaultValue: const CustomExpression('CURRENT_TIMESTAMP'),
); );
late final GeneratedColumn<int> size = GeneratedColumn<int>( late final GeneratedColumn<int> width = GeneratedColumn<int>(
'size', 'width',
aliasedName, aliasedName,
true, true,
type: DriftSqlType.int, type: DriftSqlType.int,
requiredDuringInsert: false, requiredDuringInsert: false,
); );
late final GeneratedColumn<int> height = GeneratedColumn<int>(
'height',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
late final GeneratedColumn<int> durationInSeconds = GeneratedColumn<int>(
'duration_in_seconds',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
late final GeneratedColumn<String> id = GeneratedColumn<String>(
'id',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<String> albumId = GeneratedColumn<String>(
'album_id',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<String> volume = GeneratedColumn<String>(
'volume',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
late final GeneratedColumn<String> checksum = GeneratedColumn<String>(
'checksum',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
late final GeneratedColumn<bool> isFavorite = GeneratedColumn<bool>(
'is_favorite',
aliasedName,
false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("is_favorite" IN (0, 1))',
),
defaultValue: const CustomExpression('0'),
);
late final GeneratedColumn<int> orientation = GeneratedColumn<int>(
'orientation',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const CustomExpression('0'),
);
@override @override
List<GeneratedColumn> get $columns => [ List<GeneratedColumn> get $columns => [
id,
albumId,
checksum,
name, name,
type, type,
createdAt, createdAt,
updatedAt, updatedAt,
size, width,
height,
durationInSeconds,
id,
albumId,
volume,
checksum,
isFavorite,
orientation,
]; ];
@override @override
String get aliasedName => _alias ?? actualTableName; String get aliasedName => _alias ?? actualTableName;
@ -7194,7 +7239,7 @@ class TrashedLocalAssetEntity extends Table
String get actualTableName => $name; String get actualTableName => $name;
static const String $name = 'trashed_local_asset_entity'; static const String $name = 'trashed_local_asset_entity';
@override @override
Set<GeneratedColumn> get $primaryKey => {id}; Set<GeneratedColumn> get $primaryKey => {id, albumId};
@override @override
TrashedLocalAssetEntityData map( TrashedLocalAssetEntityData map(
Map<String, dynamic> data, { Map<String, dynamic> data, {
@ -7202,18 +7247,6 @@ class TrashedLocalAssetEntity extends Table
}) { }) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return TrashedLocalAssetEntityData( return TrashedLocalAssetEntityData(
id: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}id'],
)!,
albumId: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}album_id'],
)!,
checksum: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}checksum'],
),
name: attachedDatabase.typeMapping.read( name: attachedDatabase.typeMapping.read(
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}name'], data['${effectivePrefix}name'],
@ -7230,10 +7263,42 @@ class TrashedLocalAssetEntity extends Table
DriftSqlType.dateTime, DriftSqlType.dateTime,
data['${effectivePrefix}updated_at'], data['${effectivePrefix}updated_at'],
)!, )!,
size: attachedDatabase.typeMapping.read( width: attachedDatabase.typeMapping.read(
DriftSqlType.int, DriftSqlType.int,
data['${effectivePrefix}size'], data['${effectivePrefix}width'],
), ),
height: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}height'],
),
durationInSeconds: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}duration_in_seconds'],
),
id: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}id'],
)!,
albumId: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}album_id'],
)!,
volume: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}volume'],
),
checksum: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}checksum'],
),
isFavorite: attachedDatabase.typeMapping.read(
DriftSqlType.bool,
data['${effectivePrefix}is_favorite'],
)!,
orientation: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}orientation'],
)!,
); );
} }
@ -7250,39 +7315,60 @@ class TrashedLocalAssetEntity extends Table
class TrashedLocalAssetEntityData extends DataClass class TrashedLocalAssetEntityData extends DataClass
implements Insertable<TrashedLocalAssetEntityData> { implements Insertable<TrashedLocalAssetEntityData> {
final String id;
final String albumId;
final String? checksum;
final String name; final String name;
final int type; final int type;
final DateTime createdAt; final DateTime createdAt;
final DateTime updatedAt; final DateTime updatedAt;
final int? size; final int? width;
final int? height;
final int? durationInSeconds;
final String id;
final String albumId;
final String? volume;
final String? checksum;
final bool isFavorite;
final int orientation;
const TrashedLocalAssetEntityData({ const TrashedLocalAssetEntityData({
required this.id,
required this.albumId,
this.checksum,
required this.name, required this.name,
required this.type, required this.type,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
this.size, this.width,
this.height,
this.durationInSeconds,
required this.id,
required this.albumId,
this.volume,
this.checksum,
required this.isFavorite,
required this.orientation,
}); });
@override @override
Map<String, Expression> toColumns(bool nullToAbsent) { Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{}; final map = <String, Expression>{};
map['id'] = Variable<String>(id);
map['album_id'] = Variable<String>(albumId);
if (!nullToAbsent || checksum != null) {
map['checksum'] = Variable<String>(checksum);
}
map['name'] = Variable<String>(name); map['name'] = Variable<String>(name);
map['type'] = Variable<int>(type); map['type'] = Variable<int>(type);
map['created_at'] = Variable<DateTime>(createdAt); map['created_at'] = Variable<DateTime>(createdAt);
map['updated_at'] = Variable<DateTime>(updatedAt); map['updated_at'] = Variable<DateTime>(updatedAt);
if (!nullToAbsent || size != null) { if (!nullToAbsent || width != null) {
map['size'] = Variable<int>(size); map['width'] = Variable<int>(width);
} }
if (!nullToAbsent || height != null) {
map['height'] = Variable<int>(height);
}
if (!nullToAbsent || durationInSeconds != null) {
map['duration_in_seconds'] = Variable<int>(durationInSeconds);
}
map['id'] = Variable<String>(id);
map['album_id'] = Variable<String>(albumId);
if (!nullToAbsent || volume != null) {
map['volume'] = Variable<String>(volume);
}
if (!nullToAbsent || checksum != null) {
map['checksum'] = Variable<String>(checksum);
}
map['is_favorite'] = Variable<bool>(isFavorite);
map['orientation'] = Variable<int>(orientation);
return map; return map;
} }
@ -7292,194 +7378,268 @@ class TrashedLocalAssetEntityData extends DataClass
}) { }) {
serializer ??= driftRuntimeOptions.defaultSerializer; serializer ??= driftRuntimeOptions.defaultSerializer;
return TrashedLocalAssetEntityData( return TrashedLocalAssetEntityData(
id: serializer.fromJson<String>(json['id']),
albumId: serializer.fromJson<String>(json['albumId']),
checksum: serializer.fromJson<String?>(json['checksum']),
name: serializer.fromJson<String>(json['name']), name: serializer.fromJson<String>(json['name']),
type: serializer.fromJson<int>(json['type']), type: serializer.fromJson<int>(json['type']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']), createdAt: serializer.fromJson<DateTime>(json['createdAt']),
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']), updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
size: serializer.fromJson<int?>(json['size']), width: serializer.fromJson<int?>(json['width']),
height: serializer.fromJson<int?>(json['height']),
durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
id: serializer.fromJson<String>(json['id']),
albumId: serializer.fromJson<String>(json['albumId']),
volume: serializer.fromJson<String?>(json['volume']),
checksum: serializer.fromJson<String?>(json['checksum']),
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
orientation: serializer.fromJson<int>(json['orientation']),
); );
} }
@override @override
Map<String, dynamic> toJson({ValueSerializer? serializer}) { Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer; serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{ return <String, dynamic>{
'id': serializer.toJson<String>(id),
'albumId': serializer.toJson<String>(albumId),
'checksum': serializer.toJson<String?>(checksum),
'name': serializer.toJson<String>(name), 'name': serializer.toJson<String>(name),
'type': serializer.toJson<int>(type), 'type': serializer.toJson<int>(type),
'createdAt': serializer.toJson<DateTime>(createdAt), 'createdAt': serializer.toJson<DateTime>(createdAt),
'updatedAt': serializer.toJson<DateTime>(updatedAt), 'updatedAt': serializer.toJson<DateTime>(updatedAt),
'size': serializer.toJson<int?>(size), 'width': serializer.toJson<int?>(width),
'height': serializer.toJson<int?>(height),
'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
'id': serializer.toJson<String>(id),
'albumId': serializer.toJson<String>(albumId),
'volume': serializer.toJson<String?>(volume),
'checksum': serializer.toJson<String?>(checksum),
'isFavorite': serializer.toJson<bool>(isFavorite),
'orientation': serializer.toJson<int>(orientation),
}; };
} }
TrashedLocalAssetEntityData copyWith({ TrashedLocalAssetEntityData copyWith({
String? id,
String? albumId,
Value<String?> checksum = const Value.absent(),
String? name, String? name,
int? type, int? type,
DateTime? createdAt, DateTime? createdAt,
DateTime? updatedAt, DateTime? updatedAt,
Value<int?> size = const Value.absent(), Value<int?> width = const Value.absent(),
Value<int?> height = const Value.absent(),
Value<int?> durationInSeconds = const Value.absent(),
String? id,
String? albumId,
Value<String?> volume = const Value.absent(),
Value<String?> checksum = const Value.absent(),
bool? isFavorite,
int? orientation,
}) => TrashedLocalAssetEntityData( }) => TrashedLocalAssetEntityData(
id: id ?? this.id,
albumId: albumId ?? this.albumId,
checksum: checksum.present ? checksum.value : this.checksum,
name: name ?? this.name, name: name ?? this.name,
type: type ?? this.type, type: type ?? this.type,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
size: size.present ? size.value : this.size, width: width.present ? width.value : this.width,
height: height.present ? height.value : this.height,
durationInSeconds: durationInSeconds.present
? durationInSeconds.value
: this.durationInSeconds,
id: id ?? this.id,
albumId: albumId ?? this.albumId,
volume: volume.present ? volume.value : this.volume,
checksum: checksum.present ? checksum.value : this.checksum,
isFavorite: isFavorite ?? this.isFavorite,
orientation: orientation ?? this.orientation,
); );
TrashedLocalAssetEntityData copyWithCompanion( TrashedLocalAssetEntityData copyWithCompanion(
TrashedLocalAssetEntityCompanion data, TrashedLocalAssetEntityCompanion data,
) { ) {
return TrashedLocalAssetEntityData( return TrashedLocalAssetEntityData(
id: data.id.present ? data.id.value : this.id,
albumId: data.albumId.present ? data.albumId.value : this.albumId,
checksum: data.checksum.present ? data.checksum.value : this.checksum,
name: data.name.present ? data.name.value : this.name, name: data.name.present ? data.name.value : this.name,
type: data.type.present ? data.type.value : this.type, type: data.type.present ? data.type.value : this.type,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
size: data.size.present ? data.size.value : this.size, width: data.width.present ? data.width.value : this.width,
height: data.height.present ? data.height.value : this.height,
durationInSeconds: data.durationInSeconds.present
? data.durationInSeconds.value
: this.durationInSeconds,
id: data.id.present ? data.id.value : this.id,
albumId: data.albumId.present ? data.albumId.value : this.albumId,
volume: data.volume.present ? data.volume.value : this.volume,
checksum: data.checksum.present ? data.checksum.value : this.checksum,
isFavorite: data.isFavorite.present
? data.isFavorite.value
: this.isFavorite,
orientation: data.orientation.present
? data.orientation.value
: this.orientation,
); );
} }
@override @override
String toString() { String toString() {
return (StringBuffer('TrashedLocalAssetEntityData(') return (StringBuffer('TrashedLocalAssetEntityData(')
..write('id: $id, ')
..write('albumId: $albumId, ')
..write('checksum: $checksum, ')
..write('name: $name, ') ..write('name: $name, ')
..write('type: $type, ') ..write('type: $type, ')
..write('createdAt: $createdAt, ') ..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('size: $size') ..write('width: $width, ')
..write('height: $height, ')
..write('durationInSeconds: $durationInSeconds, ')
..write('id: $id, ')
..write('albumId: $albumId, ')
..write('volume: $volume, ')
..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ')
..write('orientation: $orientation')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
id,
albumId,
checksum,
name, name,
type, type,
createdAt, createdAt,
updatedAt, updatedAt,
size, width,
height,
durationInSeconds,
id,
albumId,
volume,
checksum,
isFavorite,
orientation,
); );
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
(other is TrashedLocalAssetEntityData && (other is TrashedLocalAssetEntityData &&
other.id == this.id &&
other.albumId == this.albumId &&
other.checksum == this.checksum &&
other.name == this.name && other.name == this.name &&
other.type == this.type && other.type == this.type &&
other.createdAt == this.createdAt && other.createdAt == this.createdAt &&
other.updatedAt == this.updatedAt && other.updatedAt == this.updatedAt &&
other.size == this.size); other.width == this.width &&
other.height == this.height &&
other.durationInSeconds == this.durationInSeconds &&
other.id == this.id &&
other.albumId == this.albumId &&
other.volume == this.volume &&
other.checksum == this.checksum &&
other.isFavorite == this.isFavorite &&
other.orientation == this.orientation);
} }
class TrashedLocalAssetEntityCompanion class TrashedLocalAssetEntityCompanion
extends UpdateCompanion<TrashedLocalAssetEntityData> { extends UpdateCompanion<TrashedLocalAssetEntityData> {
final Value<String> id;
final Value<String> albumId;
final Value<String?> checksum;
final Value<String> name; final Value<String> name;
final Value<int> type; final Value<int> type;
final Value<DateTime> createdAt; final Value<DateTime> createdAt;
final Value<DateTime> updatedAt; final Value<DateTime> updatedAt;
final Value<int?> size; final Value<int?> width;
final Value<int?> height;
final Value<int?> durationInSeconds;
final Value<String> id;
final Value<String> albumId;
final Value<String?> volume;
final Value<String?> checksum;
final Value<bool> isFavorite;
final Value<int> orientation;
const TrashedLocalAssetEntityCompanion({ const TrashedLocalAssetEntityCompanion({
this.id = const Value.absent(),
this.albumId = const Value.absent(),
this.checksum = const Value.absent(),
this.name = const Value.absent(), this.name = const Value.absent(),
this.type = const Value.absent(), this.type = const Value.absent(),
this.createdAt = const Value.absent(), this.createdAt = const Value.absent(),
this.updatedAt = const Value.absent(), this.updatedAt = const Value.absent(),
this.size = const Value.absent(), this.width = const Value.absent(),
this.height = const Value.absent(),
this.durationInSeconds = const Value.absent(),
this.id = const Value.absent(),
this.albumId = const Value.absent(),
this.volume = const Value.absent(),
this.checksum = const Value.absent(),
this.isFavorite = const Value.absent(),
this.orientation = const Value.absent(),
}); });
TrashedLocalAssetEntityCompanion.insert({ TrashedLocalAssetEntityCompanion.insert({
required String id,
required String albumId,
this.checksum = const Value.absent(),
required String name, required String name,
required int type, required int type,
this.createdAt = const Value.absent(), this.createdAt = const Value.absent(),
this.updatedAt = const Value.absent(), this.updatedAt = const Value.absent(),
this.size = const Value.absent(), this.width = const Value.absent(),
}) : id = Value(id), this.height = const Value.absent(),
albumId = Value(albumId), this.durationInSeconds = const Value.absent(),
name = Value(name), required String id,
type = Value(type); required String albumId,
this.volume = const Value.absent(),
this.checksum = const Value.absent(),
this.isFavorite = const Value.absent(),
this.orientation = const Value.absent(),
}) : name = Value(name),
type = Value(type),
id = Value(id),
albumId = Value(albumId);
static Insertable<TrashedLocalAssetEntityData> custom({ static Insertable<TrashedLocalAssetEntityData> custom({
Expression<String>? id,
Expression<String>? albumId,
Expression<String>? checksum,
Expression<String>? name, Expression<String>? name,
Expression<int>? type, Expression<int>? type,
Expression<DateTime>? createdAt, Expression<DateTime>? createdAt,
Expression<DateTime>? updatedAt, Expression<DateTime>? updatedAt,
Expression<int>? size, Expression<int>? width,
Expression<int>? height,
Expression<int>? durationInSeconds,
Expression<String>? id,
Expression<String>? albumId,
Expression<String>? volume,
Expression<String>? checksum,
Expression<bool>? isFavorite,
Expression<int>? orientation,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
if (id != null) 'id': id,
if (albumId != null) 'album_id': albumId,
if (checksum != null) 'checksum': checksum,
if (name != null) 'name': name, if (name != null) 'name': name,
if (type != null) 'type': type, if (type != null) 'type': type,
if (createdAt != null) 'created_at': createdAt, if (createdAt != null) 'created_at': createdAt,
if (updatedAt != null) 'updated_at': updatedAt, if (updatedAt != null) 'updated_at': updatedAt,
if (size != null) 'size': size, if (width != null) 'width': width,
if (height != null) 'height': height,
if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
if (id != null) 'id': id,
if (albumId != null) 'album_id': albumId,
if (volume != null) 'volume': volume,
if (checksum != null) 'checksum': checksum,
if (isFavorite != null) 'is_favorite': isFavorite,
if (orientation != null) 'orientation': orientation,
}); });
} }
TrashedLocalAssetEntityCompanion copyWith({ TrashedLocalAssetEntityCompanion copyWith({
Value<String>? id,
Value<String>? albumId,
Value<String?>? checksum,
Value<String>? name, Value<String>? name,
Value<int>? type, Value<int>? type,
Value<DateTime>? createdAt, Value<DateTime>? createdAt,
Value<DateTime>? updatedAt, Value<DateTime>? updatedAt,
Value<int?>? size, Value<int?>? width,
Value<int?>? height,
Value<int?>? durationInSeconds,
Value<String>? id,
Value<String>? albumId,
Value<String?>? volume,
Value<String?>? checksum,
Value<bool>? isFavorite,
Value<int>? orientation,
}) { }) {
return TrashedLocalAssetEntityCompanion( return TrashedLocalAssetEntityCompanion(
id: id ?? this.id,
albumId: albumId ?? this.albumId,
checksum: checksum ?? this.checksum,
name: name ?? this.name, name: name ?? this.name,
type: type ?? this.type, type: type ?? this.type,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
size: size ?? this.size, width: width ?? this.width,
height: height ?? this.height,
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
id: id ?? this.id,
albumId: albumId ?? this.albumId,
volume: volume ?? this.volume,
checksum: checksum ?? this.checksum,
isFavorite: isFavorite ?? this.isFavorite,
orientation: orientation ?? this.orientation,
); );
} }
@override @override
Map<String, Expression> toColumns(bool nullToAbsent) { Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{}; final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (albumId.present) {
map['album_id'] = Variable<String>(albumId.value);
}
if (checksum.present) {
map['checksum'] = Variable<String>(checksum.value);
}
if (name.present) { if (name.present) {
map['name'] = Variable<String>(name.value); map['name'] = Variable<String>(name.value);
} }
@ -7492,8 +7652,32 @@ class TrashedLocalAssetEntityCompanion
if (updatedAt.present) { if (updatedAt.present) {
map['updated_at'] = Variable<DateTime>(updatedAt.value); map['updated_at'] = Variable<DateTime>(updatedAt.value);
} }
if (size.present) { if (width.present) {
map['size'] = Variable<int>(size.value); map['width'] = Variable<int>(width.value);
}
if (height.present) {
map['height'] = Variable<int>(height.value);
}
if (durationInSeconds.present) {
map['duration_in_seconds'] = Variable<int>(durationInSeconds.value);
}
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (albumId.present) {
map['album_id'] = Variable<String>(albumId.value);
}
if (volume.present) {
map['volume'] = Variable<String>(volume.value);
}
if (checksum.present) {
map['checksum'] = Variable<String>(checksum.value);
}
if (isFavorite.present) {
map['is_favorite'] = Variable<bool>(isFavorite.value);
}
if (orientation.present) {
map['orientation'] = Variable<int>(orientation.value);
} }
return map; return map;
} }
@ -7501,14 +7685,19 @@ class TrashedLocalAssetEntityCompanion
@override @override
String toString() { String toString() {
return (StringBuffer('TrashedLocalAssetEntityCompanion(') return (StringBuffer('TrashedLocalAssetEntityCompanion(')
..write('id: $id, ')
..write('albumId: $albumId, ')
..write('checksum: $checksum, ')
..write('name: $name, ') ..write('name: $name, ')
..write('type: $type, ') ..write('type: $type, ')
..write('createdAt: $createdAt, ') ..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ') ..write('updatedAt: $updatedAt, ')
..write('size: $size') ..write('width: $width, ')
..write('height: $height, ')
..write('durationInSeconds: $durationInSeconds, ')
..write('id: $id, ')
..write('albumId: $albumId, ')
..write('volume: $volume, ')
..write('checksum: $checksum, ')
..write('isFavorite: $isFavorite, ')
..write('orientation: $orientation')
..write(')')) ..write(')'))
.toString(); .toString();
} }