rework trashed assets handling

- add new table trashed_local_asset
- mirror trashed assets data in trashed_local_asset.
- compute checksums for assets trashed out-of-app.
- restore assets present in trashed_local_asset and non-trashed in remote_asset.
- simplify moving-to-trash logic based on remote_asset events.
This commit is contained in:
Peter Ombodi 2025-09-18 13:55:56 +03:00
parent 3d56a5ca9c
commit f7e5288173
29 changed files with 2085 additions and 876 deletions

View file

@ -89,7 +89,8 @@ data class PlatformAsset (
val height: Long? = null, val height: Long? = null,
val durationInSeconds: Long, val durationInSeconds: Long,
val orientation: Long, val orientation: Long,
val isFavorite: Boolean val isFavorite: Boolean,
val size: Long? = null
) )
{ {
companion object { companion object {
@ -104,7 +105,8 @@ 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
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite) val size = pigeonVar_list[10] as Long?
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, size)
} }
} }
fun toList(): List<Any?> { fun toList(): List<Any?> {
@ -119,6 +121,7 @@ data class PlatformAsset (
durationInSeconds, durationInSeconds,
orientation, orientation,
isFavorite, isFavorite,
size,
) )
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -209,6 +212,40 @@ data class SyncDelta (
override fun hashCode(): Int = toList().hashCode() override fun hashCode(): Int = toList().hashCode()
} }
/** Generated class from Pigeon that represents data sent in messages. */
data class TrashedAssetParams (
val id: String,
val type: Long,
val albumId: String? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): TrashedAssetParams {
val id = pigeonVar_list[0] as String
val type = pigeonVar_list[1] as Long
val albumId = pigeonVar_list[2] as String?
return TrashedAssetParams(id, type, albumId)
}
}
fun toList(): List<Any?> {
return listOf(
id,
type,
albumId,
)
}
override fun equals(other: Any?): Boolean {
if (other !is TrashedAssetParams) {
return false
}
if (this === other) {
return true
}
return MessagesPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
private open class MessagesPigeonCodec : StandardMessageCodec() { private open class MessagesPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) { return when (type) {
@ -227,6 +264,11 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
SyncDelta.fromList(it) SyncDelta.fromList(it)
} }
} }
132.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
TrashedAssetParams.fromList(it)
}
}
else -> super.readValueOfType(type, buffer) else -> super.readValueOfType(type, buffer)
} }
} }
@ -244,6 +286,10 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
stream.write(131) stream.write(131)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is TrashedAssetParams -> {
stream.write(132)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value) else -> super.writeValue(stream, value)
} }
} }
@ -260,6 +306,8 @@ interface NativeSyncApi {
fun getAssetsCountSince(albumId: String, timestamp: Long): Long fun getAssetsCountSince(albumId: String, timestamp: Long): Long
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset> fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
fun hashPaths(paths: List<String>): List<ByteArray?> fun hashPaths(paths: List<String>): List<ByteArray?>
fun getTrashedAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>): List<ByteArray?>
companion object { companion object {
/** The codec used by NativeSyncApi. */ /** The codec used by NativeSyncApi. */
@ -418,6 +466,41 @@ interface NativeSyncApi {
channel.setMessageHandler(null) channel.setMessageHandler(null)
} }
} }
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val albumIdArg = args[0] as String
val updatedTimeCondArg = args[1] as Long?
val wrapped: List<Any?> = try {
listOf(api.getTrashedAssetsForAlbum(albumIdArg, updatedTimeCondArg))
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val trashedAssetsArg = args[0] as List<TrashedAssetParams>
val wrapped: List<Any?> = try {
listOf(api.hashTrashedAssets(trashedAssetsArg))
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
} }
} }
} }

View file

@ -21,4 +21,15 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na
override fun getMediaChanges(): 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(
albumId: String,
updatedTimeCond: Long?
): List<PlatformAsset> {
throw IllegalStateException("Method not supported on this Android version.")
}
override fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>): List<ByteArray?> {
throw IllegalStateException("Method not supported on this Android version.")
}
} }

View file

@ -1,11 +1,18 @@
package app.alextran.immich.sync package app.alextran.immich.sync
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.provider.MediaStore import android.provider.MediaStore
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.RequiresExtension import androidx.annotation.RequiresExtension
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.BufferedInputStream
import java.security.DigestInputStream
import java.security.MessageDigest
@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)
@ -33,6 +40,56 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
} }
} }
@RequiresExtension(extension = Build.VERSION_CODES.R, version = 1)
@RequiresApi(Build.VERSION_CODES.R)
override fun getTrashedAssetsForAlbum(
albumId: String,
updatedTimeCond: Long?
): List<PlatformAsset> {
val trashed = mutableListOf<PlatformAsset>()
val volumes = MediaStore.getExternalVolumeNames(ctx)
var selection = "$BUCKET_SELECTION AND $MEDIA_SELECTION"
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) {
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.toTypedArray())
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
}
ctx.contentResolver.query(uri, ASSET_PROJECTION, queryArgs, null).use { cursor ->
getAssets(cursor).forEach { res ->
if (res is AssetResult.ValidAsset) trashed += res.asset
}
}
}
return trashed
}
override fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>): List<ByteArray?> {
val result = ArrayList<ByteArray?>(trashedAssets.size)
for (item in trashedAssets) {
val digest = try {
val id = item.id.toLong()
val mediaType = item.type.toInt()
val uri = contentUriForType(id, mediaType)
sha1OfUri(ctx, uri)
} catch (_: Throwable) {
null
}
result.add(digest)
}
return result
}
override fun shouldFullSync(): Boolean = override fun shouldFullSync(): Boolean =
MediaStore.getVersion(ctx) != prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null) MediaStore.getVersion(ctx) != prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null)
@ -86,4 +143,31 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
// Unmounted volumes are handled in dart when the album is removed // Unmounted volumes are handled in dart when the album is removed
return SyncDelta(hasChanges, changed, deleted, assetAlbums) return SyncDelta(hasChanges, changed, deleted, assetAlbums)
} }
private fun contentUriForType(id: Long, mediaType: Int): Uri {
val vol = MediaStore.VOLUME_EXTERNAL
val base = when (mediaType) {
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> MediaStore.Images.Media.getContentUri(vol)
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.getContentUri(vol)
MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO -> MediaStore.Audio.Media.getContentUri(vol)
else -> MediaStore.Files.getContentUri(vol)
}
return ContentUris.withAppendedId(base, id)
}
private fun sha1OfUri(ctx: Context, uri: Uri): ByteArray? {
return try {
val md = MessageDigest.getInstance("SHA-1")
ctx.contentResolver.openInputStream(uri)?.use { input ->
DigestInputStream(BufferedInputStream(input), md).use { dis ->
val buf = ByteArray(DEFAULT_BUFFER_SIZE)
while (dis.read(buf) != -1) { /* pump */
}
}
} ?: return null
md.digest()
} catch (_: Exception) {
null
}
}
} }

View file

@ -46,6 +46,7 @@ open class NativeSyncApiImplBase(context: Context) {
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)
} }
add(MediaStore.MediaColumns.SIZE)
}.toTypedArray() }.toTypedArray()
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024 const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
@ -82,6 +83,7 @@ open class NativeSyncApiImplBase(context: Context) {
val orientationColumn = val orientationColumn =
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 sizeColumn = c.getColumnIndex(MediaStore.MediaColumns.SIZE)
while (c.moveToNext()) { while (c.moveToNext()) {
val id = c.getLong(idColumn).toString() val id = c.getLong(idColumn).toString()
@ -111,7 +113,7 @@ 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 size = c.getLong(sizeColumn)
val asset = PlatformAsset( val asset = PlatformAsset(
id, id,
name, name,
@ -123,6 +125,7 @@ open class NativeSyncApiImplBase(context: Context) {
duration, duration,
orientation.toLong(), orientation.toLong(),
isFavorite, isFavorite,
size
) )
yield(AssetResult.ValidAsset(asset, bucketId)) yield(AssetResult.ValidAsset(asset, bucketId))
} }

File diff suppressed because one or more lines are too long

View file

@ -140,6 +140,7 @@ struct PlatformAsset: Hashable {
var durationInSeconds: Int64 var durationInSeconds: Int64
var orientation: Int64 var orientation: Int64
var isFavorite: Bool var isFavorite: Bool
var size: Int64? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase // swift-format-ignore: AlwaysUseLowerCamelCase
@ -154,6 +155,7 @@ 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 size: Int64? = nilOrValue(pigeonVar_list[10])
return PlatformAsset( return PlatformAsset(
id: id, id: id,
@ -165,7 +167,8 @@ struct PlatformAsset: Hashable {
height: height, height: height,
durationInSeconds: durationInSeconds, durationInSeconds: durationInSeconds,
orientation: orientation, orientation: orientation,
isFavorite: isFavorite isFavorite: isFavorite,
size: size
) )
} }
func toList() -> [Any?] { func toList() -> [Any?] {
@ -180,6 +183,7 @@ struct PlatformAsset: Hashable {
durationInSeconds, durationInSeconds,
orientation, orientation,
isFavorite, isFavorite,
size,
] ]
} }
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool { static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
@ -267,6 +271,39 @@ struct SyncDelta: Hashable {
} }
} }
/// Generated class from Pigeon that represents data sent in messages.
struct TrashedAssetParams: Hashable {
var id: String
var type: Int64
var albumId: String? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> TrashedAssetParams? {
let id = pigeonVar_list[0] as! String
let type = pigeonVar_list[1] as! Int64
let albumId: String? = nilOrValue(pigeonVar_list[2])
return TrashedAssetParams(
id: id,
type: type,
albumId: albumId
)
}
func toList() -> [Any?] {
return [
id,
type,
albumId,
]
}
static func == (lhs: TrashedAssetParams, rhs: TrashedAssetParams) -> Bool {
return deepEqualsMessages(lhs.toList(), rhs.toList()) }
func hash(into hasher: inout Hasher) {
deepHashMessages(value: toList(), hasher: &hasher)
}
}
private class MessagesPigeonCodecReader: FlutterStandardReader { private class MessagesPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? { override func readValue(ofType type: UInt8) -> Any? {
switch type { switch type {
@ -276,6 +313,8 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
return PlatformAlbum.fromList(self.readValue() as! [Any?]) return PlatformAlbum.fromList(self.readValue() as! [Any?])
case 131: case 131:
return SyncDelta.fromList(self.readValue() as! [Any?]) return SyncDelta.fromList(self.readValue() as! [Any?])
case 132:
return TrashedAssetParams.fromList(self.readValue() as! [Any?])
default: default:
return super.readValue(ofType: type) return super.readValue(ofType: type)
} }
@ -293,6 +332,9 @@ private class MessagesPigeonCodecWriter: FlutterStandardWriter {
} else if let value = value as? SyncDelta { } else if let value = value as? SyncDelta {
super.writeByte(131) super.writeByte(131)
super.writeValue(value.toList()) super.writeValue(value.toList())
} else if let value = value as? TrashedAssetParams {
super.writeByte(132)
super.writeValue(value.toList())
} else { } else {
super.writeValue(value) super.writeValue(value)
} }
@ -324,6 +366,8 @@ protocol NativeSyncApi {
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
func hashPaths(paths: [String]) throws -> [FlutterStandardTypedData?] func hashPaths(paths: [String]) throws -> [FlutterStandardTypedData?]
func getTrashedAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
func hashTrashedAssets(trashedAssets: [TrashedAssetParams]) throws -> [FlutterStandardTypedData?]
} }
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@ -476,5 +520,40 @@ class NativeSyncApiSetup {
} else { } else {
hashPathsChannel.setMessageHandler(nil) hashPathsChannel.setMessageHandler(nil)
} }
let getTrashedAssetsForAlbumChannel = taskQueue == nil
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api {
getTrashedAssetsForAlbumChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let albumIdArg = args[0] as! String
let updatedTimeCondArg: Int64? = nilOrValue(args[1])
do {
let result = try api.getTrashedAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getTrashedAssetsForAlbumChannel.setMessageHandler(nil)
}
let hashTrashedAssetsChannel = taskQueue == nil
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api {
hashTrashedAssetsChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let trashedAssetsArg = args[0] as! [TrashedAssetParams]
do {
let result = try api.hashTrashedAssets(trashedAssets: trashedAssetsArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
hashTrashedAssetsChannel.setMessageHandler(nil)
}
} }
} }

View file

