123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- import 'package:immich_mobile/shared/models/exif_info.dart';
- import 'package:immich_mobile/shared/models/store.dart';
- import 'package:immich_mobile/utils/hash.dart';
- import 'package:isar/isar.dart';
- import 'package:openapi/api.dart';
- import 'package:photo_manager/photo_manager.dart';
- import 'package:immich_mobile/utils/builtin_extensions.dart';
- import 'package:path/path.dart' as p;
- part 'asset.g.dart';
- /// Asset (online or local)
- @Collection(inheritance: false)
- class Asset {
- Asset.remote(AssetResponseDto remote)
- : remoteId = remote.id,
- isLocal = false,
- fileCreatedAt = DateTime.parse(remote.fileCreatedAt),
- fileModifiedAt = DateTime.parse(remote.fileModifiedAt),
- updatedAt = DateTime.parse(remote.updatedAt),
- durationInSeconds = remote.duration.toDuration()?.inSeconds ?? 0,
- type = remote.type.toAssetType(),
- fileName = p.basename(remote.originalPath),
- height = remote.exifInfo?.exifImageHeight?.toInt(),
- width = remote.exifInfo?.exifImageWidth?.toInt(),
- livePhotoVideoId = remote.livePhotoVideoId,
- localId = remote.deviceAssetId,
- deviceId = fastHash(remote.deviceId),
- ownerId = fastHash(remote.ownerId),
- exifInfo =
- remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null,
- isFavorite = remote.isFavorite,
- isArchived = remote.isArchived;
- Asset.local(AssetEntity local)
- : localId = local.id,
- isLocal = true,
- durationInSeconds = local.duration,
- type = AssetType.values[local.typeInt],
- height = local.height,
- width = local.width,
- fileName = local.title!,
- deviceId = Store.get(StoreKey.deviceIdHash),
- ownerId = Store.get(StoreKey.currentUser).isarId,
- fileModifiedAt = local.modifiedDateTime,
- updatedAt = local.modifiedDateTime,
- isFavorite = local.isFavorite,
- isArchived = false,
- fileCreatedAt = local.createDateTime {
- if (fileCreatedAt.year == 1970) {
- fileCreatedAt = fileModifiedAt;
- }
- if (local.latitude != null) {
- exifInfo = ExifInfo(lat: local.latitude, long: local.longitude);
- }
- }
- Asset({
- this.remoteId,
- required this.localId,
- required this.deviceId,
- required this.ownerId,
- required this.fileCreatedAt,
- required this.fileModifiedAt,
- required this.updatedAt,
- required this.durationInSeconds,
- required this.type,
- this.width,
- this.height,
- required this.fileName,
- this.livePhotoVideoId,
- this.exifInfo,
- required this.isFavorite,
- required this.isLocal,
- required this.isArchived,
- });
- @ignore
- AssetEntity? _local;
- @ignore
- AssetEntity? get local {
- if (isLocal && _local == null) {
- _local = AssetEntity(
- id: localId,
- typeInt: isImage ? 1 : 2,
- width: width ?? 0,
- height: height ?? 0,
- duration: durationInSeconds,
- createDateSecond: fileCreatedAt.millisecondsSinceEpoch ~/ 1000,
- modifiedDateSecond: fileModifiedAt.millisecondsSinceEpoch ~/ 1000,
- title: fileName,
- );
- }
- return _local;
- }
- Id id = Isar.autoIncrement;
- @Index(unique: false, replace: false, type: IndexType.hash)
- String? remoteId;
- @Index(
- unique: false,
- replace: false,
- type: IndexType.hash,
- composite: [CompositeIndex('deviceId')],
- )
- String localId;
- int deviceId;
- int ownerId;
- DateTime fileCreatedAt;
- DateTime fileModifiedAt;
- DateTime updatedAt;
- int durationInSeconds;
- @Enumerated(EnumType.ordinal)
- AssetType type;
- short? width;
- short? height;
- String fileName;
- String? livePhotoVideoId;
- bool isFavorite;
- bool isLocal;
- bool isArchived;
- @ignore
- ExifInfo? exifInfo;
- @ignore
- bool get isInDb => id != Isar.autoIncrement;
- @ignore
- String get name => p.withoutExtension(fileName);
- @ignore
- bool get isRemote => remoteId != null;
- @ignore
- bool get isImage => type == AssetType.image;
- @ignore
- Duration get duration => Duration(seconds: durationInSeconds);
- @override
- bool operator ==(other) {
- if (other is! Asset) return false;
- return id == other.id &&
- remoteId == other.remoteId &&
- localId == other.localId &&
- deviceId == other.deviceId &&
- ownerId == other.ownerId &&
- fileCreatedAt == other.fileCreatedAt &&
- fileModifiedAt == other.fileModifiedAt &&
- updatedAt == other.updatedAt &&
- durationInSeconds == other.durationInSeconds &&
- type == other.type &&
- width == other.width &&
- height == other.height &&
- fileName == other.fileName &&
- livePhotoVideoId == other.livePhotoVideoId &&
- isFavorite == other.isFavorite &&
- isLocal == other.isLocal &&
- isArchived == other.isArchived;
- }
- @override
- @ignore
- int get hashCode =>
- id.hashCode ^
- remoteId.hashCode ^
- localId.hashCode ^
- deviceId.hashCode ^
- ownerId.hashCode ^
- fileCreatedAt.hashCode ^
- fileModifiedAt.hashCode ^
- updatedAt.hashCode ^
- durationInSeconds.hashCode ^
- type.hashCode ^
- width.hashCode ^
- height.hashCode ^
- fileName.hashCode ^
- livePhotoVideoId.hashCode ^
- isFavorite.hashCode ^
- isLocal.hashCode ^
- isArchived.hashCode;
- bool updateFromAssetEntity(AssetEntity ae) {
- // TODO check more fields;
- // width and height are most important because local assets require these
- final bool hasChanges =
- isLocal == false || width != ae.width || height != ae.height;
- if (hasChanges) {
- isLocal = true;
- width = ae.width;
- height = ae.height;
- }
- return hasChanges;
- }
- Asset withUpdatesFromDto(AssetResponseDto dto) =>
- Asset.remote(dto).updateFromDb(this);
- Asset updateFromDb(Asset a) {
- assert(localId == a.localId);
- assert(deviceId == a.deviceId);
- id = a.id;
- isLocal |= a.isLocal;
- remoteId ??= a.remoteId;
- width ??= a.width;
- height ??= a.height;
- exifInfo ??= a.exifInfo;
- exifInfo?.id = id;
- if (!isRemote) {
- isArchived = a.isArchived;
- }
- return this;
- }
- Future<void> put(Isar db) async {
- await db.assets.put(this);
- if (exifInfo != null) {
- exifInfo!.id = id;
- await db.exifInfos.put(exifInfo!);
- }
- }
- /// compares assets by [ownerId], [deviceId], [localId]
- static int compareByOwnerDeviceLocalId(Asset a, Asset b) {
- final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
- if (ownerIdOrder != 0) {
- return ownerIdOrder;
- }
- final int deviceIdOrder = a.deviceId.compareTo(b.deviceId);
- if (deviceIdOrder != 0) {
- return deviceIdOrder;
- }
- final int localIdOrder = a.localId.compareTo(b.localId);
- return localIdOrder;
- }
- /// compares assets by [ownerId], [deviceId], [localId], [fileModifiedAt]
- static int compareByOwnerDeviceLocalIdModified(Asset a, Asset b) {
- final int order = compareByOwnerDeviceLocalId(a, b);
- return order != 0 ? order : a.fileModifiedAt.compareTo(b.fileModifiedAt);
- }
- static int compareById(Asset a, Asset b) => a.id.compareTo(b.id);
- static int compareByLocalId(Asset a, Asset b) =>
- a.localId.compareTo(b.localId);
- @override
- String toString() {
- return """
- {
- "remoteId": "${remoteId ?? "N/A"}",
- "localId": "$localId",
- "deviceId": "$deviceId",
- "ownerId": "$ownerId",
- "livePhotoVideoId": "${livePhotoVideoId ?? "N/A"}",
- "fileCreatedAt": "$fileCreatedAt",
- "fileModifiedAt": "$fileModifiedAt",
- "updatedAt": "$updatedAt",
- "durationInSeconds": $durationInSeconds,
- "type": "$type",
- "fileName": "$fileName",
- "isFavorite": $isFavorite,
- "isLocal": $isLocal,
- "width": ${width ?? "N/A"},
- "height": ${height ?? "N/A"},
- "isArchived": $isArchived
- }""";
- }
- }
- enum AssetType {
- // do not change this order!
- other,
- image,
- video,
- audio,
- }
- extension AssetTypeEnumHelper on AssetTypeEnum {
- AssetType toAssetType() {
- switch (this) {
- case AssetTypeEnum.IMAGE:
- return AssetType.image;
- case AssetTypeEnum.VIDEO:
- return AssetType.video;
- case AssetTypeEnum.AUDIO:
- return AssetType.audio;
- case AssetTypeEnum.OTHER:
- return AssetType.other;
- }
- throw Exception();
- }
- }
- extension AssetsHelper on IsarCollection<Asset> {
- Future<int> deleteAllByRemoteId(Iterable<String> ids) =>
- ids.isEmpty ? Future.value(0) : _remote(ids).deleteAll();
- Future<int> deleteAllByLocalId(Iterable<String> ids) =>
- ids.isEmpty ? Future.value(0) : _local(ids).deleteAll();
- Future<List<Asset>> getAllByRemoteId(Iterable<String> ids) =>
- ids.isEmpty ? Future.value([]) : _remote(ids).findAll();
- Future<List<Asset>> getAllByLocalId(Iterable<String> ids) =>
- ids.isEmpty ? Future.value([]) : _local(ids).findAll();
- QueryBuilder<Asset, Asset, QAfterWhereClause> _remote(Iterable<String> ids) =>
- where().anyOf(ids, (q, String e) => q.remoteIdEqualTo(e));
- QueryBuilder<Asset, Asset, QAfterWhereClause> _local(Iterable<String> ids) {
- return where().anyOf(
- ids,
- (q, String e) =>
- q.localIdDeviceIdEqualTo(e, Store.get(StoreKey.deviceIdHash)),
- );
- }
- }
|