more refactors and logs page handling

This commit is contained in:
shenlong-tanwen
2024-10-23 02:30:46 +05:30
parent 8f47645cdb
commit a0afea04d8
90 changed files with 2386 additions and 584 deletions
@@ -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();
}
+8 -8
View File
@@ -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);
+12 -12
View File
@@ -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,
);
}
}