@ -0,0 +1,76 @@
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
class TrashedAsset {
final String id;
final String name;
final String albumId;
final String? checksum;
final AssetType type;
final DateTime createdAt;
final DateTime updatedAt;
final int? size;
const TrashedAsset({
required this.id,
required this.name,
required this.checksum,
required this.albumId,
required this.type,
required this.createdAt,
required this.updatedAt,
this.size,
});
TrashedAsset copyWith({
String? id,
String? name,
String? albumId,
String? checksum,
AssetType? type,
DateTime? createdAt,
DateTime? updatedAt,
int? size,
}) {
return TrashedAsset(
id: id ?? this.id,
name: name ?? this.name,
albumId: albumId ?? this.albumId,
checksum: checksum ?? this.checksum,
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
size: size ?? this.size,
);
}
@override
String toString() {
return 'TrashedAsset('
'id: $id, '
'name: $name, '
'albumId: $albumId, '
'checksum: $checksum, '
'type: $type, '
'createdAt: $createdAt, '
'updatedAt: $updatedAt, '
'size: ${size ?? "<NA>"}'
')';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is TrashedAsset &&
runtimeType == other.runtimeType &&
id == other.id &&
name == other.name &&
albumId == other.albumId &&
checksum == other.checksum &&
type == other.type &&
createdAt == other.createdAt &&
updatedAt == other.updatedAt &&
size == other.size;
@override
int get hashCode => Object.hash(id, name, albumId, checksum, type, createdAt, updatedAt, size);
}

View file

@ -3,6 +3,8 @@ import 'dart:convert';
import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/constants.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/base_asset.model.dart';
import 'package:immich_mobile/domain/models/asset/trashed_asset.model.dart';
import 'package:immich_mobile/domain/services/trash_sync.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
@ -16,6 +18,7 @@ class HashService {
final DriftLocalAssetRepository _localAssetRepository; final DriftLocalAssetRepository _localAssetRepository;
final StorageRepository _storageRepository; final StorageRepository _storageRepository;
final NativeSyncApi _nativeSyncApi; final NativeSyncApi _nativeSyncApi;
final TrashSyncService _trashSyncService;
final bool Function()? _cancelChecker; final bool Function()? _cancelChecker;
final _log = Logger('HashService'); final _log = Logger('HashService');
@ -24,6 +27,7 @@ class HashService {
required DriftLocalAssetRepository localAssetRepository, required DriftLocalAssetRepository localAssetRepository,
required StorageRepository storageRepository, required StorageRepository storageRepository,
required NativeSyncApi nativeSyncApi, required NativeSyncApi nativeSyncApi,
required TrashSyncService trashSyncService,
bool Function()? cancelChecker, bool Function()? cancelChecker,
this.batchSizeLimit = kBatchHashSizeLimit, this.batchSizeLimit = kBatchHashSizeLimit,
this.batchFileLimit = kBatchHashFileLimit, this.batchFileLimit = kBatchHashFileLimit,
@ -31,7 +35,8 @@ class HashService {
_localAssetRepository = localAssetRepository, _localAssetRepository = localAssetRepository,
_storageRepository = storageRepository, _storageRepository = storageRepository,
_cancelChecker = cancelChecker, _cancelChecker = cancelChecker,
_nativeSyncApi = nativeSyncApi; _nativeSyncApi = nativeSyncApi,
_trashSyncService = trashSyncService;
bool get isCancelled => _cancelChecker?.call() ?? false; bool get isCancelled => _cancelChecker?.call() ?? false;
@ -55,6 +60,20 @@ class HashService {
} }
} }
if (_trashSyncService.isAutoSyncMode) {
final backupAlbums = await _localAlbumRepository.getBackupAlbums();
for (final album in backupAlbums) {
if (isCancelled) {
_log.warning("Hashing cancelled. Stopped processing albums.");
break;
}
final trashedToHash = await _trashSyncService.getAssetsToHash(album.id);
if (trashedToHash.isNotEmpty) {
await _hashTrashedAssets(album, trashedToHash);
}
}
}
stopwatch.stop(); stopwatch.stop();
_log.info("Hashing took - ${stopwatch.elapsedMilliseconds}ms"); _log.info("Hashing took - ${stopwatch.elapsedMilliseconds}ms");
} }
@ -130,6 +149,130 @@ class HashService {
await _localAssetRepository.updateHashes(hashed); await _localAssetRepository.updateHashes(hashed);
await _storageRepository.clearCache(); await _storageRepository.clearCache();
} }
Future<void> _hashTrashedAssets(LocalAlbum album, Iterable<TrashedAsset> assetsToHash) async {
int bytesProcessed = 0;
final toHash = <TrashedAsset>[];
for (final asset in assetsToHash) {
if (isCancelled) {
_log.warning("Hashing cancelled. Stopped processing assets.");
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;
}
bytesProcessed += asset.size!;
toHash.add(asset);
if (toHash.length >= batchFileLimit || bytesProcessed >= batchSizeLimit) {
await _processTrashedBatch(album, toHash);
toHash.clear();
bytesProcessed = 0;
}
}
await _processTrashedBatch(album, toHash);
}
Future<void> _processTrashedBatch(LocalAlbum album, List<TrashedAsset> toHash) async {
if (toHash.isEmpty) {
return;
}
_log.fine("Hashing ${toHash.length} trashed files");
final params = toHash.map((e) => TrashedAssetParams(id: e.id, type: e.type.index, albumId: album.id)).toList();
final hashes = await _nativeSyncApi.hashTrashedAssets(params);
assert(
hashes.length == toHash.length,
"Trashed Assets, Hashes length does not match toHash length: ${hashes.length} != ${toHash.length}",
);
final hashed = <TrashedAsset>[];
for (int i = 0; i < hashes.length; i++) {
if (isCancelled) {
_log.warning("Hashing cancelled. Stopped processing batch.");
return;
}
final hash = hashes[i];
final asset = toHash[i];
if (hash?.length == 20) {
hashed.add(asset.copyWith(checksum: base64.encode(hash!)));
} else {
_log.warning(
"Failed to hash trashed file for ${asset.id}: ${asset.name} created at ${asset.createdAt} from album: ${album.name}",
);
}
}
_log.fine("Hashed ${hashed.length}/${toHash.length} trashed assets");
await _trashSyncService.updateChecksums(hashed);
}
// Future<void> _hashTrashedAssets(LocalAlbum album, Iterable<TrashedAsset> assetsToHash) async {
// final trashedAssets = assetsToHash
// .map((e) => TrashedAssetParams(id: e.id, type: e.type.index, albumId: album.id))
// .toList();
//
// final byId = <String, LocalAsset>{for (final a in assetsToHash) a.id: a};
//
// final hashed = <LocalAsset>[];
// const chunkSize = 10;
//
// for (int i = 0; i < trashedAssets.length; i += chunkSize) {
// if (isCancelled) {
// _log.warning("Hashing cancelled. Stopped processing assets.");
// return;
// }
// final end = (i + chunkSize <= trashedAssets.length) ? i + chunkSize : trashedAssets.length;
// final batch = trashedAssets.sublist(i, end);
//
// List<Uint8List?> hashes;
// try {
// hashes = await _nativeSyncApi.hashTrashedAssets(batch);
// } catch (e, s) {
// _log.severe("hashTrashedAssets failed for batch [$i..${end - 1}]: $e", e, s);
// continue;
// }
//
// if (hashes.length != batch.length) {
// _log.warning(
// "hashTrashedAssets returned ${hashes.length} hashes for ${batch.length} assets (batch [$i..${end - 1}]).",
// );
// }
//
// final limit = hashes.length < batch.length ? hashes.length : batch.length;
// for (int j = 0; j < limit; j++) {
// if (isCancelled) {
// _log.warning("Hashing cancelled. Stopped processing assets.");
// return;
// }
// final asset = batch[j];
// final hash = hashes[j];
//
// if (hash != null && hash.length == 20) {
// final localAsset = byId[asset.id];
// if (localAsset != null) {
// hashed.add(localAsset.copyWith(checksum: base64.encode(hash)));
// }
// } else {
// _log.warning("Failed to hash file for ${asset.id} from album: ${album.name}");
// }
// }
// }
//
// _log.warning("updateHashes for album: ${album.name}, assets: ${hashed.map((e) => '${e.name}-${e.checksum}')}");
//
// await _trashSyncService.updateHashes(hashed);
// }
} }
class _AssetToPath { class _AssetToPath {

View file

@ -4,6 +4,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.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/base_asset.model.dart';
import 'package:immich_mobile/domain/services/trash_sync.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart';
import 'package:immich_mobile/utils/datetime_helpers.dart'; import 'package:immich_mobile/utils/datetime_helpers.dart';
@ -14,14 +15,17 @@ import 'package:platform/platform.dart';
class LocalSyncService { class LocalSyncService {
final DriftLocalAlbumRepository _localAlbumRepository; final DriftLocalAlbumRepository _localAlbumRepository;
final NativeSyncApi _nativeSyncApi; final NativeSyncApi _nativeSyncApi;
final TrashSyncService _trashSyncService;
final Platform _platform; final Platform _platform;
final Logger _log = Logger("DeviceSyncService"); final Logger _log = Logger("DeviceSyncService");
LocalSyncService({ LocalSyncService({
required DriftLocalAlbumRepository localAlbumRepository, required DriftLocalAlbumRepository localAlbumRepository,
required TrashSyncService trashSyncService,
required NativeSyncApi nativeSyncApi, required NativeSyncApi nativeSyncApi,
Platform? platform, Platform? platform,
}) : _localAlbumRepository = localAlbumRepository, }) : _localAlbumRepository = localAlbumRepository,
_trashSyncService = trashSyncService,
_nativeSyncApi = nativeSyncApi, _nativeSyncApi = nativeSyncApi,
_platform = platform ?? const LocalPlatform(); _platform = platform ?? const LocalPlatform();
@ -74,7 +78,10 @@ class LocalSyncService {
await updateAlbum(dbAlbum, album); await updateAlbum(dbAlbum, album);
} }
} }
if (_trashSyncService.isAutoSyncMode) {
// On Android we need to sync trashed assets
await _trashSyncService.updateLocalTrashFromDevice();
}
await _nativeSyncApi.checkpointSync(); await _nativeSyncApi.checkpointSync();
} catch (e, s) { } catch (e, s) {
_log.severe("Error performing device sync", e, s); _log.severe("Error performing device sync", e, s);
@ -99,6 +106,9 @@ class LocalSyncService {
onlyFirst: removeAlbum, onlyFirst: removeAlbum,
onlySecond: addAlbum, onlySecond: addAlbum,
); );
if (_trashSyncService.isAutoSyncMode) {
await _trashSyncService.updateLocalTrashFromDevice();
}
await _nativeSyncApi.checkpointSync(); await _nativeSyncApi.checkpointSync();
stopwatch.stop(); stopwatch.stop();

View file

@ -86,9 +86,11 @@ class SyncStreamService {
return _syncStreamRepository.deletePartnerV1(data.cast()); return _syncStreamRepository.deletePartnerV1(data.cast());
case SyncEntityType.assetV1: case SyncEntityType.assetV1:
final remoteSyncAssets = data.cast<SyncAssetV1>(); final remoteSyncAssets = data.cast<SyncAssetV1>();
if (_trashSyncService.isAutoSyncMode) {
await _trashSyncService.handleRemoteChanges( await _trashSyncService.handleRemoteChanges(
remoteSyncAssets.map<TrashSyncItem>((e) => (remoteId: e.id, checksum: e.checksum, deletedAt: e.deletedAt)), remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum),
); );
}
return _syncStreamRepository.updateAssetsV1(remoteSyncAssets); return _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
case SyncEntityType.assetDeleteV1: case SyncEntityType.assetDeleteV1:
return _syncStreamRepository.deleteAssetsV1(data.cast()); return _syncStreamRepository.deleteAssetsV1(data.cast());

View file

