mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
feature(mobile): sync assets, albums & users to local database on device (#1759)
* feature(mobile): sync assets, albums & users to local database on device * try to fix tests * move DB sync operations to new SyncService * clear db on user logout * fix reason for endless loading timeline * fix error when deleting album * fix thumbnail of device albums * add a few comments * fix Hive box not open in album service when loading local assets * adjust tests to int IDs * fix bug: show all albums when Recent is selected * update generated api * reworked Recents album isAll handling * guard against wrongly interleaved sync operations * fix: timeline asset ordering (sort asset state by created at) * fix: sort assets in albums by created at
This commit is contained in:
committed by
GitHub
parent
8f11529a75
commit
8708867c1c
@@ -1,132 +1,153 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
part 'album.g.dart';
|
||||
|
||||
@Collection(inheritance: false)
|
||||
class Album {
|
||||
Album.remote(AlbumResponseDto dto)
|
||||
: remoteId = dto.id,
|
||||
name = dto.albumName,
|
||||
createdAt = DateTime.parse(dto.createdAt),
|
||||
// TODO add modifiedAt to server
|
||||
modifiedAt = DateTime.parse(dto.createdAt),
|
||||
shared = dto.shared,
|
||||
ownerId = dto.ownerId,
|
||||
albumThumbnailAssetId = dto.albumThumbnailAssetId,
|
||||
assetCount = dto.assetCount,
|
||||
sharedUsers = dto.sharedUsers.map((e) => User.fromDto(e)).toList(),
|
||||
assets = dto.assets.map(Asset.remote).toList();
|
||||
|
||||
@protected
|
||||
Album({
|
||||
this.remoteId,
|
||||
this.localId,
|
||||
required this.name,
|
||||
required this.ownerId,
|
||||
required this.createdAt,
|
||||
required this.modifiedAt,
|
||||
required this.shared,
|
||||
required this.assetCount,
|
||||
this.albumThumbnailAssetId,
|
||||
this.sharedUsers = const [],
|
||||
this.assets = const [],
|
||||
});
|
||||
|
||||
Id id = Isar.autoIncrement;
|
||||
@Index(unique: false, replace: false, type: IndexType.hash)
|
||||
String? remoteId;
|
||||
@Index(unique: false, replace: false, type: IndexType.hash)
|
||||
String? localId;
|
||||
String name;
|
||||
String ownerId;
|
||||
DateTime createdAt;
|
||||
DateTime modifiedAt;
|
||||
bool shared;
|
||||
String? albumThumbnailAssetId;
|
||||
int assetCount;
|
||||
List<User> sharedUsers = const [];
|
||||
List<Asset> assets = const [];
|
||||
final IsarLink<User> owner = IsarLink<User>();
|
||||
final IsarLink<Asset> thumbnail = IsarLink<Asset>();
|
||||
final IsarLinks<User> sharedUsers = IsarLinks<User>();
|
||||
final IsarLinks<Asset> assets = IsarLinks<Asset>();
|
||||
|
||||
List<Asset> _sortedAssets = [];
|
||||
|
||||
@ignore
|
||||
List<Asset> get sortedAssets => _sortedAssets;
|
||||
|
||||
@ignore
|
||||
bool get isRemote => remoteId != null;
|
||||
|
||||
@ignore
|
||||
bool get isLocal => localId != null;
|
||||
|
||||
String get id => isRemote ? remoteId! : localId!;
|
||||
@ignore
|
||||
int get assetCount => assets.length;
|
||||
|
||||
@ignore
|
||||
String? get ownerId => owner.value?.id;
|
||||
|
||||
Future<void> loadSortedAssets() async {
|
||||
_sortedAssets = await assets.filter().sortByFileCreatedAt().findAll();
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
if (other is! Album) return false;
|
||||
return remoteId == other.remoteId &&
|
||||
return id == other.id &&
|
||||
remoteId == other.remoteId &&
|
||||
localId == other.localId &&
|
||||
name == other.name &&
|
||||
createdAt == other.createdAt &&
|
||||
modifiedAt == other.modifiedAt &&
|
||||
shared == other.shared &&
|
||||
ownerId == other.ownerId &&
|
||||
albumThumbnailAssetId == other.albumThumbnailAssetId;
|
||||
owner.value == other.owner.value &&
|
||||
thumbnail.value == other.thumbnail.value &&
|
||||
sharedUsers.length == other.sharedUsers.length &&
|
||||
assets.length == other.assets.length;
|
||||
}
|
||||
|
||||
@override
|
||||
@ignore
|
||||
int get hashCode =>
|
||||
id.hashCode ^
|
||||
remoteId.hashCode ^
|
||||
localId.hashCode ^
|
||||
name.hashCode ^
|
||||
createdAt.hashCode ^
|
||||
modifiedAt.hashCode ^
|
||||
shared.hashCode ^
|
||||
ownerId.hashCode ^
|
||||
albumThumbnailAssetId.hashCode;
|
||||
owner.value.hashCode ^
|
||||
thumbnail.value.hashCode ^
|
||||
sharedUsers.length.hashCode ^
|
||||
assets.length.hashCode;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json["remoteId"] = remoteId;
|
||||
json["localId"] = localId;
|
||||
json["name"] = name;
|
||||
json["ownerId"] = ownerId;
|
||||
json["createdAt"] = createdAt.millisecondsSinceEpoch;
|
||||
json["modifiedAt"] = modifiedAt.millisecondsSinceEpoch;
|
||||
json["shared"] = shared;
|
||||
json["albumThumbnailAssetId"] = albumThumbnailAssetId;
|
||||
json["assetCount"] = assetCount;
|
||||
json["sharedUsers"] = sharedUsers;
|
||||
json["assets"] = assets;
|
||||
return json;
|
||||
static Album local(AssetPathEntity ape) {
|
||||
final Album a = Album(
|
||||
name: ape.name,
|
||||
createdAt: ape.lastModified?.toUtc() ?? DateTime.now().toUtc(),
|
||||
modifiedAt: ape.lastModified?.toUtc() ?? DateTime.now().toUtc(),
|
||||
shared: false,
|
||||
);
|
||||
a.owner.value = Store.get(StoreKey.currentUser);
|
||||
a.localId = ape.id;
|
||||
return a;
|
||||
}
|
||||
|
||||
static Album? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
return Album(
|
||||
remoteId: json["remoteId"],
|
||||
localId: json["localId"],
|
||||
name: json["name"],
|
||||
ownerId: json["ownerId"],
|
||||
createdAt: DateTime.fromMillisecondsSinceEpoch(
|
||||
json["createdAt"],
|
||||
isUtc: true,
|
||||
),
|
||||
modifiedAt: DateTime.fromMillisecondsSinceEpoch(
|
||||
json["modifiedAt"],
|
||||
isUtc: true,
|
||||
),
|
||||
shared: json["shared"],
|
||||
albumThumbnailAssetId: json["albumThumbnailAssetId"],
|
||||
assetCount: json["assetCount"],
|
||||
sharedUsers: _listFromJson<User>(json["sharedUsers"], User.fromJson),
|
||||
assets: _listFromJson<Asset>(json["assets"], Asset.fromJson),
|
||||
);
|
||||
static Future<Album> remote(AlbumResponseDto dto) async {
|
||||
final Isar db = Isar.getInstance()!;
|
||||
final Album a = Album(
|
||||
remoteId: dto.id,
|
||||
name: dto.albumName,
|
||||
createdAt: DateTime.parse(dto.createdAt),
|
||||
modifiedAt: DateTime.parse(dto.updatedAt),
|
||||
shared: dto.shared,
|
||||
);
|
||||
a.owner.value = await db.users.getById(dto.ownerId);
|
||||
if (dto.albumThumbnailAssetId != null) {
|
||||
a.thumbnail.value = await db.assets
|
||||
.where()
|
||||
.remoteIdEqualTo(dto.albumThumbnailAssetId)
|
||||
.findFirst();
|
||||
}
|
||||
return null;
|
||||
if (dto.sharedUsers.isNotEmpty) {
|
||||
final users = await db.users
|
||||
.getAllById(dto.sharedUsers.map((e) => e.id).toList(growable: false));
|
||||
a.sharedUsers.addAll(users.cast());
|
||||
}
|
||||
if (dto.assets.isNotEmpty) {
|
||||
final assets =
|
||||
await db.assets.getAllByRemoteId(dto.assets.map((e) => e.id));
|
||||
a.assets.addAll(assets);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
List<T> _listFromJson<T>(
|
||||
dynamic json,
|
||||
T? Function(dynamic) fromJson,
|
||||
) {
|
||||
final result = <T>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final entry in json) {
|
||||
final value = fromJson(entry);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
extension AssetsHelper on IsarCollection<Album> {
|
||||
Future<void> store(Album a) async {
|
||||
await put(a);
|
||||
await a.owner.save();
|
||||
await a.thumbnail.save();
|
||||
await a.sharedUsers.save();
|
||||
await a.assets.save();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
extension AssetPathEntityHelper on AssetPathEntity {
|
||||
Future<List<Asset>> getAssets({
|
||||
int start = 0,
|
||||
int end = 0x7fffffffffffffff,
|
||||
}) async {
|
||||
final assetEntities = await getAssetListRange(start: start, end: end);
|
||||
return assetEntities.map(Asset.local).toList();
|
||||
}
|
||||
}
|
||||
|
||||
extension AlbumResponseDtoHelper on AlbumResponseDto {
|
||||
List<Asset> getAssets() => assets.map(Asset.remote).toList();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user