import 'dart:io'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/android_device_asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/device_asset.entity.dart'; import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; import 'package:isar/isar.dart'; final assetRepositoryProvider = Provider((ref) => AssetRepository(ref.watch(dbProvider))); class AssetRepository extends DatabaseRepository implements IAssetRepository { AssetRepository(super.db); @override Future> getByAlbum( Album album, { Iterable notOwnedBy = const [], int? ownerId, AssetState? state, AssetSort? sortBy, }) { var query = album.assets.filter(); if (notOwnedBy.length == 1) { query = query.not().ownerIdEqualTo(notOwnedBy.first); } else if (notOwnedBy.isNotEmpty) { query = query.not().anyOf(notOwnedBy, (q, int id) => q.ownerIdEqualTo(id)); } if (ownerId != null) { query = query.ownerIdEqualTo(ownerId); } if (state != null) { query = switch (state) { AssetState.local => query.remoteIdIsNull(), AssetState.remote => query.localIdIsNull(), AssetState.merged => query.localIdIsNotNull().remoteIdIsNotNull(), }; } final QueryBuilder sortedQuery = switch (sortBy) { null => query.noOp(), AssetSort.checksum => query.sortByChecksum(), AssetSort.ownerIdChecksum => query.sortByOwnerId().thenByChecksum(), }; return sortedQuery.findAll(); } @override Future deleteById(List ids) => txn(() async { await db.assets.deleteAll(ids); await db.exifInfos.deleteAll(ids); }); @override Future getByRemoteId(String id) => db.assets.getByRemoteId(id); @override Future> getAllByRemoteId( Iterable ids, { AssetState? state, }) => _getAllByRemoteIdImpl(ids, state).findAll(); QueryBuilder _getAllByRemoteIdImpl( Iterable ids, AssetState? state, ) { final query = db.assets.remote(ids).filter(); return switch (state) { null => query.noOp(), AssetState.local => query.remoteIdIsNull(), AssetState.remote => query.localIdIsNull(), AssetState.merged => query.localIdIsNotEmpty().remoteIdIsNotNull(), }; } @override Future> getAll({ required int ownerId, AssetState? state, AssetSort? sortBy, int? limit, }) { final baseQuery = db.assets.where(); final QueryBuilder filteredQuery = switch (state) { null => baseQuery.ownerIdEqualToAnyChecksum(ownerId).noOp(), AssetState.local => baseQuery .remoteIdIsNull() .filter() .localIdIsNotNull() .ownerIdEqualTo(ownerId), AssetState.remote => baseQuery .localIdIsNull() .filter() .remoteIdIsNotNull() .ownerIdEqualTo(ownerId), AssetState.merged => baseQuery .ownerIdEqualToAnyChecksum(ownerId) .filter() .remoteIdIsNotNull() .localIdIsNotNull(), }; final QueryBuilder query = switch (sortBy) { null => filteredQuery.noOp(), AssetSort.checksum => filteredQuery.sortByChecksum(), AssetSort.ownerIdChecksum => filteredQuery.sortByOwnerId().thenByChecksum(), }; return limit == null ? query.findAll() : query.limit(limit).findAll(); } @override Future> updateAll(List assets) async { await txn(() => db.assets.putAll(assets)); return assets; } @override Future> getMatches({ required List assets, required int ownerId, AssetState? state, int limit = 100, }) { final baseQuery = db.assets.where(); final QueryBuilder query = switch (state) { null => baseQuery.noOp(), AssetState.local => baseQuery.remoteIdIsNull().filter().localIdIsNotNull(), AssetState.remote => baseQuery.localIdIsNull().filter().remoteIdIsNotNull(), AssetState.merged => baseQuery.localIdIsNotNull().filter().remoteIdIsNotNull(), }; return _getMatchesImpl(query, ownerId, assets, limit); } @override Future> getDeviceAssetsById(List ids) => Platform.isAndroid ? db.androidDeviceAssets.getAll(ids.cast()) : db.iOSDeviceAssets.getAllById(ids.cast()); @override Future upsertDeviceAssets(List deviceAssets) => txn( () => Platform.isAndroid ? db.androidDeviceAssets.putAll(deviceAssets.cast()) : db.iOSDeviceAssets.putAll(deviceAssets.cast()), ); @override Future update(Asset asset) async { await txn(() => asset.put(db)); return asset; } @override Future upsertDuplicatedAssets(Iterable duplicatedAssets) => txn( () => db.duplicatedAssets .putAll(duplicatedAssets.map(DuplicatedAsset.new).toList()), ); @override Future> getAllDuplicatedAssetIds() => db.duplicatedAssets.where().idProperty().findAll(); @override Future getByOwnerIdChecksum(int ownerId, String checksum) => db.assets.getByOwnerIdChecksum(ownerId, checksum); @override Future> getAllByOwnerIdChecksum( List ids, List checksums, ) => db.assets.getAllByOwnerIdChecksum(ids, checksums); @override Future> getAllLocal() => db.assets.where().localIdIsNotNull().findAll(); @override Future deleteAllByRemoteId(List ids, {AssetState? state}) => txn(() => _getAllByRemoteIdImpl(ids, state).deleteAll()); } Future> _getMatchesImpl( QueryBuilder query, int ownerId, List assets, int limit, ) => query .ownerIdEqualTo(ownerId) .anyOf( assets, (q, Asset a) => q .fileNameEqualTo(a.fileName) .and() .durationInSecondsEqualTo(a.durationInSeconds) .and() .fileCreatedAtBetween( a.fileCreatedAt.subtract(const Duration(hours: 12)), a.fileCreatedAt.add(const Duration(hours: 12)), ) .and() .not() .checksumEqualTo(a.checksum), ) .sortByFileName() .thenByFileCreatedAt() .thenByFileModifiedAt() .limit(limit) .findAll();