@ -1,10 +1,13 @@
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/local_trashed_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/trashed_asset.model.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/platform/native_sync_api.g.dart';
import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/datetime_helpers.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
@ -12,8 +15,10 @@ typedef TrashSyncItem = ({String remoteId, String checksum, DateTime? deletedAt}
class TrashSyncService { class TrashSyncService {
final AppSettingsService _appSettingsService; final AppSettingsService _appSettingsService;
final RemoteAssetRepository _remoteAssetRepository; final NativeSyncApi _nativeSyncApi;
final DriftLocalAssetRepository _localAssetRepository; final DriftLocalAssetRepository _localAssetRepository;
final DriftLocalAlbumRepository _localAlbumRepository;
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
final LocalFilesManagerRepository _localFilesManager; final LocalFilesManagerRepository _localFilesManager;
final StorageRepository _storageRepository; final StorageRepository _storageRepository;
final Platform _platform; final Platform _platform;
@ -21,85 +26,96 @@ class TrashSyncService {
TrashSyncService({ TrashSyncService({
required AppSettingsService appSettingsService, required AppSettingsService appSettingsService,
required RemoteAssetRepository remoteAssetRepository, required NativeSyncApi nativeSyncApi,
required DriftLocalAssetRepository localAssetRepository, required DriftLocalAssetRepository localAssetRepository,
required DriftLocalAlbumRepository localAlbumRepository,
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
required LocalFilesManagerRepository localFilesManager, required LocalFilesManagerRepository localFilesManager,
required StorageRepository storageRepository, required StorageRepository storageRepository,
}) : _appSettingsService = appSettingsService, }) : _appSettingsService = appSettingsService,
_remoteAssetRepository = remoteAssetRepository, _nativeSyncApi = nativeSyncApi,
_localAssetRepository = localAssetRepository, _localAssetRepository = localAssetRepository,
_localAlbumRepository = localAlbumRepository,
_trashedLocalAssetRepository = trashedLocalAssetRepository,
_localFilesManager = localFilesManager, _localFilesManager = localFilesManager,
_storageRepository = storageRepository, _storageRepository = storageRepository,
_platform = const LocalPlatform(); _platform = const LocalPlatform();
Future<void> handleRemoteChanges(Iterable<TrashSyncItem> syncItems) async { bool get isAutoSyncMode =>
if (!_platform.isAndroid || !_appSettingsService.getSetting<bool>(AppSettingsEnum.manageLocalMediaAndroid)) { _platform.isAndroid && _appSettingsService.getSetting<bool>(AppSettingsEnum.manageLocalMediaAndroid);
return Future.value();
Future<void> updateChecksums(Iterable<TrashedAsset> assets) async =>
_trashedLocalAssetRepository.updateChecksums(assets);
Future<Iterable<TrashedAsset>> getAssetsToHash(String albumId) async =>
_trashedLocalAssetRepository.getToHash(albumId);
Future<void> updateLocalTrashFromDevice() async {
final backupAlbums = await _localAlbumRepository.getBackupAlbums();
if (backupAlbums.isEmpty) {
_logger.info("No backup albums found");
return;
} }
final trashedAssetsItems = <TrashSyncItem>[]; for (final album in backupAlbums) {
final modifiedAssetsChecksums = <String>{}; _logger.info("deviceTrashedAssets prepare, album: ${album.id}/${album.name}");
for (var syncItem in syncItems) { final deviceTrashedAssets = await _nativeSyncApi.getTrashedAssetsForAlbum(album.id);
if (syncItem.deletedAt != null) { await _trashedLocalAssetRepository.applyTrashSnapshot(deviceTrashedAssets.toTrashedAssets(album.id), album.id);
trashedAssetsItems.add(syncItem);
} else {
modifiedAssetsChecksums.add(syncItem.checksum);
} }
} // todo find for more suitable place
await _applyRemoteTrashToLocal(trashedAssetsItems); await applyRemoteRestoreToLocal();
await _applyRemoteRestoreToLocal(modifiedAssetsChecksums);
} }
Future<void> _applyRemoteTrashToLocal(Iterable<TrashSyncItem> trashedAssets) async { Future<void> handleRemoteChanges(Iterable<String> checksums) async {
if (trashedAssets.isEmpty) { if (checksums.isEmpty) {
return Future.value(); return Future.value();
} else { } else {
final trashedAssetsMap = <String, String>{for (final e in trashedAssets) e.checksum: e.remoteId}; final localAssetsToTrash = await _localAssetRepository.getBackupSelectedAssetsByAlbum(checksums);
final localAssetsToTrash = await _localAssetRepository.getBackupSelectedAssets(trashedAssetsMap.keys);
if (localAssetsToTrash.isNotEmpty) { if (localAssetsToTrash.isNotEmpty) {
final mediaUrls = await Future.wait( final mediaUrls = await Future.wait(
localAssetsToTrash.map( localAssetsToTrash.values
(localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl()), .expand((e) => e)
), .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
); );
_logger.info("Moving to trash ${mediaUrls.join(", ")} assets"); _logger.info("Moving to trash ${mediaUrls.join(", ")} assets");
await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
final itemsToTrash = <LocalRemoteIds>[]; if (result) {
for (final asset in localAssetsToTrash) { await _localAssetRepository.trash(localAssetsToTrash);
final remoteId = trashedAssetsMap[asset.checksum]!;
itemsToTrash.add((localId: asset.id, remoteId: remoteId));
} }
await _localAssetRepository.trash(itemsToTrash);
} else { } else {
_logger.info("No assets found in backup-enabled albums for assets: $trashedAssetsMap"); _logger.info("No assets found in backup-enabled albums for assets: $checksums");
} }
} }
} }
Future<void> _applyRemoteRestoreToLocal(Iterable<String> modifiedAssetsChecksums) async { Future<void> applyRemoteRestoreToLocal() async {
if (modifiedAssetsChecksums.isEmpty) { final remoteAssetsToRestore = await _trashedLocalAssetRepository.getToRestore();
return Future.value();
} else {
final remoteAssetsToRestore = await _remoteAssetRepository.getByChecksums(
modifiedAssetsChecksums,
isTrashed: true,
);
if (remoteAssetsToRestore.isNotEmpty) { if (remoteAssetsToRestore.isNotEmpty) {
final remoteAssetMap = <String, AssetType>{for (final e in remoteAssetsToRestore) e.id: e.type}; _logger.info("remoteAssetsToRestore: $remoteAssetsToRestore");
_logger.info("remoteAssetsToRestore: $remoteAssetMap"); for (final asset in remoteAssetsToRestore) {
final localTrashedAssets = await _localAssetRepository.getLocalTrashedAssets(remoteAssetMap.keys); _logger.info("Restoring from trash, localId: ${asset.id}, remoteId: ${asset.checksum}");
if (localTrashedAssets.isNotEmpty) { await _localFilesManager.restoreFromTrashById(asset.id, asset.type.index);
for (final LocalTrashedAsset asset in localTrashedAssets) {
_logger.info("Restoring from trash, localId: ${asset.localId}, remoteId: ${asset.remoteId}");
final type = remoteAssetMap[asset.remoteId]!;
await _localFilesManager.restoreFromTrashById(asset.localId, type.index);
await _localAssetRepository.deleteLocalTrashedAssets(remoteAssetMap.keys);
}
} else {
_logger.info("No local assets found for restoration");
} }
// 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");
} }
} }
}
extension on Iterable<PlatformAsset> {
List<TrashedAsset> toTrashedAssets(String albumId) {
return map(
(e) => TrashedAsset(
id: e.id,
name: e.name,
checksum: null,
type: AssetType.values.elementAtOrNull(e.type) ?? AssetType.other,
createdAt: tryFromSecondsSinceEpoch(e.createdAt) ?? DateTime.now(),
updatedAt: tryFromSecondsSinceEpoch(e.updatedAt) ?? DateTime.now(),
size: e.size,
albumId: albumId,
),
).toList();
} }
} }

View file

@ -1,25 +0,0 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/local_trashed_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_trashed_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
@TableIndex.sql(
'CREATE INDEX IF NOT EXISTS idx_local_trashed_asset_remote_id ON local_trashed_asset_entity (remote_id)',
)
class LocalTrashedAssetEntity extends Table with DriftDefaultsMixin {
const LocalTrashedAssetEntity();
TextColumn get id => text()();
TextColumn get remoteId => text().references(RemoteAssetEntity, #id, onDelete: KeyAction.cascade)();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
@override
Set<Column> get primaryKey => {id};
}
extension LocalTrashedAssetEntityDataDomainExtension on LocalTrashedAssetEntityData {
LocalTrashedAsset toDto() => LocalTrashedAsset(localId: id, remoteId: remoteId, createdAt: createdAt);
}

View file

@ -1,614 +0,0 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:immich_mobile/infrastructure/entities/local_trashed_asset.entity.drift.dart'
as i1;
import 'package:immich_mobile/infrastructure/entities/local_trashed_asset.entity.dart'
as i2;
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
as i4;
import 'package:drift/internal/modular.dart' as i5;
typedef $$LocalTrashedAssetEntityTableCreateCompanionBuilder =
i1.LocalTrashedAssetEntityCompanion Function({
required String id,
required String remoteId,
i0.Value<DateTime> createdAt,
});
typedef $$LocalTrashedAssetEntityTableUpdateCompanionBuilder =
i1.LocalTrashedAssetEntityCompanion Function({
i0.Value<String> id,
i0.Value<String> remoteId,
i0.Value<DateTime> createdAt,
});
final class $$LocalTrashedAssetEntityTableReferences
extends
i0.BaseReferences<
i0.GeneratedDatabase,
i1.$LocalTrashedAssetEntityTable,
i1.LocalTrashedAssetEntityData
> {
$$LocalTrashedAssetEntityTableReferences(
super.$_db,
super.$_table,
super.$_typedResult,
);
static i4.$RemoteAssetEntityTable _remoteIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db)
.resultSet<i1.$LocalTrashedAssetEntityTable>(
'local_trashed_asset_entity',
)
.remoteId,
i5.ReadDatabaseContainer(
db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
);
i4.$$RemoteAssetEntityTableProcessedTableManager get remoteId {
final $_column = $_itemColumn<String>('remote_id')!;
final manager = i4
.$$RemoteAssetEntityTableTableManager(
$_db,
i5.ReadDatabaseContainer(
$_db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
)
.filter((f) => f.id.sqlEquals($_column));
final item = $_typedResult.readTableOrNull(_remoteIdTable($_db));
if (item == null) return manager;
return i0.ProcessedTableManager(
manager.$state.copyWith(prefetchedData: [item]),
);
}
}
class $$LocalTrashedAssetEntityTableFilterComposer
extends
i0.Composer<i0.GeneratedDatabase, i1.$LocalTrashedAssetEntityTable> {
$$LocalTrashedAssetEntityTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnFilters<String> get id => $composableBuilder(
column: $table.id,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => i0.ColumnFilters(column),
);
i4.$$RemoteAssetEntityTableFilterComposer get remoteId {
final i4.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.remoteId,
referencedTable: i5.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i4.$$RemoteAssetEntityTableFilterComposer(
$db: $db,
$table: i5.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return composer;
}
}
class $$LocalTrashedAssetEntityTableOrderingComposer
extends
i0.Composer<i0.GeneratedDatabase, i1.$LocalTrashedAssetEntityTable> {
$$LocalTrashedAssetEntityTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnOrderings<String> get id => $composableBuilder(
column: $table.id,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => i0.ColumnOrderings(column),
);
i4.$$RemoteAssetEntityTableOrderingComposer get remoteId {
final i4.$$RemoteAssetEntityTableOrderingComposer composer =
$composerBuilder(
composer: this,
getCurrentColumn: (t) => t.remoteId,
referencedTable: i5.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i4.$$RemoteAssetEntityTableOrderingComposer(
$db: $db,
$table: i5.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return composer;
}
}
class $$LocalTrashedAssetEntityTableAnnotationComposer
extends
i0.Composer<i0.GeneratedDatabase, i1.$LocalTrashedAssetEntityTable> {
$$LocalTrashedAssetEntityTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.GeneratedColumn<String> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
i0.GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
i4.$$RemoteAssetEntityTableAnnotationComposer get remoteId {
final i4.$$RemoteAssetEntityTableAnnotationComposer composer =
$composerBuilder(
composer: this,
getCurrentColumn: (t) => t.remoteId,
referencedTable: i5.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => i4.$$RemoteAssetEntityTableAnnotationComposer(
$db: $db,
$table: i5.ReadDatabaseContainer(
$db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return composer;
}
}
class $$LocalTrashedAssetEntityTableTableManager
extends
i0.RootTableManager<
i0.GeneratedDatabase,
i1.$LocalTrashedAssetEntityTable,
i1.LocalTrashedAssetEntityData,
i1.$$LocalTrashedAssetEntityTableFilterComposer,
i1.$$LocalTrashedAssetEntityTableOrderingComposer,
i1.$$LocalTrashedAssetEntityTableAnnotationComposer,
$$LocalTrashedAssetEntityTableCreateCompanionBuilder,
$$LocalTrashedAssetEntityTableUpdateCompanionBuilder,
(
i1.LocalTrashedAssetEntityData,
i1.$$LocalTrashedAssetEntityTableReferences,
),
i1.LocalTrashedAssetEntityData,
i0.PrefetchHooks Function({bool remoteId})
> {
$$LocalTrashedAssetEntityTableTableManager(
i0.GeneratedDatabase db,
i1.$LocalTrashedAssetEntityTable table,
) : super(
i0.TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
i1.$$LocalTrashedAssetEntityTableFilterComposer(
$db: db,
$table: table,
),
createOrderingComposer: () =>
i1.$$LocalTrashedAssetEntityTableOrderingComposer(
$db: db,
$table: table,
),
createComputedFieldComposer: () =>
i1.$$LocalTrashedAssetEntityTableAnnotationComposer(
$db: db,
$table: table,
),
updateCompanionCallback:
({
i0.Value<String> id = const i0.Value.absent(),
i0.Value<String> remoteId = const i0.Value.absent(),
i0.Value<DateTime> createdAt = const i0.Value.absent(),
}) => i1.LocalTrashedAssetEntityCompanion(
id: id,
remoteId: remoteId,
createdAt: createdAt,
),
createCompanionCallback:
({
required String id,
required String remoteId,
i0.Value<DateTime> createdAt = const i0.Value.absent(),
}) => i1.LocalTrashedAssetEntityCompanion.insert(
id: id,
remoteId: remoteId,
createdAt: createdAt,
),
withReferenceMapper: (p0) => p0
.map(
(e) => (
e.readTable(table),
i1.$$LocalTrashedAssetEntityTableReferences(db, table, e),
),
)
.toList(),
prefetchHooksCallback: ({remoteId = false}) {
return i0.PrefetchHooks(
db: db,
explicitlyWatchedTables: [],
addJoins:
<
T extends i0.TableManagerState<
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic
>
>(state) {
if (remoteId) {
state =
state.withJoin(
currentTable: table,
currentColumn: table.remoteId,
referencedTable: i1
.$$LocalTrashedAssetEntityTableReferences
._remoteIdTable(db),
referencedColumn: i1
.$$LocalTrashedAssetEntityTableReferences
._remoteIdTable(db)
.id,
)
as T;
}
return state;
},
getPrefetchedDataCallback: (items) async {
return [];
},
);
},
),
);
}
typedef $$LocalTrashedAssetEntityTableProcessedTableManager =
i0.ProcessedTableManager<
i0.GeneratedDatabase,
i1.$LocalTrashedAssetEntityTable,
i1.LocalTrashedAssetEntityData,
i1.$$LocalTrashedAssetEntityTableFilterComposer,
i1.$$LocalTrashedAssetEntityTableOrderingComposer,
i1.$$LocalTrashedAssetEntityTableAnnotationComposer,
$$LocalTrashedAssetEntityTableCreateCompanionBuilder,
$$LocalTrashedAssetEntityTableUpdateCompanionBuilder,
(
i1.LocalTrashedAssetEntityData,
i1.$$LocalTrashedAssetEntityTableReferences,
),
i1.LocalTrashedAssetEntityData,
i0.PrefetchHooks Function({bool remoteId})
>;
i0.Index get idxLocalTrashedAssetRemoteId => i0.Index(
'idx_local_trashed_asset_remote_id',
'CREATE INDEX IF NOT EXISTS idx_local_trashed_asset_remote_id ON local_trashed_asset_entity (remote_id)',
);
class $LocalTrashedAssetEntityTable extends i2.LocalTrashedAssetEntity
with
i0.TableInfo<
$LocalTrashedAssetEntityTable,
i1.LocalTrashedAssetEntityData
> {
@override
final i0.GeneratedDatabase attachedDatabase;
final String? _alias;
$LocalTrashedAssetEntityTable(this.attachedDatabase, [this._alias]);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
'id',
aliasedName,
false,
type: i0.DriftSqlType.string,
requiredDuringInsert: true,
);
static const i0.VerificationMeta _remoteIdMeta = const i0.VerificationMeta(
'remoteId',
);
@override
late final i0.GeneratedColumn<String> remoteId = i0.GeneratedColumn<String>(
'remote_id',
aliasedName,
false,
type: i0.DriftSqlType.string,
requiredDuringInsert: true,
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
'REFERENCES remote_asset_entity (id) ON DELETE CASCADE',
),
);
static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta(
'createdAt',
);
@override
late final i0.GeneratedColumn<DateTime> createdAt =
i0.GeneratedColumn<DateTime>(
'created_at',
aliasedName,
false,
type: i0.DriftSqlType.dateTime,
requiredDuringInsert: false,
defaultValue: i3.currentDateAndTime,
);
@override
List<i0.GeneratedColumn> get $columns => [id, remoteId, createdAt];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'local_trashed_asset_entity';
@override
i0.VerificationContext validateIntegrity(
i0.Insertable<i1.LocalTrashedAssetEntityData> instance, {
bool isInserting = false,
}) {
final context = i0.VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
} else if (isInserting) {
context.missing(_idMeta);
}
if (data.containsKey('remote_id')) {
context.handle(
_remoteIdMeta,
remoteId.isAcceptableOrUnknown(data['remote_id']!, _remoteIdMeta),
);
} else if (isInserting) {
context.missing(_remoteIdMeta);
}
if (data.containsKey('created_at')) {
context.handle(
_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
);
}
return context;
}
@override
Set<i0.GeneratedColumn> get $primaryKey => {id};
@override
i1.LocalTrashedAssetEntityData map(
Map<String, dynamic> data, {
String? tablePrefix,
}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return i1.LocalTrashedAssetEntityData(
id: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}id'],
)!,
remoteId: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}remote_id'],
)!,
createdAt: attachedDatabase.typeMapping.read(
i0.DriftSqlType.dateTime,
data['${effectivePrefix}created_at'],
)!,
);
}
@override
$LocalTrashedAssetEntityTable createAlias(String alias) {
return $LocalTrashedAssetEntityTable(attachedDatabase, alias);
}
@override
bool get withoutRowId => true;
@override
bool get isStrict => true;
}
class LocalTrashedAssetEntityData extends i0.DataClass
implements i0.Insertable<i1.LocalTrashedAssetEntityData> {
final String id;
final String remoteId;
final DateTime createdAt;
const LocalTrashedAssetEntityData({
required this.id,
required this.remoteId,
required this.createdAt,
});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
map['id'] = i0.Variable<String>(id);
map['remote_id'] = i0.Variable<String>(remoteId);
map['created_at'] = i0.Variable<DateTime>(createdAt);
return map;
}
factory LocalTrashedAssetEntityData.fromJson(
Map<String, dynamic> json, {
i0.ValueSerializer? serializer,
}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return LocalTrashedAssetEntityData(
id: serializer.fromJson<String>(json['id']),
remoteId: serializer.fromJson<String>(json['remoteId']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
);
}
@override
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<String>(id),
'remoteId': serializer.toJson<String>(remoteId),
'createdAt': serializer.toJson<DateTime>(createdAt),
};
}
i1.LocalTrashedAssetEntityData copyWith({
String? id,
String? remoteId,
DateTime? createdAt,
}) => i1.LocalTrashedAssetEntityData(
id: id ?? this.id,
remoteId: remoteId ?? this.remoteId,
createdAt: createdAt ?? this.createdAt,
);
LocalTrashedAssetEntityData copyWithCompanion(
i1.LocalTrashedAssetEntityCompanion data,
) {
return LocalTrashedAssetEntityData(
id: data.id.present ? data.id.value : this.id,
remoteId: data.remoteId.present ? data.remoteId.value : this.remoteId,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
);
}
@override
String toString() {
return (StringBuffer('LocalTrashedAssetEntityData(')
..write('id: $id, ')
..write('remoteId: $remoteId, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, remoteId, createdAt);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is i1.LocalTrashedAssetEntityData &&
other.id == this.id &&
other.remoteId == this.remoteId &&
other.createdAt == this.createdAt);
}
class LocalTrashedAssetEntityCompanion
extends i0.UpdateCompanion<i1.LocalTrashedAssetEntityData> {
final i0.Value<String> id;
final i0.Value<String> remoteId;
final i0.Value<DateTime> createdAt;
const LocalTrashedAssetEntityCompanion({
this.id = const i0.Value.absent(),
this.remoteId = const i0.Value.absent(),
this.createdAt = const i0.Value.absent(),
});
LocalTrashedAssetEntityCompanion.insert({
required String id,
required String remoteId,
this.createdAt = const i0.Value.absent(),
}) : id = i0.Value(id),
remoteId = i0.Value(remoteId);
static i0.Insertable<i1.LocalTrashedAssetEntityData> custom({
i0.Expression<String>? id,
i0.Expression<String>? remoteId,
i0.Expression<DateTime>? createdAt,
}) {
return i0.RawValuesInsertable({
if (id != null) 'id': id,
if (remoteId != null) 'remote_id': remoteId,
if (createdAt != null) 'created_at': createdAt,
});
}
i1.LocalTrashedAssetEntityCompanion copyWith({
i0.Value<String>? id,
i0.Value<String>? remoteId,
i0.Value<DateTime>? createdAt,
}) {
return i1.LocalTrashedAssetEntityCompanion(
id: id ?? this.id,
remoteId: remoteId ?? this.remoteId,
createdAt: createdAt ?? this.createdAt,
);
}
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
if (id.present) {
map['id'] = i0.Variable<String>(id.value);
}
if (remoteId.present) {
map['remote_id'] = i0.Variable<String>(remoteId.value);
}
if (createdAt.present) {
map['created_at'] = i0.Variable<DateTime>(createdAt.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('LocalTrashedAssetEntityCompanion(')
..write('id: $id, ')
..write('remoteId: $remoteId, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
}
}

View file

@ -0,0 +1,42 @@
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/infrastructure/entities/trashed_local_asset.entity.drift.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)')
class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin {
const TrashedLocalAssetEntity();
TextColumn get id => text()();
TextColumn get albumId => text()();
TextColumn get checksum => text().nullable()();
TextColumn get name => text()();
IntColumn get type => intEnum<AssetType>()();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
IntColumn get size => integer().nullable()();
@override
Set<Column> get primaryKey => {id};
}
extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityData {
TrashedAsset toDto(String albumId) => TrashedAsset(
id: id,
name: name,
albumId: albumId,
checksum: checksum,
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
size: size,
);
}

View file

@ -0,0 +1,794 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'
as i1;
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2;
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'
as i3;
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder =
i1.TrashedLocalAssetEntityCompanion Function({
required String id,
required String albumId,
i0.Value<String?> checksum,
required String name,
required i2.AssetType type,
i0.Value<DateTime> createdAt,
i0.Value<DateTime> updatedAt,
i0.Value<int?> size,
});
typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
i1.TrashedLocalAssetEntityCompanion Function({
i0.Value<String> id,
i0.Value<String> albumId,
i0.Value<String?> checksum,
i0.Value<String> name,
i0.Value<i2.AssetType> type,
i0.Value<DateTime> createdAt,
i0.Value<DateTime> updatedAt,
i0.Value<int?> size,
});
class $$TrashedLocalAssetEntityTableFilterComposer
extends
i0.Composer<i0.GeneratedDatabase, i1.$TrashedLocalAssetEntityTable> {
$$TrashedLocalAssetEntityTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnFilters<String> get id => $composableBuilder(
column: $table.id,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<String> get albumId => $composableBuilder(
column: $table.albumId,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<String> get checksum => $composableBuilder(
column: $table.checksum,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<String> get name => $composableBuilder(
column: $table.name,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnWithTypeConverterFilters<i2.AssetType, i2.AssetType, int> get type =>
$composableBuilder(
column: $table.type,
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
);
i0.ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
column: $table.updatedAt,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<int> get size => $composableBuilder(
column: $table.size,
builder: (column) => i0.ColumnFilters(column),
);
}
class $$TrashedLocalAssetEntityTableOrderingComposer
extends
i0.Composer<i0.GeneratedDatabase, i1.$TrashedLocalAssetEntityTable> {
$$TrashedLocalAssetEntityTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnOrderings<String> get id => $composableBuilder(
column: $table.id,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<String> get albumId => $composableBuilder(
column: $table.albumId,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<String> get checksum => $composableBuilder(
column: $table.checksum,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<String> get name => $composableBuilder(
column: $table.name,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<int> get type => $composableBuilder(
column: $table.type,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
column: $table.updatedAt,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<int> get size => $composableBuilder(
column: $table.size,
builder: (column) => i0.ColumnOrderings(column),
);
}
class $$TrashedLocalAssetEntityTableAnnotationComposer
extends
i0.Composer<i0.GeneratedDatabase, i1.$TrashedLocalAssetEntityTable> {
$$TrashedLocalAssetEntityTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.GeneratedColumn<String> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
i0.GeneratedColumn<String> get albumId =>
$composableBuilder(column: $table.albumId, builder: (column) => column);
i0.GeneratedColumn<String> get checksum =>
$composableBuilder(column: $table.checksum, builder: (column) => column);
i0.GeneratedColumn<String> get name =>
$composableBuilder(column: $table.name, builder: (column) => column);
i0.GeneratedColumnWithTypeConverter<i2.AssetType, int> get type =>
$composableBuilder(column: $table.type, builder: (column) => column);
i0.GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
i0.GeneratedColumn<DateTime> get updatedAt =>
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
i0.GeneratedColumn<int> get size =>
$composableBuilder(column: $table.size, builder: (column) => column);
}
class $$TrashedLocalAssetEntityTableTableManager
extends
i0.RootTableManager<
i0.GeneratedDatabase,
i1.$TrashedLocalAssetEntityTable,
i1.TrashedLocalAssetEntityData,
i1.$$TrashedLocalAssetEntityTableFilterComposer,
i1.$$TrashedLocalAssetEntityTableOrderingComposer,
i1.$$TrashedLocalAssetEntityTableAnnotationComposer,
$$TrashedLocalAssetEntityTableCreateCompanionBuilder,
$$TrashedLocalAssetEntityTableUpdateCompanionBuilder,
(
i1.TrashedLocalAssetEntityData,
i0.BaseReferences<
i0.GeneratedDatabase,
i1.$TrashedLocalAssetEntityTable,
i1.TrashedLocalAssetEntityData
>,
),
i1.TrashedLocalAssetEntityData,
i0.PrefetchHooks Function()
> {
$$TrashedLocalAssetEntityTableTableManager(
i0.GeneratedDatabase db,
i1.$TrashedLocalAssetEntityTable table,
) : super(
i0.TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
i1.$$TrashedLocalAssetEntityTableFilterComposer(
$db: db,
$table: table,
),
createOrderingComposer: () =>
i1.$$TrashedLocalAssetEntityTableOrderingComposer(
$db: db,
$table: table,
),
createComputedFieldComposer: () =>
i1.$$TrashedLocalAssetEntityTableAnnotationComposer(
$db: db,
$table: table,
),
updateCompanionCallback:
({
i0.Value<String> id = const i0.Value.absent(),
i0.Value<String> albumId = const i0.Value.absent(),
i0.Value<String?> checksum = const i0.Value.absent(),
i0.Value<String> name = const i0.Value.absent(),
i0.Value<i2.AssetType> type = const i0.Value.absent(),
i0.Value<DateTime> createdAt = const i0.Value.absent(),
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> size = const i0.Value.absent(),
}) => i1.TrashedLocalAssetEntityCompanion(
id: id,
albumId: albumId,
checksum: checksum,
name: name,
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
size: size,
),
createCompanionCallback:
({
required String id,
required String albumId,
i0.Value<String?> checksum = const i0.Value.absent(),
required String name,
required i2.AssetType type,
i0.Value<DateTime> createdAt = const i0.Value.absent(),
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
i0.Value<int?> size = const i0.Value.absent(),
}) => i1.TrashedLocalAssetEntityCompanion.insert(
id: id,
albumId: albumId,
checksum: checksum,
name: name,
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
size: size,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
),
);
}
typedef $$TrashedLocalAssetEntityTableProcessedTableManager =
i0.ProcessedTableManager<
i0.GeneratedDatabase,
i1.$TrashedLocalAssetEntityTable,
i1.TrashedLocalAssetEntityData,
i1.$$TrashedLocalAssetEntityTableFilterComposer,
i1.$$TrashedLocalAssetEntityTableOrderingComposer,
i1.$$TrashedLocalAssetEntityTableAnnotationComposer,
$$TrashedLocalAssetEntityTableCreateCompanionBuilder,
$$TrashedLocalAssetEntityTableUpdateCompanionBuilder,
(
i1.TrashedLocalAssetEntityData,
i0.BaseReferences<
i0.GeneratedDatabase,
i1.$TrashedLocalAssetEntityTable,
i1.TrashedLocalAssetEntityData
>,
),
i1.TrashedLocalAssetEntityData,
i0.PrefetchHooks Function()
>;
i0.Index get idxTrashedLocalAssetChecksum => i0.Index(
'idx_trashed_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
);
class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
with
i0.TableInfo<
$TrashedLocalAssetEntityTable,
i1.TrashedLocalAssetEntityData
> {
@override
final i0.GeneratedDatabase attachedDatabase;
final String? _alias;
$TrashedLocalAssetEntityTable(this.attachedDatabase, [this._alias]);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
'id',
aliasedName,
false,
type: i0.DriftSqlType.string,
requiredDuringInsert: true,
);
static const i0.VerificationMeta _albumIdMeta = const i0.VerificationMeta(
'albumId',
);
@override
late final i0.GeneratedColumn<String> albumId = i0.GeneratedColumn<String>(
'album_id',
aliasedName,
false,
type: i0.DriftSqlType.string,
requiredDuringInsert: true,
);
static const i0.VerificationMeta _checksumMeta = const i0.VerificationMeta(
'checksum',
);
@override
late final i0.GeneratedColumn<String> checksum = i0.GeneratedColumn<String>(
'checksum',
aliasedName,
true,
type: i0.DriftSqlType.string,
requiredDuringInsert: false,
);
static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta(
'name',
);
@override
late final i0.GeneratedColumn<String> name = i0.GeneratedColumn<String>(
'name',
aliasedName,
false,
type: i0.DriftSqlType.string,
requiredDuringInsert: true,
);
@override
late final i0.GeneratedColumnWithTypeConverter<i2.AssetType, int> type =
i0.GeneratedColumn<int>(
'type',
aliasedName,
false,
type: i0.DriftSqlType.int,
requiredDuringInsert: true,
).withConverter<i2.AssetType>(
i1.$TrashedLocalAssetEntityTable.$convertertype,
);
static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta(
'createdAt',
);
@override
late final i0.GeneratedColumn<DateTime> createdAt =
i0.GeneratedColumn<DateTime>(
'created_at',
aliasedName,
false,
type: i0.DriftSqlType.dateTime,
requiredDuringInsert: false,
defaultValue: i4.currentDateAndTime,
);
static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta(
'updatedAt',
);
@override
late final i0.GeneratedColumn<DateTime> updatedAt =
i0.GeneratedColumn<DateTime>(
'updated_at',
aliasedName,
false,
type: i0.DriftSqlType.dateTime,
requiredDuringInsert: false,
defaultValue: i4.currentDateAndTime,
);
static const i0.VerificationMeta _sizeMeta = const i0.VerificationMeta(
'size',
);
@override
late final i0.GeneratedColumn<int> size = i0.GeneratedColumn<int>(
'size',
aliasedName,
true,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
);
@override
List<i0.GeneratedColumn> get $columns => [
id,
albumId,
checksum,
name,
type,
createdAt,
updatedAt,
size,
];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'trashed_local_asset_entity';
@override
i0.VerificationContext validateIntegrity(
i0.Insertable<i1.TrashedLocalAssetEntityData> instance, {
bool isInserting = false,
}) {
final context = i0.VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
} else if (isInserting) {
context.missing(_idMeta);
}
if (data.containsKey('album_id')) {
context.handle(
_albumIdMeta,
albumId.isAcceptableOrUnknown(data['album_id']!, _albumIdMeta),
);
} else if (isInserting) {
context.missing(_albumIdMeta);
}
if (data.containsKey('checksum')) {
context.handle(
_checksumMeta,
checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta),
);
}
if (data.containsKey('name')) {
context.handle(
_nameMeta,
name.isAcceptableOrUnknown(data['name']!, _nameMeta),
);
} else if (isInserting) {
context.missing(_nameMeta);
}
if (data.containsKey('created_at')) {
context.handle(
_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
);
}
if (data.containsKey('updated_at')) {
context.handle(
_updatedAtMeta,
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
);
}
if (data.containsKey('size')) {
context.handle(
_sizeMeta,
size.isAcceptableOrUnknown(data['size']!, _sizeMeta),
);
}
return context;
}
@override
Set<i0.GeneratedColumn> get $primaryKey => {id};
@override
i1.TrashedLocalAssetEntityData map(
Map<String, dynamic> data, {
String? tablePrefix,
}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return i1.TrashedLocalAssetEntityData(
id: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}id'],
)!,
albumId: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}album_id'],
)!,
checksum: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}checksum'],
),
name: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}name'],
)!,
type: i1.$TrashedLocalAssetEntityTable.$convertertype.fromSql(
attachedDatabase.typeMapping.read(
i0.DriftSqlType.int,
data['${effectivePrefix}type'],
)!,
),
createdAt: attachedDatabase.typeMapping.read(
i0.DriftSqlType.dateTime,
data['${effectivePrefix}created_at'],
)!,
updatedAt: attachedDatabase.typeMapping.read(
i0.DriftSqlType.dateTime,
data['${effectivePrefix}updated_at'],
)!,
size: attachedDatabase.typeMapping.read(
i0.DriftSqlType.int,
data['${effectivePrefix}size'],
),
);
}
@override
$TrashedLocalAssetEntityTable createAlias(String alias) {
return $TrashedLocalAssetEntityTable(attachedDatabase, alias);
}
static i0.JsonTypeConverter2<i2.AssetType, int, int> $convertertype =
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
@override
bool get withoutRowId => true;
@override
bool get isStrict => true;
}
class TrashedLocalAssetEntityData extends i0.DataClass
implements i0.Insertable<i1.TrashedLocalAssetEntityData> {
final String id;
final String albumId;
final String? checksum;
final String name;
final i2.AssetType type;
final DateTime createdAt;
final DateTime updatedAt;
final int? size;
const TrashedLocalAssetEntityData({
required this.id,
required this.albumId,
this.checksum,
required this.name,
required this.type,
required this.createdAt,
required this.updatedAt,
this.size,
});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
map['id'] = i0.Variable<String>(id);
map['album_id'] = i0.Variable<String>(albumId);
if (!nullToAbsent || checksum != null) {
map['checksum'] = i0.Variable<String>(checksum);
}
map['name'] = i0.Variable<String>(name);
{
map['type'] = i0.Variable<int>(
i1.$TrashedLocalAssetEntityTable.$convertertype.toSql(type),
);
}
map['created_at'] = i0.Variable<DateTime>(createdAt);
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
if (!nullToAbsent || size != null) {
map['size'] = i0.Variable<int>(size);
}
return map;
}
factory TrashedLocalAssetEntityData.fromJson(
Map<String, dynamic> json, {
i0.ValueSerializer? serializer,
}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
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']),
type: i1.$TrashedLocalAssetEntityTable.$convertertype.fromJson(
serializer.fromJson<int>(json['type']),
),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
size: serializer.fromJson<int?>(json['size']),
);
}
@override
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<String>(id),
'albumId': serializer.toJson<String>(albumId),
'checksum': serializer.toJson<String?>(checksum),
'name': serializer.toJson<String>(name),
'type': serializer.toJson<int>(
i1.$TrashedLocalAssetEntityTable.$convertertype.toJson(type),
),
'createdAt': serializer.toJson<DateTime>(createdAt),
'updatedAt': serializer.toJson<DateTime>(updatedAt),
'size': serializer.toJson<int?>(size),
};
}
i1.TrashedLocalAssetEntityData copyWith({
String? id,
String? albumId,
i0.Value<String?> checksum = const i0.Value.absent(),
String? name,
i2.AssetType? type,
DateTime? createdAt,
DateTime? updatedAt,
i0.Value<int?> size = const i0.Value.absent(),
}) => i1.TrashedLocalAssetEntityData(
id: id ?? this.id,
albumId: albumId ?? this.albumId,
checksum: checksum.present ? checksum.value : this.checksum,
name: name ?? this.name,
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
size: size.present ? size.value : this.size,
);
TrashedLocalAssetEntityData copyWithCompanion(
i1.TrashedLocalAssetEntityCompanion data,
) {
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,
type: data.type.present ? data.type.value : this.type,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
size: data.size.present ? data.size.value : this.size,
);
}
@override
String toString() {
return (StringBuffer('TrashedLocalAssetEntityData(')
..write('id: $id, ')
..write('albumId: $albumId, ')
..write('checksum: $checksum, ')
..write('name: $name, ')
..write('type: $type, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('size: $size')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(
id,
albumId,
checksum,
name,
type,
createdAt,
updatedAt,
size,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is i1.TrashedLocalAssetEntityData &&
other.id == this.id &&
other.albumId == this.albumId &&
other.checksum == this.checksum &&
other.name == this.name &&
other.type == this.type &&
other.createdAt == this.createdAt &&
other.updatedAt == this.updatedAt &&
other.size == this.size);
}
class TrashedLocalAssetEntityCompanion
extends i0.UpdateCompanion<i1.TrashedLocalAssetEntityData> {
final i0.Value<String> id;
final i0.Value<String> albumId;
final i0.Value<String?> checksum;
final i0.Value<String> name;
final i0.Value<i2.AssetType> type;
final i0.Value<DateTime> createdAt;
final i0.Value<DateTime> updatedAt;
final i0.Value<int?> size;
const TrashedLocalAssetEntityCompanion({
this.id = const i0.Value.absent(),
this.albumId = const i0.Value.absent(),
this.checksum = const i0.Value.absent(),
this.name = const i0.Value.absent(),
this.type = const i0.Value.absent(),
this.createdAt = const i0.Value.absent(),
this.updatedAt = const i0.Value.absent(),
this.size = const i0.Value.absent(),
});
TrashedLocalAssetEntityCompanion.insert({
required String id,
required String albumId,
this.checksum = const i0.Value.absent(),
required String name,
required i2.AssetType type,
this.createdAt = const i0.Value.absent(),
this.updatedAt = const i0.Value.absent(),
this.size = const i0.Value.absent(),
}) : id = i0.Value(id),
albumId = i0.Value(albumId),
name = i0.Value(name),
type = i0.Value(type);
static i0.Insertable<i1.TrashedLocalAssetEntityData> custom({
i0.Expression<String>? id,
i0.Expression<String>? albumId,
i0.Expression<String>? checksum,
i0.Expression<String>? name,
i0.Expression<int>? type,
i0.Expression<DateTime>? createdAt,
i0.Expression<DateTime>? updatedAt,
i0.Expression<int>? size,
}) {
return i0.RawValuesInsertable({
if (id != null) 'id': id,
if (albumId != null) 'album_id': albumId,
if (checksum != null) 'checksum': checksum,
if (name != null) 'name': name,
if (type != null) 'type': type,
if (createdAt != null) 'created_at': createdAt,
if (updatedAt != null) 'updated_at': updatedAt,
if (size != null) 'size': size,
});
}
i1.TrashedLocalAssetEntityCompanion copyWith({
i0.Value<String>? id,
i0.Value<String>? albumId,
i0.Value<String?>? checksum,
i0.Value<String>? name,
i0.Value<i2.AssetType>? type,
i0.Value<DateTime>? createdAt,
i0.Value<DateTime>? updatedAt,
i0.Value<int?>? size,
}) {
return i1.TrashedLocalAssetEntityCompanion(
id: id ?? this.id,
albumId: albumId ?? this.albumId,
checksum: checksum ?? this.checksum,
name: name ?? this.name,
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
size: size ?? this.size,
);
}
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
if (id.present) {
map['id'] = i0.Variable<String>(id.value);
}
if (albumId.present) {
map['album_id'] = i0.Variable<String>(albumId.value);
}
if (checksum.present) {
map['checksum'] = i0.Variable<String>(checksum.value);
}
if (name.present) {
map['name'] = i0.Variable<String>(name.value);
}
if (type.present) {
map['type'] = i0.Variable<int>(
i1.$TrashedLocalAssetEntityTable.$convertertype.toSql(type.value),
);
}
if (createdAt.present) {
map['created_at'] = i0.Variable<DateTime>(createdAt.value);
}
if (updatedAt.present) {
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
}
if (size.present) {
map['size'] = i0.Variable<int>(size.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('TrashedLocalAssetEntityCompanion(')
..write('id: $id, ')
..write('albumId: $albumId, ')
..write('checksum: $checksum, ')
..write('name: $name, ')
..write('type: $type, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('size: $size')
..write(')'))
.toString();
}
}

View file

@ -10,7 +10,7 @@ import 'package:immich_mobile/infrastructure/entities/exif.entity.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_album_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.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_trashed_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
@ -63,7 +63,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
PersonEntity, PersonEntity,
AssetFaceEntity, AssetFaceEntity,
StoreEntity, StoreEntity,
LocalTrashedAssetEntity, TrashedLocalAssetEntity,
], ],
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'}, include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
) )
@ -136,8 +136,8 @@ class Drift extends $Drift implements IDatabaseRepository {
await m.alterTable(TableMigration(v10.userEntity)); await m.alterTable(TableMigration(v10.userEntity));
}, },
from10To11: (m, v11) async { from10To11: (m, v11) async {
await m.create(v11.localTrashedAssetEntity); await m.create(v11.trashedLocalAssetEntity);
await m.createIndex(v11.idxLocalTrashedAssetRemoteId); await m.createIndex(v11.idxTrashedLocalAssetChecksum);
}, },
), ),
); );

