more refactors and logs page handling
This commit is contained in:
@@ -4,14 +4,14 @@ import 'package:immich_mobile/domain/models/album.model.dart';
|
||||
|
||||
abstract interface class IAlbumRepository {
|
||||
/// Inserts a new album into the DB or updates if existing and returns the updated data
|
||||
FutureOr<Album?> upsert(Album album);
|
||||
Future<Album?> upsert(Album album);
|
||||
|
||||
/// Fetch all albums
|
||||
FutureOr<List<Album>> getAll({bool localOnly, bool remoteOnly});
|
||||
Future<List<Album>> getAll({bool localOnly, bool remoteOnly});
|
||||
|
||||
/// Removes album with the given [id]
|
||||
FutureOr<void> deleteId(int id);
|
||||
Future<void> deleteId(int id);
|
||||
|
||||
/// Removes all albums
|
||||
FutureOr<void> deleteAll();
|
||||
Future<void> deleteAll();
|
||||
}
|
||||
|
||||
@@ -4,17 +4,17 @@ import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||
|
||||
abstract interface class IAlbumToAssetRepository {
|
||||
/// Link a list of assetIds to the given albumId
|
||||
FutureOr<bool> addAssetIds(int albumId, Iterable<int> assetIds);
|
||||
Future<bool> addAssetIds(int albumId, Iterable<int> assetIds);
|
||||
|
||||
/// Returns assets that are only part of the given album and nothing else
|
||||
FutureOr<List<int>> getAssetIdsOnlyInAlbum(int albumId);
|
||||
Future<List<int>> getAssetIdsOnlyInAlbum(int albumId);
|
||||
|
||||
/// Returns the assets for the given [albumId]
|
||||
FutureOr<List<Asset>> getAssetsForAlbum(int albumId);
|
||||
Future<List<Asset>> getAssetsForAlbum(int albumId);
|
||||
|
||||
/// Removes album with the given [albumId]
|
||||
FutureOr<void> deleteAlbumId(int albumId);
|
||||
Future<void> deleteAlbumId(int albumId);
|
||||
|
||||
/// Removes all album to asset mappings
|
||||
FutureOr<void> deleteAll();
|
||||
Future<void> deleteAll();
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import 'package:immich_mobile/domain/models/album_etag.model.dart';
|
||||
|
||||
abstract interface class IAlbumETagRepository {
|
||||
/// Inserts or updates the album etag for the given [albumId]
|
||||
FutureOr<bool> upsert(AlbumETag albumETag);
|
||||
Future<bool> upsert(AlbumETag albumETag);
|
||||
|
||||
/// Fetches the album etag for the given [albumId]
|
||||
FutureOr<AlbumETag?> get(int albumId);
|
||||
Future<AlbumETag?> get(int albumId);
|
||||
|
||||
/// Removes all album eTags
|
||||
FutureOr<void> deleteAll();
|
||||
Future<void> deleteAll();
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||
|
||||
abstract interface class IAssetRepository {
|
||||
/// Batch upsert asset
|
||||
FutureOr<bool> upsertAll(Iterable<Asset> assets);
|
||||
Future<bool> upsertAll(Iterable<Asset> assets);
|
||||
|
||||
/// Removes assets with the [localIds]
|
||||
FutureOr<List<Asset>> getForLocalIds(Iterable<String> localIds);
|
||||
Future<List<Asset>> getForLocalIds(Iterable<String> localIds);
|
||||
|
||||
/// Removes assets with the [remoteIds]
|
||||
FutureOr<List<Asset>> getForRemoteIds(Iterable<String> remoteIds);
|
||||
Future<List<Asset>> getForRemoteIds(Iterable<String> remoteIds);
|
||||
|
||||
/// Get assets with the [hashes]
|
||||
FutureOr<List<Asset>> getForHashes(Iterable<String> hashes);
|
||||
Future<List<Asset>> getForHashes(Iterable<String> hashes);
|
||||
|
||||
/// Fetch assets from the [offset] with the [limit]
|
||||
FutureOr<List<Asset>> getAll({int? offset, int? limit});
|
||||
Future<List<Asset>> getAll({int? offset, int? limit});
|
||||
|
||||
/// Removes assets with the given [ids]
|
||||
FutureOr<void> deleteIds(Iterable<int> ids);
|
||||
Future<void> deleteIds(Iterable<int> ids);
|
||||
|
||||
/// Removes all assets
|
||||
FutureOr<bool> deleteAll();
|
||||
Future<bool> deleteAll();
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||
|
||||
abstract interface class IDeviceAlbumRepository {
|
||||
/// Fetches all [Album] from device
|
||||
FutureOr<List<Album>> getAll();
|
||||
Future<List<Album>> getAll();
|
||||
|
||||
/// Returns the number of asset in the album
|
||||
FutureOr<int> getAssetCount(String albumId);
|
||||
Future<int> getAssetCount(String albumId);
|
||||
|
||||
/// Fetches assets belong to the albumId
|
||||
FutureOr<List<Asset>> getAssetsForAlbum(
|
||||
Future<List<Asset>> getAssetsForAlbum(
|
||||
String albumId, {
|
||||
int start = 0,
|
||||
int end = 0x7fffffffffffffff,
|
||||
|
||||
@@ -8,10 +8,10 @@ import 'package:immich_mobile/utils/constants/globals.dart';
|
||||
|
||||
abstract interface class IDeviceAssetRepository<T> {
|
||||
/// Fetches the [File] for the given [assetId]
|
||||
FutureOr<File?> getOriginalFile(String assetId);
|
||||
Future<File?> getOriginalFile(String assetId);
|
||||
|
||||
/// Fetches the thumbnail for the given [assetId]
|
||||
FutureOr<Uint8List?> getThumbnail(
|
||||
Future<Uint8List?> getThumbnail(
|
||||
String assetId, {
|
||||
int width = kGridThumbnailSize,
|
||||
int height = kGridThumbnailSize,
|
||||
|
||||
@@ -4,11 +4,11 @@ import 'package:immich_mobile/domain/models/device_asset_hash.model.dart';
|
||||
|
||||
abstract interface class IDeviceAssetToHashRepository {
|
||||
/// Add a new device asset to hash entry
|
||||
FutureOr<bool> upsertAll(Iterable<DeviceAssetToHash> assetHash);
|
||||
Future<bool> upsertAll(Iterable<DeviceAssetToHash> assetHash);
|
||||
|
||||
// Gets the asset with the local ID from the device
|
||||
FutureOr<List<DeviceAssetToHash>> getForIds(Iterable<String> localIds);
|
||||
Future<List<DeviceAssetToHash>> getForIds(Iterable<String> localIds);
|
||||
|
||||
/// Removes assets with the given [ids]
|
||||
FutureOr<void> deleteIds(Iterable<int> ids);
|
||||
Future<void> deleteIds(Iterable<int> ids);
|
||||
}
|
||||
|
||||
@@ -4,17 +4,17 @@ import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
|
||||
abstract interface class ILogRepository {
|
||||
/// Inserts a new log into the DB
|
||||
FutureOr<bool> create(LogMessage log);
|
||||
Future<bool> create(LogMessage log);
|
||||
|
||||
/// Bulk insert logs into DB
|
||||
FutureOr<bool> createAll(Iterable<LogMessage> log);
|
||||
Future<bool> createAll(Iterable<LogMessage> log);
|
||||
|
||||
/// Fetches all logs
|
||||
FutureOr<List<LogMessage>> getAll();
|
||||
Future<List<LogMessage>> getAll();
|
||||
|
||||
/// Clears all logs
|
||||
FutureOr<bool> deleteAll();
|
||||
Future<bool> deleteAll();
|
||||
|
||||
/// Truncates the logs to the most recent [limit]. Defaults to recent 250 logs
|
||||
FutureOr<void> truncate({int limit = 250});
|
||||
Future<void> truncate({int limit = 250});
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@ abstract class IStoreConverter<T, U> {
|
||||
}
|
||||
|
||||
abstract interface class IStoreRepository {
|
||||
FutureOr<bool> upsert<T, U>(StoreKey<T, U> key, T value);
|
||||
Future<bool> upsert<T, U>(StoreKey<T, U> key, T value);
|
||||
|
||||
FutureOr<T> get<T, U>(StoreKey<T, U> key);
|
||||
Future<T> get<T, U>(StoreKey<T, U> key);
|
||||
|
||||
FutureOr<T?> tryGet<T, U>(StoreKey<T, U> key);
|
||||
Future<T?> tryGet<T, U>(StoreKey<T, U> key);
|
||||
|
||||
Stream<T?> watch<T, U>(StoreKey<T, U> key);
|
||||
|
||||
FutureOr<void> delete(StoreKey key);
|
||||
Future<void> delete(StoreKey key);
|
||||
|
||||
FutureOr<void> deleteAll();
|
||||
Future<void> deleteAll();
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
|
||||
abstract interface class IUserRepository {
|
||||
/// Insert user
|
||||
FutureOr<bool> upsert(User user);
|
||||
Future<bool> upsert(User user);
|
||||
|
||||
/// Fetches user
|
||||
FutureOr<User?> getForId(String userId);
|
||||
Future<User?> getForId(String userId);
|
||||
|
||||
/// Removes all users
|
||||
FutureOr<void> deleteAll();
|
||||
Future<void> deleteAll();
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ class Asset {
|
||||
createdTime: createdTime ?? this.createdTime,
|
||||
modifiedTime: modifiedTime ?? this.modifiedTime,
|
||||
duration: duration ?? this.duration,
|
||||
localId: localId != null ? localId() : this.localId,
|
||||
remoteId: remoteId != null ? remoteId() : this.remoteId,
|
||||
localId: localId == null ? this.localId : localId(),
|
||||
remoteId: remoteId == null ? this.remoteId : remoteId(),
|
||||
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
||||
);
|
||||
}
|
||||
@@ -89,20 +89,20 @@ class Asset {
|
||||
if (newAsset.modifiedTime.isAfter(existingAsset.modifiedTime)) {
|
||||
return newAsset.copyWith(
|
||||
id: newAsset.id ?? existingAsset.id,
|
||||
height: newAsset.height ?? existingAsset.height,
|
||||
width: newAsset.width ?? existingAsset.width,
|
||||
createdTime: oldestCreationTime,
|
||||
localId: () => existingAsset.localId ?? newAsset.localId,
|
||||
remoteId: () => existingAsset.remoteId ?? newAsset.remoteId,
|
||||
width: newAsset.width ?? existingAsset.width,
|
||||
height: newAsset.height ?? existingAsset.height,
|
||||
createdTime: oldestCreationTime,
|
||||
);
|
||||
}
|
||||
|
||||
return existingAsset.copyWith(
|
||||
height: existingAsset.height ?? newAsset.height,
|
||||
width: existingAsset.width ?? newAsset.width,
|
||||
createdTime: oldestCreationTime,
|
||||
localId: () => existingAsset.localId ?? newAsset.localId,
|
||||
remoteId: () => existingAsset.remoteId ?? newAsset.remoteId,
|
||||
width: existingAsset.width ?? newAsset.width,
|
||||
height: existingAsset.height ?? newAsset.height,
|
||||
createdTime: oldestCreationTime,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import 'package:immich_mobile/domain/models/render_list_element.model.dart';
|
||||
class RenderList {
|
||||
final List<RenderListElement> elements;
|
||||
late final int totalCount;
|
||||
final DateTime modifiedTime;
|
||||
|
||||
RenderList({required this.elements}) {
|
||||
RenderList({required this.elements, required this.modifiedTime}) {
|
||||
final lastAssetElement =
|
||||
elements.whereType<RenderListAssetElement>().lastOrNull;
|
||||
if (lastAssetElement == null) {
|
||||
@@ -16,6 +17,21 @@ class RenderList {
|
||||
}
|
||||
|
||||
factory RenderList.empty() {
|
||||
return RenderList(elements: []);
|
||||
return RenderList(elements: [], modifiedTime: DateTime.now());
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'RenderList(totalCount: $totalCount, modifiedTime: $modifiedTime)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant RenderList other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.totalCount == totalCount &&
|
||||
other.modifiedTime.isAtSameMomentAs(modifiedTime);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => totalCount.hashCode ^ modifiedTime.hashCode;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class AlbumRepository with LogMixin implements IAlbumRepository {
|
||||
const AlbumRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||
|
||||
@override
|
||||
FutureOr<Album?> upsert(Album album) async {
|
||||
Future<Album?> upsert(Album album) async {
|
||||
try {
|
||||
final albumData = _toEntity(album);
|
||||
final data = await _db.album.insertReturningOrNull(
|
||||
@@ -30,7 +30,7 @@ class AlbumRepository with LogMixin implements IAlbumRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<List<Album>> getAll({
|
||||
Future<List<Album>> getAll({
|
||||
bool localOnly = false,
|
||||
bool remoteOnly = false,
|
||||
}) async {
|
||||
@@ -49,12 +49,12 @@ class AlbumRepository with LogMixin implements IAlbumRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> deleteId(int id) async {
|
||||
Future<void> deleteId(int id) async {
|
||||
await _db.album.deleteWhere((row) => row.id.equals(id));
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> deleteAll() async {
|
||||
Future<void> deleteAll() async {
|
||||
await _db.album.deleteAll();
|
||||
}
|
||||
}
|
||||
@@ -62,11 +62,11 @@ class AlbumRepository with LogMixin implements IAlbumRepository {
|
||||
AlbumCompanion _toEntity(Album album) {
|
||||
return AlbumCompanion.insert(
|
||||
id: Value.absentIfNull(album.id),
|
||||
localId: Value(album.localId),
|
||||
remoteId: Value(album.remoteId),
|
||||
name: album.name,
|
||||
modifiedTime: Value(album.modifiedTime),
|
||||
thumbnailAssetId: Value(album.thumbnailAssetId),
|
||||
localId: Value(album.localId),
|
||||
remoteId: Value(album.remoteId),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class AlbumToAssetRepository with LogMixin implements IAlbumToAssetRepository {
|
||||
: _db = db;
|
||||
|
||||
@override
|
||||
FutureOr<bool> addAssetIds(int albumId, Iterable<int> assetIds) async {
|
||||
Future<bool> addAssetIds(int albumId, Iterable<int> assetIds) async {
|
||||
try {
|
||||
await _db.albumToAsset.insertAll(
|
||||
assetIds.map(
|
||||
@@ -33,14 +33,14 @@ class AlbumToAssetRepository with LogMixin implements IAlbumToAssetRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<List<int>> getAssetIdsOnlyInAlbum(int albumId) async {
|
||||
Future<List<int>> getAssetIdsOnlyInAlbum(int albumId) async {
|
||||
final assetId = _db.asset.id;
|
||||
final query = _db.asset.selectOnly()
|
||||
..addColumns([assetId])
|
||||
..join([
|
||||
innerJoin(
|
||||
_db.albumToAsset,
|
||||
_db.albumToAsset.assetId.equalsExp(_db.asset.id) &
|
||||
_db.albumToAsset.assetId.equalsExp(assetId) &
|
||||
_db.asset.remoteId.isNull(),
|
||||
useColumns: false,
|
||||
),
|
||||
@@ -55,7 +55,7 @@ class AlbumToAssetRepository with LogMixin implements IAlbumToAssetRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<List<Asset>> getAssetsForAlbum(int albumId) async {
|
||||
Future<List<Asset>> getAssetsForAlbum(int albumId) async {
|
||||
final query = _db.asset.select().join([
|
||||
innerJoin(
|
||||
_db.albumToAsset,
|
||||
@@ -72,12 +72,12 @@ class AlbumToAssetRepository with LogMixin implements IAlbumToAssetRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> deleteAlbumId(int albumId) async {
|
||||
Future<void> deleteAlbumId(int albumId) async {
|
||||
await _db.albumToAsset.deleteWhere((row) => row.albumId.equals(albumId));
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> deleteAll() async {
|
||||
Future<void> deleteAll() async {
|
||||
await _db.albumToAsset.deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class AlbumETagRepository with LogMixin implements IAlbumETagRepository {
|
||||
const AlbumETagRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||
|
||||
@override
|
||||
FutureOr<bool> upsert(AlbumETag albumETag) async {
|
||||
Future<bool> upsert(AlbumETag albumETag) async {
|
||||
try {
|
||||
final entity = _toEntity(albumETag);
|
||||
await _db.albumETag.insertOne(
|
||||
@@ -28,14 +28,14 @@ class AlbumETagRepository with LogMixin implements IAlbumETagRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<AlbumETag?> get(int albumId) async {
|
||||
Future<AlbumETag?> get(int albumId) async {
|
||||
final query = _db.albumETag.select()
|
||||
..where((r) => r.albumId.equals(albumId));
|
||||
return await query.map(_toModel).getSingleOrNull();
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> deleteAll() async {
|
||||
Future<void> deleteAll() async {
|
||||
await _db.albumETag.deleteAll();
|
||||
}
|
||||
}
|
||||
@@ -43,17 +43,17 @@ class AlbumETagRepository with LogMixin implements IAlbumETagRepository {
|
||||
AlbumETagCompanion _toEntity(AlbumETag albumETag) {
|
||||
return AlbumETagCompanion.insert(
|
||||
id: Value.absentIfNull(albumETag.id),
|
||||
modifiedTime: Value(albumETag.modifiedTime),
|
||||
albumId: albumETag.albumId,
|
||||
modifiedTime: Value(albumETag.modifiedTime),
|
||||
assetCount: Value(albumETag.assetCount),
|
||||
);
|
||||
}
|
||||
|
||||
AlbumETag _toModel(AlbumETagData albumETag) {
|
||||
return AlbumETag(
|
||||
id: albumETag.id,
|
||||
albumId: albumETag.albumId,
|
||||
assetCount: albumETag.assetCount,
|
||||
modifiedTime: albumETag.modifiedTime,
|
||||
id: albumETag.id,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,16 +32,16 @@ class SyncApiRepository with LogMixin implements ISyncApiRepository {
|
||||
}
|
||||
|
||||
Asset _fromAssetResponseDto(AssetResponseDto dto) => Asset(
|
||||
remoteId: dto.id,
|
||||
createdTime: dto.fileCreatedAt,
|
||||
duration: dto.duration.tryParseInt() ?? 0,
|
||||
name: dto.originalFileName,
|
||||
hash: dto.checksum,
|
||||
height: dto.exifInfo?.exifImageHeight?.toInt(),
|
||||
width: dto.exifInfo?.exifImageWidth?.toInt(),
|
||||
hash: dto.checksum,
|
||||
name: dto.originalFileName,
|
||||
livePhotoVideoId: dto.livePhotoVideoId,
|
||||
modifiedTime: dto.fileModifiedAt,
|
||||
type: _toAssetType(dto.type),
|
||||
createdTime: dto.fileCreatedAt,
|
||||
modifiedTime: dto.fileModifiedAt,
|
||||
duration: dto.duration.tryParseInt() ?? 0,
|
||||
remoteId: dto.id,
|
||||
livePhotoVideoId: dto.livePhotoVideoId,
|
||||
);
|
||||
|
||||
AssetType _toAssetType(AssetTypeEnum type) => switch (type) {
|
||||
|
||||
@@ -16,16 +16,16 @@ class AssetRepository with LogMixin implements IAssetRepository {
|
||||
@override
|
||||
Future<bool> upsertAll(Iterable<Asset> assets) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
final rows = assets.map(_toEntity);
|
||||
for (final row in rows) {
|
||||
batch.insert(
|
||||
_db.asset,
|
||||
row,
|
||||
onConflict: DoUpdate((_) => row, target: [_db.asset.hash]),
|
||||
);
|
||||
}
|
||||
});
|
||||
await _db.txn(() async => await _db.batch((batch) {
|
||||
final rows = assets.map(_toEntity);
|
||||
for (final row in rows) {
|
||||
batch.insert(
|
||||
_db.asset,
|
||||
row,
|
||||
onConflict: DoUpdate((_) => row, target: [_db.asset.hash]),
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
@@ -85,7 +85,7 @@ class AssetRepository with LogMixin implements IAssetRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> deleteIds(Iterable<int> ids) async {
|
||||
Future<void> deleteIds(Iterable<int> ids) async {
|
||||
await _db.asset.deleteWhere((row) => row.id.isIn(ids));
|
||||
}
|
||||
}
|
||||
@@ -93,16 +93,16 @@ class AssetRepository with LogMixin implements IAssetRepository {
|
||||
AssetCompanion _toEntity(Asset asset) {
|
||||
return AssetCompanion.insert(
|
||||
id: Value.absentIfNull(asset.id),
|
||||
localId: Value(asset.localId),
|
||||
remoteId: Value(asset.remoteId),
|
||||
name: asset.name,
|
||||
hash: asset.hash,
|
||||
height: Value(asset.height),
|
||||
width: Value(asset.width),
|
||||
type: asset.type,
|
||||
createdTime: asset.createdTime,
|
||||
duration: Value(asset.duration),
|
||||
modifiedTime: Value(asset.modifiedTime),
|
||||
duration: Value(asset.duration),
|
||||
localId: Value(asset.localId),
|
||||
remoteId: Value(asset.remoteId),
|
||||
livePhotoVideoId: Value(asset.livePhotoVideoId),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ class DriftDatabaseRepository extends $DriftDatabaseRepository
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
onCreate: (m) => m.createAll(),
|
||||
// ignore: no-empty-block
|
||||
onUpgrade: (m, from, to) async {},
|
||||
beforeOpen: (details) async {
|
||||
if (kDebugMode) {
|
||||
await validateDatabaseSchema();
|
||||
@@ -52,8 +54,6 @@ class DriftDatabaseRepository extends $DriftDatabaseRepository
|
||||
await customStatement('PRAGMA journal_mode = WAL');
|
||||
await customStatement('PRAGMA foreign_keys = ON');
|
||||
},
|
||||
// ignore: no-empty-block
|
||||
onUpgrade: (m, from, to) async {},
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
@@ -52,17 +52,17 @@ class DeviceAlbumRepository with LogMixin implements IDeviceAlbumRepository {
|
||||
return await AssetPathEntity.fromId(
|
||||
albumId,
|
||||
filterOption: FilterOptionGroup(
|
||||
containsPathModified: true,
|
||||
orders: orderByModificationDate
|
||||
? [const OrderOption(type: OrderOptionType.updateDate)]
|
||||
: [],
|
||||
imageOption: const FilterOption(needTitle: true),
|
||||
videoOption: const FilterOption(needTitle: true),
|
||||
containsPathModified: true,
|
||||
updateTimeCond: DateTimeCond(
|
||||
min: modifiedFrom ?? DateTime.utc(-271820),
|
||||
max: modifiedUntil ?? DateTime.utc(275760),
|
||||
ignore: modifiedFrom != null || modifiedUntil != null,
|
||||
),
|
||||
orders: orderByModificationDate
|
||||
? [const OrderOption(type: OrderOptionType.updateDate)]
|
||||
: [],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,25 +16,23 @@ class DeviceAssetRepository
|
||||
|
||||
@override
|
||||
Future<Asset> toAsset(ph.AssetEntity entity) async {
|
||||
var asset = Asset(
|
||||
hash: '',
|
||||
return Asset(
|
||||
name: await entity.titleAsync,
|
||||
type: _toAssetType(entity.type),
|
||||
createdTime: entity.createDateTime,
|
||||
modifiedTime: entity.modifiedDateTime,
|
||||
duration: entity.duration,
|
||||
hash: '',
|
||||
height: entity.height,
|
||||
width: entity.width,
|
||||
type: _toAssetType(entity.type),
|
||||
createdTime: entity.createDateTime.year == 1970
|
||||
? entity.modifiedDateTime
|
||||
: entity.createDateTime,
|
||||
modifiedTime: entity.modifiedDateTime,
|
||||
duration: entity.duration,
|
||||
localId: entity.id,
|
||||
);
|
||||
if (asset.createdTime.year == 1970) {
|
||||
asset = asset.copyWith(createdTime: asset.modifiedTime);
|
||||
}
|
||||
return asset;
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<File?> getOriginalFile(String localId) async {
|
||||
Future<File?> getOriginalFile(String localId) async {
|
||||
try {
|
||||
final entity = await ph.AssetEntity.fromId(localId);
|
||||
if (entity == null) {
|
||||
@@ -49,7 +47,7 @@ class DeviceAssetRepository
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<Uint8List?> getThumbnail(
|
||||
Future<Uint8List?> getThumbnail(
|
||||
String assetId, {
|
||||
int width = kGridThumbnailSize,
|
||||
int height = kGridThumbnailSize,
|
||||
|
||||
@@ -16,12 +16,13 @@ class DeviceAssetToHashRepository
|
||||
: _db = db;
|
||||
|
||||
@override
|
||||
FutureOr<bool> upsertAll(Iterable<DeviceAssetToHash> assetHash) async {
|
||||
Future<bool> upsertAll(Iterable<DeviceAssetToHash> assetHash) async {
|
||||
try {
|
||||
await _db.batch((batch) => batch.insertAllOnConflictUpdate(
|
||||
_db.deviceAssetToHash,
|
||||
assetHash.map(_toEntity),
|
||||
));
|
||||
await _db.txn(() async =>
|
||||
await _db.batch((batch) => batch.insertAllOnConflictUpdate(
|
||||
_db.deviceAssetToHash,
|
||||
assetHash.map(_toEntity),
|
||||
)));
|
||||
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
@@ -38,7 +39,7 @@ class DeviceAssetToHashRepository
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> deleteIds(Iterable<int> ids) async {
|
||||
Future<void> deleteIds(Iterable<int> ids) async {
|
||||
await _db.deviceAssetToHash.deleteWhere((row) => row.id.isIn(ids));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ class LogRepository implements ILogRepository {
|
||||
|
||||
@override
|
||||
Future<List<LogMessage>> getAll() async {
|
||||
return await _db.managers.logs.map(_toModel).get();
|
||||
return await _db.managers.logs
|
||||
.orderBy((o) => o.createdAt.desc())
|
||||
.map(_toModel)
|
||||
.get();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -23,14 +26,14 @@ class LogRepository implements ILogRepository {
|
||||
if (totalCount > limit) {
|
||||
final rowsToDelete = totalCount - limit;
|
||||
await _db.managers.logs
|
||||
.orderBy((o) => o.createdAt.desc())
|
||||
.orderBy((o) => o.createdAt.asc())
|
||||
.limit(rowsToDelete)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<bool> create(LogMessage log) async {
|
||||
Future<bool> create(LogMessage log) async {
|
||||
try {
|
||||
await _db.logs.insertOne(_toEntity(log));
|
||||
return true;
|
||||
@@ -41,11 +44,11 @@ class LogRepository implements ILogRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<bool> createAll(Iterable<LogMessage> logs) async {
|
||||
Future<bool> createAll(Iterable<LogMessage> logs) async {
|
||||
try {
|
||||
await _db.batch((b) {
|
||||
b.insertAll(_db.logs, logs.map(_toEntity));
|
||||
});
|
||||
await _db.txn(() async => await _db.batch((b) {
|
||||
b.insertAll(_db.logs, logs.map(_toEntity));
|
||||
}));
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint("Error while adding a log to the DB - $e");
|
||||
@@ -54,7 +57,7 @@ class LogRepository implements ILogRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<bool> deleteAll() async {
|
||||
Future<bool> deleteAll() async {
|
||||
try {
|
||||
await _db.logs.deleteAll();
|
||||
return true;
|
||||
@@ -70,8 +73,8 @@ LogsCompanion _toEntity(LogMessage log) {
|
||||
content: log.content,
|
||||
level: log.level,
|
||||
createdAt: Value(log.createdAt),
|
||||
error: Value(log.error),
|
||||
logger: Value(log.logger),
|
||||
error: Value(log.error),
|
||||
stack: Value(log.stack),
|
||||
);
|
||||
}
|
||||
@@ -79,10 +82,10 @@ LogsCompanion _toEntity(LogMessage log) {
|
||||
LogMessage _toModel(Log log) {
|
||||
return LogMessage(
|
||||
content: log.content,
|
||||
createdAt: log.createdAt,
|
||||
level: log.level,
|
||||
error: log.error,
|
||||
createdAt: log.createdAt,
|
||||
logger: log.logger,
|
||||
error: log.error,
|
||||
stack: log.stack,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,22 +15,30 @@ class RenderListRepository with LogMixin implements IRenderListRepository {
|
||||
Stream<RenderList> watchAll() {
|
||||
final assetCountExp = _db.asset.id.count();
|
||||
final createdTimeExp = _db.asset.createdTime;
|
||||
final monthYearExp = _db.asset.createdTime.strftime('%m-%Y');
|
||||
final modifiedTimeExp = _db.asset.modifiedTime.max();
|
||||
final monthYearExp = createdTimeExp.strftime('%m-%Y');
|
||||
|
||||
final query = _db.asset.selectOnly()
|
||||
..addColumns([assetCountExp, createdTimeExp])
|
||||
..addColumns([assetCountExp, createdTimeExp, modifiedTimeExp])
|
||||
..groupBy([monthYearExp])
|
||||
..orderBy([OrderingTerm.desc(createdTimeExp)]);
|
||||
|
||||
int lastAssetOffset = 0;
|
||||
DateTime recentModifiedTime = DateTime(1);
|
||||
|
||||
return query
|
||||
.expand((row) {
|
||||
final createdTime = row.read<DateTime>(createdTimeExp)!;
|
||||
final assetCount = row.read(assetCountExp)!;
|
||||
final modifiedTime = row.read(modifiedTimeExp)!;
|
||||
final assetOffset = lastAssetOffset;
|
||||
lastAssetOffset += assetCount;
|
||||
|
||||
// Get the recent modifed time. This is used to prevent unnecessary grid updates
|
||||
if (modifiedTime.isAfter(recentModifiedTime)) {
|
||||
recentModifiedTime = modifiedTime;
|
||||
}
|
||||
|
||||
return [
|
||||
RenderListMonthHeaderElement(date: createdTime),
|
||||
RenderListAssetElement(
|
||||
@@ -44,7 +52,9 @@ class RenderListRepository with LogMixin implements IRenderListRepository {
|
||||
.map((elements) {
|
||||
// Resets the value in closure so the watch refresh will work properly
|
||||
lastAssetOffset = 0;
|
||||
return RenderList(elements: elements);
|
||||
final modified = recentModifiedTime;
|
||||
recentModifiedTime = DateTime(1);
|
||||
return RenderList(elements: elements, modifiedTime: modified);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class StoreRepository with LogMixin implements IStoreRepository {
|
||||
const StoreRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||
|
||||
@override
|
||||
FutureOr<T?> tryGet<T, U>(StoreKey<T, U> key) async {
|
||||
Future<T?> tryGet<T, U>(StoreKey<T, U> key) async {
|
||||
final storeData = await _db.managers.store
|
||||
.filter((s) => s.id.equals(key.id))
|
||||
.getSingleOrNull();
|
||||
@@ -21,7 +21,7 @@ class StoreRepository with LogMixin implements IStoreRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<T> get<T, U>(StoreKey<T, U> key) async {
|
||||
Future<T> get<T, U>(StoreKey<T, U> key) async {
|
||||
final value = await tryGet(key);
|
||||
if (value == null) {
|
||||
throw StoreKeyNotFoundException(key);
|
||||
@@ -30,16 +30,16 @@ class StoreRepository with LogMixin implements IStoreRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<bool> upsert<T, U>(StoreKey<T, U> key, T value) async {
|
||||
Future<bool> upsert<T, U>(StoreKey<T, U> key, T value) async {
|
||||
try {
|
||||
final storeValue = key.converter.toPrimitive(value);
|
||||
final intValue = (key.type == int) ? storeValue as int : null;
|
||||
final stringValue = (key.type == String) ? storeValue as String : null;
|
||||
await _db.into(_db.store).insertOnConflictUpdate(StoreCompanion.insert(
|
||||
id: Value(key.id),
|
||||
intValue: Value(intValue),
|
||||
stringValue: Value(stringValue),
|
||||
));
|
||||
await _db.store.insertOnConflictUpdate(StoreCompanion.insert(
|
||||
id: Value(key.id),
|
||||
intValue: Value(intValue),
|
||||
stringValue: Value(stringValue),
|
||||
));
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
log.e("Cannot set store value - ${key.name}; id - ${key.id}", e, s);
|
||||
@@ -48,7 +48,7 @@ class StoreRepository with LogMixin implements IStoreRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> delete(StoreKey key) async {
|
||||
Future<void> delete(StoreKey key) async {
|
||||
await _db.managers.store.filter((s) => s.id.equals(key.id)).delete();
|
||||
}
|
||||
|
||||
@@ -61,11 +61,11 @@ class StoreRepository with LogMixin implements IStoreRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> deleteAll() async {
|
||||
Future<void> deleteAll() async {
|
||||
await _db.managers.store.delete();
|
||||
}
|
||||
|
||||
FutureOr<T?> _getValueFromStoreData<T, U>(
|
||||
Future<T?> _getValueFromStoreData<T, U>(
|
||||
StoreKey<T, U> key,
|
||||
StoreData? data,
|
||||
) async {
|
||||
|
||||
@@ -13,7 +13,7 @@ class UserRepository with LogMixin implements IUserRepository {
|
||||
const UserRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||
|
||||
@override
|
||||
FutureOr<User?> getForId(String userId) async {
|
||||
Future<User?> getForId(String userId) async {
|
||||
return await _db.managers.user
|
||||
.filter((f) => f.id.equals(userId))
|
||||
.map(_toModel)
|
||||
@@ -21,23 +21,23 @@ class UserRepository with LogMixin implements IUserRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<bool> upsert(User user) async {
|
||||
Future<bool> upsert(User user) async {
|
||||
try {
|
||||
await _db.into(_db.user).insertOnConflictUpdate(
|
||||
UserCompanion.insert(
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
profileImagePath: user.profileImagePath,
|
||||
avatarColor: user.avatarColor,
|
||||
inTimeline: Value(user.inTimeline),
|
||||
isAdmin: Value(user.isAdmin),
|
||||
memoryEnabled: Value(user.memoryEnabled),
|
||||
quotaSizeInBytes: Value(user.quotaSizeInBytes),
|
||||
quotaUsageInBytes: Value(user.quotaSizeInBytes),
|
||||
updatedAt: Value(user.updatedAt),
|
||||
),
|
||||
);
|
||||
await _db.user.insertOnConflictUpdate(
|
||||
UserCompanion.insert(
|
||||
id: user.id,
|
||||
updatedAt: Value(user.updatedAt),
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
isAdmin: Value(user.isAdmin),
|
||||
quotaSizeInBytes: Value(user.quotaSizeInBytes),
|
||||
quotaUsageInBytes: Value(user.quotaSizeInBytes),
|
||||
inTimeline: Value(user.inTimeline),
|
||||
profileImagePath: user.profileImagePath,
|
||||
memoryEnabled: Value(user.memoryEnabled),
|
||||
avatarColor: user.avatarColor,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
log.e("Cannot insert User into table - $user", e, s);
|
||||
@@ -46,7 +46,7 @@ class UserRepository with LogMixin implements IUserRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> deleteAll() async {
|
||||
Future<void> deleteAll() async {
|
||||
await _db.user.deleteAll();
|
||||
}
|
||||
}
|
||||
@@ -54,15 +54,15 @@ class UserRepository with LogMixin implements IUserRepository {
|
||||
User _toModel(UserData user) {
|
||||
return User(
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
avatarColor: user.avatarColor,
|
||||
inTimeline: user.inTimeline,
|
||||
isAdmin: user.isAdmin,
|
||||
memoryEnabled: user.memoryEnabled,
|
||||
updatedAt: user.updatedAt,
|
||||
name: user.name,
|
||||
profileImagePath: user.profileImagePath,
|
||||
email: user.email,
|
||||
isAdmin: user.isAdmin,
|
||||
quotaSizeInBytes: user.quotaSizeInBytes,
|
||||
quotaUsageInBytes: user.quotaUsageInBytes,
|
||||
updatedAt: user.updatedAt,
|
||||
inTimeline: user.inTimeline,
|
||||
profileImagePath: user.profileImagePath,
|
||||
memoryEnabled: user.memoryEnabled,
|
||||
avatarColor: user.avatarColor,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ class AssetSyncService with LogMixin {
|
||||
);
|
||||
|
||||
final assetsFromServer = await syncApiRepo.getFullSyncForUser(
|
||||
lastId: lastAssetId,
|
||||
limit: chunkSize,
|
||||
updatedUntil: updatedTill,
|
||||
lastId: lastAssetId,
|
||||
userId: user.id,
|
||||
);
|
||||
if (assetsFromServer == null) {
|
||||
@@ -92,8 +92,8 @@ class AssetSyncService with LogMixin {
|
||||
final (toAdd, toUpdate, toRemove) = await _diffAssets(
|
||||
newAssets,
|
||||
existingAssets,
|
||||
compare: compare,
|
||||
isRemoteSync: isRemoteSync,
|
||||
compare: compare,
|
||||
);
|
||||
|
||||
final assetsToAdd = toAdd.followedBy(toUpdate);
|
||||
@@ -111,7 +111,7 @@ class AssetSyncService with LogMixin {
|
||||
}) async {
|
||||
// fast paths for trivial cases: reduces memory usage during initial sync etc.
|
||||
if (newAssets.isEmpty && inDb.isEmpty) {
|
||||
return const (<Asset>[], <Asset>[], <Asset>[]);
|
||||
return (<Asset>[], <Asset>[], <Asset>[]);
|
||||
} else if (newAssets.isEmpty && isRemoteSync == null) {
|
||||
// remove all from database
|
||||
return (const <Asset>[], const <Asset>[], inDb);
|
||||
|
||||
@@ -88,7 +88,8 @@ class HashService with LogMixin {
|
||||
}
|
||||
|
||||
assert(hashesInDB.isEmpty, "All hashes should be processed at this point");
|
||||
_assetHashRepository.deleteIds(orphanedHashes.map((e) => e.id!).toList());
|
||||
await _assetHashRepository
|
||||
.deleteIds(orphanedHashes.map((e) => e.id!).toList());
|
||||
|
||||
return hashedAssets;
|
||||
}
|
||||
@@ -103,21 +104,21 @@ class HashService with LogMixin {
|
||||
final hashedAssets = <Asset>[];
|
||||
|
||||
for (final (index, hash) in hashes.indexed) {
|
||||
// ignore: avoid-unsafe-collection-methods
|
||||
final asset = toBeHashed.elementAt(index).asset;
|
||||
if (hash?.length == 20) {
|
||||
final asset = toBeHashed.elementAtOrNull(index)?.asset;
|
||||
if (asset != null && hash?.length == 20) {
|
||||
hashedAssets.add(asset.copyWith(hash: base64.encode(hash!)));
|
||||
} else {
|
||||
log.w("Failed to hash file ${asset.localId ?? '<null>'}, skipping");
|
||||
log.w("Failed to hash file ${asset?.localId ?? '<null>'}, skipping");
|
||||
}
|
||||
}
|
||||
|
||||
// Store the cache for future retrieval
|
||||
_assetHashRepository.upsertAll(hashedAssets.map((a) => DeviceAssetToHash(
|
||||
localId: a.localId!,
|
||||
hash: a.hash,
|
||||
modifiedTime: a.modifiedTime,
|
||||
)));
|
||||
await _assetHashRepository
|
||||
.upsertAll(hashedAssets.map((a) => DeviceAssetToHash(
|
||||
localId: a.localId!,
|
||||
hash: a.hash,
|
||||
modifiedTime: a.modifiedTime,
|
||||
)));
|
||||
|
||||
log.v("Hashed ${hashedAssets.length}/${toBeHashed.length} assets");
|
||||
return hashedAssets;
|
||||
@@ -127,8 +128,7 @@ class HashService with LogMixin {
|
||||
/// Files that could not be hashed will have a `null` value
|
||||
Future<List<Uint8List?>> _hashFiles(List<String> paths) async {
|
||||
try {
|
||||
final hashes = await _hostService.digestFiles(paths);
|
||||
return hashes;
|
||||
return await _hostService.digestFiles(paths);
|
||||
} catch (e, s) {
|
||||
log.e("Error occured while hashing assets", e, s);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import 'package:immich_mobile/domain/entities/asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/domain/models/asset.model.dart';
|
||||
|
||||
class DriftModelConverters {
|
||||
abstract final class DriftModelConverters {
|
||||
static Asset toAssetModel(AssetData asset) {
|
||||
return Asset(
|
||||
id: asset.id,
|
||||
localId: asset.localId,
|
||||
remoteId: asset.remoteId,
|
||||
name: asset.name,
|
||||
type: asset.type,
|
||||
hash: asset.hash,
|
||||
createdTime: asset.createdTime,
|
||||
modifiedTime: asset.modifiedTime,
|
||||
height: asset.height,
|
||||
width: asset.width,
|
||||
livePhotoVideoId: asset.livePhotoVideoId,
|
||||
type: asset.type,
|
||||
createdTime: asset.createdTime,
|
||||
modifiedTime: asset.modifiedTime,
|
||||
duration: asset.duration,
|
||||
localId: asset.localId,
|
||||
remoteId: asset.remoteId,
|
||||
livePhotoVideoId: asset.livePhotoVideoId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user