View file

@ -37,7 +37,7 @@ import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.da
as i17; as i17;
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
as i18; as i18;
import 'package:immich_mobile/infrastructure/entities/local_trashed_asset.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'
as i19; as i19;
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
as i20; as i20;
@ -79,8 +79,8 @@ abstract class $Drift extends i0.GeneratedDatabase {
late final i17.$AssetFaceEntityTable assetFaceEntity = i17 late final i17.$AssetFaceEntityTable assetFaceEntity = i17
.$AssetFaceEntityTable(this); .$AssetFaceEntityTable(this);
late final i18.$StoreEntityTable storeEntity = i18.$StoreEntityTable(this); late final i18.$StoreEntityTable storeEntity = i18.$StoreEntityTable(this);
late final i19.$LocalTrashedAssetEntityTable localTrashedAssetEntity = i19 late final i19.$TrashedLocalAssetEntityTable trashedLocalAssetEntity = i19
.$LocalTrashedAssetEntityTable(this); .$TrashedLocalAssetEntityTable(this);
i20.MergedAssetDrift get mergedAssetDrift => i21.ReadDatabaseContainer( i20.MergedAssetDrift get mergedAssetDrift => i21.ReadDatabaseContainer(
this, this,
).accessor<i20.MergedAssetDrift>(i20.MergedAssetDrift.new); ).accessor<i20.MergedAssetDrift>(i20.MergedAssetDrift.new);
@ -112,9 +112,9 @@ abstract class $Drift extends i0.GeneratedDatabase {
personEntity, personEntity,
assetFaceEntity, assetFaceEntity,
storeEntity, storeEntity,
localTrashedAssetEntity, trashedLocalAssetEntity,
i11.idxLatLng, i11.idxLatLng,
i19.idxLocalTrashedAssetRemoteId, i19.idxTrashedLocalAssetChecksum,
]; ];
@override @override
i0.StreamQueryUpdateRules i0.StreamQueryUpdateRules
@ -294,18 +294,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
), ),
result: [i0.TableUpdate('asset_face_entity', kind: i0.UpdateKind.update)], result: [i0.TableUpdate('asset_face_entity', kind: i0.UpdateKind.update)],
), ),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName(
'remote_asset_entity',
limitUpdateKind: i0.UpdateKind.delete,
),
result: [
i0.TableUpdate(
'local_trashed_asset_entity',
kind: i0.UpdateKind.delete,
),
],
),
]); ]);
@override @override
i0.DriftDatabaseOptions get options => i0.DriftDatabaseOptions get options =>
@ -354,9 +342,9 @@ class $DriftManager {
i17.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity); i17.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
i18.$$StoreEntityTableTableManager get storeEntity => i18.$$StoreEntityTableTableManager get storeEntity =>
i18.$$StoreEntityTableTableManager(_db, _db.storeEntity); i18.$$StoreEntityTableTableManager(_db, _db.storeEntity);
i19.$$LocalTrashedAssetEntityTableTableManager get localTrashedAssetEntity => i19.$$TrashedLocalAssetEntityTableTableManager get trashedLocalAssetEntity =>
i19.$$LocalTrashedAssetEntityTableTableManager( i19.$$TrashedLocalAssetEntityTableTableManager(
_db, _db,
_db.localTrashedAssetEntity, _db.trashedLocalAssetEntity,
); );
} }

View file

@ -4298,9 +4298,9 @@ final class Schema11 extends i0.VersionedSchema {
personEntity, personEntity,
assetFaceEntity, assetFaceEntity,
storeEntity, storeEntity,
localTrashedAssetEntity, trashedLocalAssetEntity,
idxLatLng, idxLatLng,
idxLocalTrashedAssetRemoteId, idxTrashedLocalAssetChecksum,
]; ];
late final Shape20 userEntity = Shape20( late final Shape20 userEntity = Shape20(
source: i0.VersionedTable( source: i0.VersionedTable(
@ -4645,13 +4645,22 @@ final class Schema11 extends i0.VersionedSchema {
), ),
alias: null, alias: null,
); );
late final Shape22 localTrashedAssetEntity = Shape22( late final Shape22 trashedLocalAssetEntity = Shape22(
source: i0.VersionedTable( source: i0.VersionedTable(
entityName: 'local_trashed_asset_entity', entityName: 'trashed_local_asset_entity',
withoutRowId: true, withoutRowId: true,
isStrict: true, isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'], tableConstraints: ['PRIMARY KEY(id)'],
columns: [_column_0, _column_95, _column_9], columns: [
_column_0,
_column_95,
_column_22,
_column_1,
_column_8,
_column_9,
_column_5,
_column_96,
],
attachedDatabase: database, attachedDatabase: database,
), ),
alias: null, alias: null,
@ -4660,9 +4669,9 @@ final class Schema11 extends i0.VersionedSchema {
'idx_lat_lng', 'idx_lat_lng',
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
); );
final i1.Index idxLocalTrashedAssetRemoteId = i1.Index( final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
'idx_local_trashed_asset_remote_id', 'idx_trashed_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_local_trashed_asset_remote_id ON local_trashed_asset_entity (remote_id)', 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
); );
} }
@ -4670,21 +4679,35 @@ class Shape22 extends i0.VersionedTable {
Shape22({required super.source, required super.alias}) : super.aliased(); Shape22({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get id => i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>; columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get remoteId => i1.GeneratedColumn<String> get albumId =>
columnsByName['remote_id']! as i1.GeneratedColumn<String>; columnsByName['album_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get checksum =>
columnsByName['checksum']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get type =>
columnsByName['type']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<DateTime> get createdAt => i1.GeneratedColumn<DateTime> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>; columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get updatedAt =>
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<int> get size =>
columnsByName['size']! as i1.GeneratedColumn<int>;
} }
i1.GeneratedColumn<String> _column_95(String aliasedName) => i1.GeneratedColumn<String> _column_95(String aliasedName) =>
i1.GeneratedColumn<String>( i1.GeneratedColumn<String>(
'remote_id', 'album_id',
aliasedName, aliasedName,
false, false,
type: i1.DriftSqlType.string, type: i1.DriftSqlType.string,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways( );
'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', i1.GeneratedColumn<int> _column_96(String aliasedName) =>
), i1.GeneratedColumn<int>(
'size',
aliasedName,
true,
type: i1.DriftSqlType.int,
); );
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

@ -3,14 +3,12 @@ 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/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/domain/models/local_trashed_asset.model.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/local_trashed_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/local_trashed_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
typedef LocalRemoteIds = ({String localId, String remoteId}); typedef AssetsByAlbums = Map<String, List<LocalAsset>>;
class DriftLocalAssetRepository extends DriftDatabaseRepository { class DriftLocalAssetRepository extends DriftDatabaseRepository {
final Drift _db; final Drift _db;
@ -58,7 +56,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
}); });
} }
Future<void> delete(Iterable<String> ids) { Future<void> delete(List<String> ids) {
if (ids.isEmpty) { if (ids.isEmpty) {
return Future.value(); return Future.value();
} }
@ -70,27 +68,37 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
}); });
} }
Future<void> trash(Iterable<LocalRemoteIds> ids) async { Future<void> trash(AssetsByAlbums assetsByAlbums) async {
if (ids.isEmpty) return; if (assetsByAlbums.isEmpty) {
return;
final Map<String, String> idToRemote = {for (final e in ids) e.localId: e.remoteId};
final localRows = await (_db.localAssetEntity.select()..where((t) => t.id.isIn(idToRemote.keys))).get();
await _db.batch((batch) {
for (final row in localRows) {
final remoteId = idToRemote[row.id];
if (remoteId == null) {
continue;
} }
batch.insert(
_db.localTrashedAssetEntity, final companions = <TrashedLocalAssetEntityCompanion>[];
LocalTrashedAssetEntityCompanion(id: Value(row.id), remoteId: Value(remoteId)), final idToDelete = <String>{};
mode: InsertMode.insertOrReplace,
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),
),
); );
} }
for (final slice in idToRemote.keys.slices(32000)) { });
batch.deleteWhere(_db.localAssetEntity, (e) => e.id.isIn(slice));
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();
} }
}); });
} }
@ -128,41 +136,30 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
return query.map((localAlbum) => localAlbum.toDto()).get(); return query.map((localAlbum) => localAlbum.toDto()).get();
} }
Future<List<LocalAsset>> getBackupSelectedAssets(Iterable<String> checksums) { Future<AssetsByAlbums> getBackupSelectedAssetsByAlbum(Iterable<String> checksums) async {
if (checksums.isEmpty) { if (checksums.isEmpty) {
return Future.value([]); return {};
}
final backedUpAssetIds = _db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId])
..join([
innerJoin(
_db.localAlbumEntity,
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
useColumns: false,
),
])
..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected));
final query = _db.localAssetEntity.select()
..where((la) => la.checksum.isIn(checksums) & la.id.isInQuery(backedUpAssetIds));
return query.map((row) => row.toDto()).get();
} }
Future<List<LocalTrashedAsset>> getLocalTrashedAssets(Iterable<String> remoteIds) { final lAlbumAsset = _db.localAlbumAssetEntity;
if (remoteIds.isEmpty) { final lAlbum = _db.localAlbumEntity;
return Future.value([]); final lAsset = _db.localAssetEntity;
}
final query = _db.localTrashedAssetEntity.select()..where((t) => t.remoteId.isIn(remoteIds));
return query.map((row) => row.toDto()).get();
}
Future<void> deleteLocalTrashedAssets(Iterable<String> remoteIds) { final result = <String, List<LocalAsset>>{};
if (remoteIds.isEmpty) {
return Future.value(); for (final slice in checksums.toSet().slices(800)) {
final rows = await (_db.select(lAlbumAsset).join([
innerJoin(lAlbum, lAlbumAsset.albumId.equalsExp(lAlbum.id)),
innerJoin(lAsset, lAlbumAsset.assetId.equalsExp(lAsset.id)),
])..where(lAlbum.backupSelection.equalsValue(BackupSelection.selected) & lAsset.checksum.isIn(slice))).get();
for (final row in rows) {
final albumId = row.readTable(lAlbumAsset).albumId;
final assetData = row.readTable(lAsset);
final asset = assetData.toDto();
(result[albumId] ??= <LocalAsset>[]).add(asset);
} }
return _db.batch((batch) {
for (final slice in remoteIds.slices(32000)) {
batch.deleteWhere(_db.localTrashedAssetEntity, (e) => e.remoteId.isIn(slice));
} }
}); return result;
} }
} }

View file

@ -241,13 +241,4 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
Future<int> getCount() { Future<int> getCount() {
return _db.managers.remoteAssetEntity.count(); return _db.managers.remoteAssetEntity.count();
} }
Future<List<RemoteAsset>> getByChecksums(Iterable<String> checksums, {bool? isTrashed}) {
if (checksums.isEmpty) return Future.value([]);
final query = _db.remoteAssetEntity.select()..where((rae) => rae.checksum.isIn(checksums));
if (isTrashed != null) {
query.where((rae) => isTrashed ? rae.deletedAt.isNotNull() : rae.deletedAt.isNull());
}
return query.map((row) => row.toDto()).get();
}
} }

View file

@ -0,0 +1,122 @@
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/trashed_asset.model.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/repositories/db.repository.dart';
class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftTrashedLocalAssetRepository(this._db) : super(_db);
Future<void> updateChecksums(Iterable<TrashedAsset> assets) {
if (assets.isEmpty) {
return Future.value();
}
return _db.batch((batch) async {
for (final asset in assets) {
batch.update(
_db.trashedLocalAssetEntity,
TrashedLocalAssetEntityCompanion(checksum: Value(asset.checksum)),
where: (e) => e.id.equals(asset.id),
);
}
});
}
Future<Iterable<TrashedAsset>> getToHash(String albumId) {
final query = _db.trashedLocalAssetEntity.select()..where((r) => r.albumId.equals(albumId) & r.checksum.isNull());
return query.map((row) => row.toDto(albumId)).get();
}
Future<List<TrashedAsset>> getToRestore() async {
final trashed = _db.trashedLocalAssetEntity;
final remote = _db.remoteAssetEntity;
final album = _db.localAlbumEntity;
final selectedAlbumIds = (_db.selectOnly(album)
..addColumns([album.id])
..where(album.backupSelection.equalsValue(BackupSelection.selected)));
final rows = await (_db.select(trashed).join([
innerJoin(remote, remote.checksum.equalsExp(trashed.checksum)),
])..where(trashed.albumId.isInQuery(selectedAlbumIds) & remote.deletedAt.isNull())).get();
return rows.map((result) {
final assetData = result.readTable(trashed);
return assetData.toDto(assetData.albumId);
}).toList();
}
/// Applies resulted snapshot of trashed assets:
/// - upserts incoming rows
/// - deletes rows that are not present in the snapshot
Future<void> applyTrashSnapshot(Iterable<TrashedAsset> assets, String albumId) async {
if (assets.isEmpty) {
await _db.delete(_db.trashedLocalAssetEntity).go();
return;
}
return _db.transaction(() async {
final table = _db.trashedLocalAssetEntity;
final companions = assets.map(
(a) => TrashedLocalAssetEntityCompanion.insert(
id: a.id,
albumId: albumId,
checksum: a.checksum == null ? const Value.absent() : Value(a.checksum),
name: a.name,
type: a.type,
createdAt: Value(a.createdAt),
updatedAt: Value(a.updatedAt),
size: a.size == null ? const Value.absent() : Value(a.size),
),
);
for (final slice in companions.slices(400)) {
await _db.batch((b) {
b.insertAllOnConflictUpdate(table, slice);
});
}
final keepIds = assets.map((asset) => asset.id);
if (keepIds.length <= 900) {
await (_db.delete(table)..where((row) => row.id.isNotIn(keepIds))).go();
} else {
final existingIds = await (_db.selectOnly(table)..addColumns([table.id])).map((r) => r.read(table.id)!).get();
final toDelete = existingIds.where((id) => !keepIds.contains(id));
for (final slice in toDelete.slices(400)) {
await (_db.delete(table)..where((row) => row.id.isIn(slice))).go();
}
}
});
}
Stream<int> watchCount() {
final t = _db.trashedLocalAssetEntity;
return (_db.selectOnly(t)..addColumns([t.id.count()])).watchSingle().map((row) => row.read<int>(t.id.count()) ?? 0);
}
Stream<int> watchHashedCount() {
final t = _db.trashedLocalAssetEntity;
return (_db.selectOnly(t)
..addColumns([t.id.count()])
..where(t.checksum.isNotNull()))
.watchSingle()
.map((row) => row.read<int>(t.id.count()) ?? 0);
}
Future<void> delete(Iterable<String> ids) {
if (ids.isEmpty) {
return Future.value();
}
return _db.batch((batch) {
for (final slice in ids.slices(32000)) {
batch.deleteWhere(_db.trashedLocalAssetEntity, (e) => e.id.isIn(slice));
}
});
}
}

View file

@ -41,6 +41,7 @@ class PlatformAsset {
required this.durationInSeconds, required this.durationInSeconds,
required this.orientation, required this.orientation,
required this.isFavorite, required this.isFavorite,
this.size,
}); });
String id; String id;
@ -63,8 +64,22 @@ class PlatformAsset {
bool isFavorite; bool isFavorite;
int? size;
List<Object?> _toList() { List<Object?> _toList() {
return <Object?>[id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite]; return <Object?>[
id,
name,
type,
createdAt,
updatedAt,
width,
height,
durationInSeconds,
orientation,
isFavorite,
size,
];
} }
Object encode() { Object encode() {
@ -84,6 +99,7 @@ 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,
size: result[10] as int?,
); );
} }
@ -205,6 +221,45 @@ class SyncDelta {
int get hashCode => Object.hashAll(_toList()); int get hashCode => Object.hashAll(_toList());
} }
class TrashedAssetParams {
TrashedAssetParams({required this.id, required this.type, this.albumId});
String id;
int type;
String? albumId;
List<Object?> _toList() {
return <Object?>[id, type, albumId];
}
Object encode() {
return _toList();
}
static TrashedAssetParams decode(Object result) {
result as List<Object?>;
return TrashedAssetParams(id: result[0]! as String, type: result[1]! as int, albumId: result[2] as String?);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (other is! TrashedAssetParams || other.runtimeType != runtimeType) {
return false;
}
if (identical(this, other)) {
return true;
}
return _deepEquals(encode(), other.encode());
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList());
}
class _PigeonCodec extends StandardMessageCodec { class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec(); const _PigeonCodec();
@override @override
@ -221,6 +276,9 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is SyncDelta) { } else if (value is SyncDelta) {
buffer.putUint8(131); buffer.putUint8(131);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is TrashedAssetParams) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else { } else {
super.writeValue(buffer, value); super.writeValue(buffer, value);
} }
@ -235,6 +293,8 @@ class _PigeonCodec extends StandardMessageCodec {
return PlatformAlbum.decode(readValue(buffer)!); return PlatformAlbum.decode(readValue(buffer)!);
case 131: case 131:
return SyncDelta.decode(readValue(buffer)!); return SyncDelta.decode(readValue(buffer)!);
case 132:
return TrashedAssetParams.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
} }
@ -495,4 +555,60 @@ class NativeSyncApi {
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<Uint8List?>(); return (pigeonVar_replyList[0] as List<Object?>?)!.cast<Uint8List?>();
} }
} }
Future<List<PlatformAsset>> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond}) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[albumId, updatedTimeCond]);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<PlatformAsset>();
}
}
Future<List<Uint8List?>> hashTrashedAssets(List<TrashedAssetParams> trashedAssets) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[trashedAssets]);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<Uint8List?>();
}
}
} }

View file

@ -2,6 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/asset.service.dart'; import 'package:immich_mobile/domain/services/asset.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final localAssetRepository = Provider<DriftLocalAssetRepository>( final localAssetRepository = Provider<DriftLocalAssetRepository>(
@ -12,6 +13,10 @@ final remoteAssetRepositoryProvider = Provider<RemoteAssetRepository>(
(ref) => RemoteAssetRepository(ref.watch(driftProvider)), (ref) => RemoteAssetRepository(ref.watch(driftProvider)),
); );
final trashedLocalAssetRepository = Provider<DriftTrashedLocalAssetRepository>(
(ref) => DriftTrashedLocalAssetRepository(ref.watch(driftProvider)),
);
final assetServiceProvider = Provider( final assetServiceProvider = Provider(
(ref) => AssetService( (ref) => AssetService(
remoteAssetRepository: ref.watch(remoteAssetRepositoryProvider), remoteAssetRepository: ref.watch(remoteAssetRepositoryProvider),

View file

@ -29,6 +29,7 @@ final syncStreamRepositoryProvider = Provider((ref) => SyncStreamRepository(ref.
final localSyncServiceProvider = Provider( final localSyncServiceProvider = Provider(
(ref) => LocalSyncService( (ref) => LocalSyncService(
localAlbumRepository: ref.watch(localAlbumRepository), localAlbumRepository: ref.watch(localAlbumRepository),
trashSyncService: ref.watch(trashSyncServiceProvider),
nativeSyncApi: ref.watch(nativeSyncApiProvider), nativeSyncApi: ref.watch(nativeSyncApiProvider),
), ),
); );
@ -39,5 +40,6 @@ final hashServiceProvider = Provider(
localAssetRepository: ref.watch(localAssetRepository), localAssetRepository: ref.watch(localAssetRepository),
storageRepository: ref.watch(storageRepositoryProvider), storageRepository: ref.watch(storageRepositoryProvider),
nativeSyncApi: ref.watch(nativeSyncApiProvider), nativeSyncApi: ref.watch(nativeSyncApiProvider),
trashSyncService: ref.watch(trashSyncServiceProvider),
), ),
); );

View file

@ -1,17 +1,31 @@
import 'package:async/async.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/trash_sync.service.dart'; import 'package:immich_mobile/domain/services/trash_sync.service.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'asset.provider.dart'; import 'asset.provider.dart';
typedef TrashedAssetsCount = ({int total, int hashed});
final trashSyncServiceProvider = Provider( final trashSyncServiceProvider = Provider(
(ref) => TrashSyncService( (ref) => TrashSyncService(
appSettingsService: ref.watch(appSettingsServiceProvider), appSettingsService: ref.watch(appSettingsServiceProvider),
remoteAssetRepository: ref.watch(remoteAssetRepositoryProvider), nativeSyncApi: ref.watch(nativeSyncApiProvider),
localAssetRepository: ref.watch(localAssetRepository), localAssetRepository: ref.watch(localAssetRepository),
localAlbumRepository: ref.watch(localAlbumRepository),
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
localFilesManager: ref.watch(localFilesManagerRepositoryProvider), localFilesManager: ref.watch(localFilesManagerRepositoryProvider),
storageRepository: ref.watch(storageRepositoryProvider), storageRepository: ref.watch(storageRepositoryProvider),
), ),
); );
final trashedAssetsCountProvider = StreamProvider<TrashedAssetsCount>((ref) {
final repo = ref.watch(trashedLocalAssetRepository);
final total$ = repo.watchCount();
final hashed$ = repo.watchHashedCount();
return StreamZip<int>([total$, hashed$]).map((values) => (total: values[0], hashed: values[1]));
});

View file

@ -5,11 +5,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart';
import 'package:immich_mobile/providers/sync_status.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart';
import 'package:immich_mobile/utils/migration.dart'; import 'package:immich_mobile/utils/migration.dart';
import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart'; import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart';
@ -225,6 +226,7 @@ class _SyncStatsCounts extends ConsumerWidget {
final localAlbumService = ref.watch(localAlbumServiceProvider); final localAlbumService = ref.watch(localAlbumServiceProvider);
final remoteAlbumService = ref.watch(remoteAlbumServiceProvider); final remoteAlbumService = ref.watch(remoteAlbumServiceProvider);
final memoryService = ref.watch(driftMemoryServiceProvider); final memoryService = ref.watch(driftMemoryServiceProvider);
final trashSyncService = ref.watch(trashSyncServiceProvider);
Future<List<dynamic>> loadCounts() async { Future<List<dynamic>> loadCounts() async {
final assetCounts = assetService.getAssetCounts(); final assetCounts = assetService.getAssetCounts();
@ -347,6 +349,42 @@ class _SyncStatsCounts extends ConsumerWidget {
], ],
), ),
), ),
if (trashSyncService.isAutoSyncMode) ...[
_SectionHeaderText(text: "trashed".t(context: context)),
Consumer(
builder: (context, ref, _) {
final counts = ref.watch(trashedAssetsCountProvider);
return counts.when(
data: (c) => Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 8.0,
children: [
Expanded(
child: EntitiyCountTile(
label: "local".t(context: context),
count: c.total,
icon: Icons.delete_outline,
),
),
Expanded(
child: EntitiyCountTile(
label: "hashed_assets".t(context: context),
count: c.hashed,
icon: Icons.tag,
),
),
],
),
),
loading: () => const CircularProgressIndicator(),
error: (e, st) => Text('Error: $e'),
);
},
),
],
], ],
); );
}, },

View file

@ -24,6 +24,7 @@ class PlatformAsset {
final int durationInSeconds; final int durationInSeconds;
final int orientation; final int orientation;
final bool isFavorite; final bool isFavorite;
final int? size;
const PlatformAsset({ const PlatformAsset({
required this.id, required this.id,
@ -36,6 +37,7 @@ class PlatformAsset {
this.durationInSeconds = 0, this.durationInSeconds = 0,
this.orientation = 0, this.orientation = 0,
this.isFavorite = false, this.isFavorite = false,
this.size,
}); });
} }
@ -71,6 +73,19 @@ class SyncDelta {
}); });
} }
class TrashedAssetParams {
final String id;
// Follows AssetType enum from base_asset.model.dart
final int type;
final String? albumId;
const TrashedAssetParams({
required this.id,
required this.type,
this.albumId,
});
}
@HostApi() @HostApi()
abstract class NativeSyncApi { abstract class NativeSyncApi {
bool shouldFullSync(); bool shouldFullSync();
@ -96,4 +111,11 @@ abstract class NativeSyncApi {
@TaskQueue(type: TaskQueueType.serialBackgroundThread) @TaskQueue(type: TaskQueueType.serialBackgroundThread)
List<Uint8List?> hashPaths(List<String> paths); List<Uint8List?> hashPaths(List<String> paths);
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
List<PlatformAsset> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond});
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
List<Uint8List?> hashTrashedAssets(List<TrashedAssetParams> trashedAssets);
} }

View file

@ -21,6 +21,7 @@ void main() {
late MockLocalAssetRepository mockAssetRepo; late MockLocalAssetRepository mockAssetRepo;
late MockStorageRepository mockStorageRepo; late MockStorageRepository mockStorageRepo;
late MockNativeSyncApi mockNativeApi; late MockNativeSyncApi mockNativeApi;
late MockTrashSyncService mockTrashSyncService;
final sortBy = {SortLocalAlbumsBy.backupSelection, SortLocalAlbumsBy.isIosSharedAlbum}; final sortBy = {SortLocalAlbumsBy.backupSelection, SortLocalAlbumsBy.isIosSharedAlbum};
setUp(() { setUp(() {
@ -28,12 +29,14 @@ void main() {
mockAssetRepo = MockLocalAssetRepository(); mockAssetRepo = MockLocalAssetRepository();
mockStorageRepo = MockStorageRepository(); mockStorageRepo = MockStorageRepository();
mockNativeApi = MockNativeSyncApi(); mockNativeApi = MockNativeSyncApi();
mockTrashSyncService = MockTrashSyncService();
sut = HashService( sut = HashService(
localAlbumRepository: mockAlbumRepo, localAlbumRepository: mockAlbumRepo,
localAssetRepository: mockAssetRepo, localAssetRepository: mockAssetRepo,
storageRepository: mockStorageRepo, storageRepository: mockStorageRepo,
nativeSyncApi: mockNativeApi, nativeSyncApi: mockNativeApi,
trashSyncService: mockTrashSyncService,
); );
registerFallbackValue(LocalAlbumStub.recent); registerFallbackValue(LocalAlbumStub.recent);
@ -138,6 +141,7 @@ void main() {
localAssetRepository: mockAssetRepo, localAssetRepository: mockAssetRepo,
storageRepository: mockStorageRepo, storageRepository: mockStorageRepo,
nativeSyncApi: mockNativeApi, nativeSyncApi: mockNativeApi,
trashSyncService: mockTrashSyncService,
batchFileLimit: 1, batchFileLimit: 1,
); );
@ -173,6 +177,7 @@ void main() {
localAssetRepository: mockAssetRepo, localAssetRepository: mockAssetRepo,
storageRepository: mockStorageRepo, storageRepository: mockStorageRepo,
nativeSyncApi: mockNativeApi, nativeSyncApi: mockNativeApi,
trashSyncService: mockTrashSyncService,
batchSizeLimit: 80, batchSizeLimit: 80,
); );

View file

@ -7074,12 +7074,12 @@ class StoreEntityCompanion extends UpdateCompanion<StoreEntityData> {
} }
} }
class LocalTrashedAssetEntity extends Table class TrashedLocalAssetEntity extends Table
with TableInfo<LocalTrashedAssetEntity, LocalTrashedAssetEntityData> { with TableInfo<TrashedLocalAssetEntity, TrashedLocalAssetEntityData> {
@override @override
final GeneratedDatabase attachedDatabase; final GeneratedDatabase attachedDatabase;
final String? _alias; final String? _alias;
LocalTrashedAssetEntity(this.attachedDatabase, [this._alias]); TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<String> id = GeneratedColumn<String>( late final GeneratedColumn<String> id = GeneratedColumn<String>(
'id', 'id',
aliasedName, aliasedName,
@ -7087,15 +7087,33 @@ class LocalTrashedAssetEntity extends Table
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: true, requiredDuringInsert: true,
); );
late final GeneratedColumn<String> remoteId = GeneratedColumn<String>( late final GeneratedColumn<String> albumId = GeneratedColumn<String>(
'remote_id', 'album_id',
aliasedName, aliasedName,
false, false,
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: true, requiredDuringInsert: true,
defaultConstraints: GeneratedColumn.constraintIsAlways( );
'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', late final GeneratedColumn<String> checksum = GeneratedColumn<String>(
), 'checksum',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<int> type = GeneratedColumn<int>(
'type',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: true,
); );
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
'created_at', 'created_at',
@ -7105,40 +7123,84 @@ class LocalTrashedAssetEntity extends Table
requiredDuringInsert: false, requiredDuringInsert: false,
defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), defaultValue: const CustomExpression('CURRENT_TIMESTAMP'),
); );
late final GeneratedColumn<DateTime> updatedAt = GeneratedColumn<DateTime>(
'updated_at',
aliasedName,
false,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
defaultValue: const CustomExpression('CURRENT_TIMESTAMP'),
);
late final GeneratedColumn<int> size = GeneratedColumn<int>(
'size',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
@override @override
List<GeneratedColumn> get $columns => [id, remoteId, createdAt]; List<GeneratedColumn> get $columns => [
id,
albumId,
checksum,
name,
type,
createdAt,
updatedAt,
size,
];
@override @override
String get aliasedName => _alias ?? actualTableName; String get aliasedName => _alias ?? actualTableName;
@override @override
String get actualTableName => $name; String get actualTableName => $name;
static const String $name = 'local_trashed_asset_entity'; static const String $name = 'trashed_local_asset_entity';
@override @override
Set<GeneratedColumn> get $primaryKey => {id}; Set<GeneratedColumn> get $primaryKey => {id};
@override @override
LocalTrashedAssetEntityData map( TrashedLocalAssetEntityData map(
Map<String, dynamic> data, { Map<String, dynamic> data, {
String? tablePrefix, String? tablePrefix,
}) { }) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return LocalTrashedAssetEntityData( return TrashedLocalAssetEntityData(
id: attachedDatabase.typeMapping.read( id: attachedDatabase.typeMapping.read(
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}id'], data['${effectivePrefix}id'],
)!, )!,
remoteId: attachedDatabase.typeMapping.read( albumId: attachedDatabase.typeMapping.read(
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}remote_id'], data['${effectivePrefix}album_id'],
)!,
checksum: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}checksum'],
),
name: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}name'],
)!,
type: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}type'],
)!, )!,
createdAt: attachedDatabase.typeMapping.read( createdAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime, DriftSqlType.dateTime,
data['${effectivePrefix}created_at'], data['${effectivePrefix}created_at'],
)!, )!,
updatedAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}updated_at'],
)!,
size: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}size'],
),
); );
} }
@override @override
LocalTrashedAssetEntity createAlias(String alias) { TrashedLocalAssetEntity createAlias(String alias) {
return LocalTrashedAssetEntity(attachedDatabase, alias); return TrashedLocalAssetEntity(attachedDatabase, alias);
} }
@override @override
@ -7147,34 +7209,58 @@ class LocalTrashedAssetEntity extends Table
bool get isStrict => true; bool get isStrict => true;
} }
class LocalTrashedAssetEntityData extends DataClass class TrashedLocalAssetEntityData extends DataClass
implements Insertable<LocalTrashedAssetEntityData> { implements Insertable<TrashedLocalAssetEntityData> {
final String id; final String id;
final String remoteId; final String albumId;
final String? checksum;
final String name;
final int type;
final DateTime createdAt; final DateTime createdAt;
const LocalTrashedAssetEntityData({ final DateTime updatedAt;
final int? size;
const TrashedLocalAssetEntityData({
required this.id, required this.id,
required this.remoteId, required this.albumId,
this.checksum,
required this.name,
required this.type,
required this.createdAt, required this.createdAt,
required this.updatedAt,
this.size,
}); });
@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['id'] = Variable<String>(id);
map['remote_id'] = Variable<String>(remoteId); map['album_id'] = Variable<String>(albumId);
if (!nullToAbsent || checksum != null) {
map['checksum'] = Variable<String>(checksum);
}
map['name'] = Variable<String>(name);
map['type'] = Variable<int>(type);
map['created_at'] = Variable<DateTime>(createdAt); map['created_at'] = Variable<DateTime>(createdAt);
map['updated_at'] = Variable<DateTime>(updatedAt);
if (!nullToAbsent || size != null) {
map['size'] = Variable<int>(size);
}
return map; return map;
} }
factory LocalTrashedAssetEntityData.fromJson( factory TrashedLocalAssetEntityData.fromJson(
Map<String, dynamic> json, { Map<String, dynamic> json, {
ValueSerializer? serializer, ValueSerializer? serializer,
}) { }) {
serializer ??= driftRuntimeOptions.defaultSerializer; serializer ??= driftRuntimeOptions.defaultSerializer;
return LocalTrashedAssetEntityData( return TrashedLocalAssetEntityData(
id: serializer.fromJson<String>(json['id']), id: serializer.fromJson<String>(json['id']),
remoteId: serializer.fromJson<String>(json['remoteId']), albumId: serializer.fromJson<String>(json['albumId']),
checksum: serializer.fromJson<String?>(json['checksum']),
name: serializer.fromJson<String>(json['name']),
type: serializer.fromJson<int>(json['type']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']), createdAt: serializer.fromJson<DateTime>(json['createdAt']),
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
size: serializer.fromJson<int?>(json['size']),
); );
} }
@override @override
@ -7182,88 +7268,164 @@ class LocalTrashedAssetEntityData extends DataClass
serializer ??= driftRuntimeOptions.defaultSerializer; serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{ return <String, dynamic>{
'id': serializer.toJson<String>(id), 'id': serializer.toJson<String>(id),
'remoteId': serializer.toJson<String>(remoteId), 'albumId': serializer.toJson<String>(albumId),
'checksum': serializer.toJson<String?>(checksum),
'name': serializer.toJson<String>(name),
'type': serializer.toJson<int>(type),
'createdAt': serializer.toJson<DateTime>(createdAt), 'createdAt': serializer.toJson<DateTime>(createdAt),
'updatedAt': serializer.toJson<DateTime>(updatedAt),
'size': serializer.toJson<int?>(size),
}; };
} }
LocalTrashedAssetEntityData copyWith({ TrashedLocalAssetEntityData copyWith({
String? id, String? id,
String? remoteId, String? albumId,
Value<String?> checksum = const Value.absent(),
String? name,
int? type,
DateTime? createdAt, DateTime? createdAt,
}) => LocalTrashedAssetEntityData( DateTime? updatedAt,
Value<int?> size = const Value.absent(),
}) => TrashedLocalAssetEntityData(
id: id ?? this.id, id: id ?? this.id,
remoteId: remoteId ?? this.remoteId, albumId: albumId ?? this.albumId,
checksum: checksum.present ? checksum.value : this.checksum,
name: name ?? this.name,
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
size: size.present ? size.value : this.size,
); );
LocalTrashedAssetEntityData copyWithCompanion( TrashedLocalAssetEntityData copyWithCompanion(
LocalTrashedAssetEntityCompanion data, TrashedLocalAssetEntityCompanion data,
) { ) {
return LocalTrashedAssetEntityData( return TrashedLocalAssetEntityData(
id: data.id.present ? data.id.value : this.id, id: data.id.present ? data.id.value : this.id,
remoteId: data.remoteId.present ? data.remoteId.value : this.remoteId, 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,
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,
size: data.size.present ? data.size.value : this.size,
); );
} }
@override @override
String toString() { String toString() {
return (StringBuffer('LocalTrashedAssetEntityData(') return (StringBuffer('TrashedLocalAssetEntityData(')
..write('id: $id, ') ..write('id: $id, ')
..write('remoteId: $remoteId, ') ..write('albumId: $albumId, ')
..write('createdAt: $createdAt') ..write('checksum: $checksum, ')
..write('name: $name, ')
..write('type: $type, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('size: $size')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@override @override
int get hashCode => Object.hash(id, remoteId, createdAt); int get hashCode => Object.hash(
id,
albumId,
checksum,
name,
type,
createdAt,
updatedAt,
size,
);
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
(other is LocalTrashedAssetEntityData && (other is TrashedLocalAssetEntityData &&
other.id == this.id && other.id == this.id &&
other.remoteId == this.remoteId && other.albumId == this.albumId &&
other.createdAt == this.createdAt); other.checksum == this.checksum &&
other.name == this.name &&
other.type == this.type &&
other.createdAt == this.createdAt &&
other.updatedAt == this.updatedAt &&
other.size == this.size);
} }
class LocalTrashedAssetEntityCompanion class TrashedLocalAssetEntityCompanion
extends UpdateCompanion<LocalTrashedAssetEntityData> { extends UpdateCompanion<TrashedLocalAssetEntityData> {
final Value<String> id; final Value<String> id;
final Value<String> remoteId; final Value<String> albumId;
final Value<String?> checksum;
final Value<String> name;
final Value<int> type;
final Value<DateTime> createdAt; final Value<DateTime> createdAt;
const LocalTrashedAssetEntityCompanion({ final Value<DateTime> updatedAt;
final Value<int?> size;
const TrashedLocalAssetEntityCompanion({
this.id = const Value.absent(), this.id = const Value.absent(),
this.remoteId = const Value.absent(), this.albumId = const Value.absent(),
this.checksum = const Value.absent(),
this.name = const Value.absent(),
this.type = const Value.absent(),
this.createdAt = const Value.absent(), this.createdAt = const Value.absent(),
this.updatedAt = const Value.absent(),
this.size = const Value.absent(),
}); });
LocalTrashedAssetEntityCompanion.insert({ TrashedLocalAssetEntityCompanion.insert({
required String id, required String id,
required String remoteId, required String albumId,
this.checksum = const Value.absent(),
required String name,
required int type,
this.createdAt = const Value.absent(), this.createdAt = const Value.absent(),
this.updatedAt = const Value.absent(),
this.size = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
remoteId = Value(remoteId); albumId = Value(albumId),
static Insertable<LocalTrashedAssetEntityData> custom({ name = Value(name),
type = Value(type);
static Insertable<TrashedLocalAssetEntityData> custom({
Expression<String>? id, Expression<String>? id,
Expression<String>? remoteId, Expression<String>? albumId,
Expression<String>? checksum,
Expression<String>? name,
Expression<int>? type,
Expression<DateTime>? createdAt, Expression<DateTime>? createdAt,
Expression<DateTime>? updatedAt,
Expression<int>? size,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
if (id != null) 'id': id, if (id != null) 'id': id,
if (remoteId != null) 'remote_id': remoteId, if (albumId != null) 'album_id': albumId,
if (checksum != null) 'checksum': checksum,
if (name != null) 'name': name,
if (type != null) 'type': type,
if (createdAt != null) 'created_at': createdAt, if (createdAt != null) 'created_at': createdAt,
if (updatedAt != null) 'updated_at': updatedAt,
if (size != null) 'size': size,
}); });
} }
LocalTrashedAssetEntityCompanion copyWith({ TrashedLocalAssetEntityCompanion copyWith({
Value<String>? id, Value<String>? id,
Value<String>? remoteId, Value<String>? albumId,
Value<String?>? checksum,
Value<String>? name,
Value<int>? type,
Value<DateTime>? createdAt, Value<DateTime>? createdAt,
Value<DateTime>? updatedAt,
Value<int?>? size,
}) { }) {
return LocalTrashedAssetEntityCompanion( return TrashedLocalAssetEntityCompanion(
id: id ?? this.id, id: id ?? this.id,
remoteId: remoteId ?? this.remoteId, albumId: albumId ?? this.albumId,
checksum: checksum ?? this.checksum,
name: name ?? this.name,
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
size: size ?? this.size,
); );
} }
@ -7273,21 +7435,41 @@ class LocalTrashedAssetEntityCompanion
if (id.present) { if (id.present) {
map['id'] = Variable<String>(id.value); map['id'] = Variable<String>(id.value);
} }
if (remoteId.present) { if (albumId.present) {
map['remote_id'] = Variable<String>(remoteId.value); map['album_id'] = Variable<String>(albumId.value);
}
if (checksum.present) {
map['checksum'] = Variable<String>(checksum.value);
}
if (name.present) {
map['name'] = Variable<String>(name.value);
}
if (type.present) {
map['type'] = Variable<int>(type.value);
} }
if (createdAt.present) { if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value); map['created_at'] = Variable<DateTime>(createdAt.value);
} }
if (updatedAt.present) {
map['updated_at'] = Variable<DateTime>(updatedAt.value);
}
if (size.present) {
map['size'] = Variable<int>(size.value);
}
return map; return map;
} }
@override @override
String toString() { String toString() {
return (StringBuffer('LocalTrashedAssetEntityCompanion(') return (StringBuffer('TrashedLocalAssetEntityCompanion(')
..write('id: $id, ') ..write('id: $id, ')
..write('remoteId: $remoteId, ') ..write('albumId: $albumId, ')
..write('createdAt: $createdAt') ..write('checksum: $checksum, ')
..write('name: $name, ')
..write('type: $type, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('size: $size')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@ -7336,15 +7518,15 @@ class DatabaseAtV11 extends GeneratedDatabase {
late final PersonEntity personEntity = PersonEntity(this); late final PersonEntity personEntity = PersonEntity(this);
late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this);
late final StoreEntity storeEntity = StoreEntity(this); late final StoreEntity storeEntity = StoreEntity(this);
late final LocalTrashedAssetEntity localTrashedAssetEntity = late final TrashedLocalAssetEntity trashedLocalAssetEntity =
LocalTrashedAssetEntity(this); TrashedLocalAssetEntity(this);
late final Index idxLatLng = Index( late final Index idxLatLng = Index(
'idx_lat_lng', 'idx_lat_lng',
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
); );
late final Index idxLocalTrashedAssetRemoteId = Index( late final Index idxTrashedLocalAssetChecksum = Index(
'idx_local_trashed_asset_remote_id', 'idx_trashed_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_local_trashed_asset_remote_id ON local_trashed_asset_entity (remote_id)', 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
); );
@override @override
Iterable<TableInfo<Table, Object?>> get allTables => Iterable<TableInfo<Table, Object?>> get allTables =>
@ -7374,9 +7556,9 @@ class DatabaseAtV11 extends GeneratedDatabase {
personEntity, personEntity,
assetFaceEntity, assetFaceEntity,
storeEntity, storeEntity,
localTrashedAssetEntity, trashedLocalAssetEntity,
idxLatLng, idxLatLng,
idxLocalTrashedAssetRemoteId, idxTrashedLocalAssetChecksum,
]; ];
@override @override
int get schemaVersion => 11; int get schemaVersion => 11;