2023-02-04 22:42:42 +02:00
|
|
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
2023-03-04 00:38:30 +02:00
|
|
|
import 'package:immich_mobile/shared/models/store.dart';
|
|
|
|
import 'package:immich_mobile/utils/hash.dart';
|
|
|
|
import 'package:isar/isar.dart';
|
2022-11-08 19:00:24 +02:00
|
|
|
import 'package:openapi/api.dart';
|
|
|
|
import 'package:photo_manager/photo_manager.dart';
|
2023-02-04 22:42:42 +02:00
|
|
|
import 'package:immich_mobile/utils/builtin_extensions.dart';
|
|
|
|
import 'package:path/path.dart' as p;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
part 'asset.g.dart';
|
|
|
|
|
2022-11-08 19:00:24 +02:00
|
|
|
/// Asset (online or local)
|
2023-03-04 00:38:30 +02:00
|
|
|
@Collection(inheritance: false)
|
2022-11-08 19:00:24 +02:00
|
|
|
class Asset {
|
2023-02-04 22:42:42 +02:00
|
|
|
Asset.remote(AssetResponseDto remote)
|
|
|
|
: remoteId = remote.id,
|
2023-03-04 00:38:30 +02:00
|
|
|
isLocal = false,
|
|
|
|
fileCreatedAt = DateTime.parse(remote.fileCreatedAt).toUtc(),
|
|
|
|
fileModifiedAt = DateTime.parse(remote.fileModifiedAt).toUtc(),
|
|
|
|
updatedAt = DateTime.parse(remote.updatedAt).toUtc(),
|
2023-03-06 17:27:01 +02:00
|
|
|
// use -1 as fallback duration (to not mix it up with non-video assets correctly having duration=0)
|
|
|
|
durationInSeconds = remote.duration.toDuration()?.inSeconds ?? -1,
|
2023-02-04 22:42:42 +02:00
|
|
|
fileName = p.basename(remote.originalPath),
|
|
|
|
height = remote.exifInfo?.exifImageHeight?.toInt(),
|
|
|
|
width = remote.exifInfo?.exifImageWidth?.toInt(),
|
|
|
|
livePhotoVideoId = remote.livePhotoVideoId,
|
2023-03-04 00:38:30 +02:00
|
|
|
localId = remote.deviceAssetId,
|
|
|
|
deviceId = fastHash(remote.deviceId),
|
|
|
|
ownerId = fastHash(remote.ownerId),
|
2023-02-04 22:42:42 +02:00
|
|
|
exifInfo =
|
2023-02-05 05:25:11 +02:00
|
|
|
remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null,
|
|
|
|
isFavorite = remote.isFavorite;
|
2023-02-04 22:42:42 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
Asset.local(AssetEntity local)
|
2023-02-04 22:42:42 +02:00
|
|
|
: localId = local.id,
|
2023-03-04 00:38:30 +02:00
|
|
|
isLocal = true,
|
2023-02-04 22:42:42 +02:00
|
|
|
durationInSeconds = local.duration,
|
|
|
|
height = local.height,
|
|
|
|
width = local.width,
|
|
|
|
fileName = local.title!,
|
2023-03-04 00:38:30 +02:00
|
|
|
deviceId = Store.get(StoreKey.deviceIdHash),
|
2023-03-23 03:36:44 +02:00
|
|
|
ownerId = Store.get(StoreKey.currentUser).isarId,
|
2023-02-19 18:44:53 +02:00
|
|
|
fileModifiedAt = local.modifiedDateTime.toUtc(),
|
2023-03-04 00:38:30 +02:00
|
|
|
updatedAt = local.modifiedDateTime.toUtc(),
|
2023-02-05 05:25:11 +02:00
|
|
|
isFavorite = local.isFavorite,
|
2023-02-19 18:44:53 +02:00
|
|
|
fileCreatedAt = local.createDateTime.toUtc() {
|
|
|
|
if (fileCreatedAt.year == 1970) {
|
|
|
|
fileCreatedAt = fileModifiedAt;
|
2023-02-04 22:42:42 +02:00
|
|
|
}
|
2023-03-04 00:38:30 +02:00
|
|
|
if (local.latitude != null) {
|
|
|
|
exifInfo = ExifInfo(lat: local.latitude, long: local.longitude);
|
|
|
|
}
|
2022-11-08 19:00:24 +02:00
|
|
|
}
|
|
|
|
|
2023-02-04 22:42:42 +02:00
|
|
|
Asset({
|
|
|
|
this.remoteId,
|
2023-03-04 00:38:30 +02:00
|
|
|
required this.localId,
|
2023-02-04 22:42:42 +02:00
|
|
|
required this.deviceId,
|
|
|
|
required this.ownerId,
|
2023-02-19 18:44:53 +02:00
|
|
|
required this.fileCreatedAt,
|
|
|
|
required this.fileModifiedAt,
|
2023-03-04 00:38:30 +02:00
|
|
|
required this.updatedAt,
|
2023-02-04 22:42:42 +02:00
|
|
|
required this.durationInSeconds,
|
|
|
|
this.width,
|
|
|
|
this.height,
|
|
|
|
required this.fileName,
|
|
|
|
this.livePhotoVideoId,
|
|
|
|
this.exifInfo,
|
2023-02-05 05:25:11 +02:00
|
|
|
required this.isFavorite,
|
2023-03-04 00:38:30 +02:00
|
|
|
required this.isLocal,
|
2023-02-04 22:42:42 +02:00
|
|
|
});
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
@ignore
|
2023-02-04 22:42:42 +02:00
|
|
|
AssetEntity? _local;
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
@ignore
|
2023-02-04 22:42:42 +02:00
|
|
|
AssetEntity? get local {
|
|
|
|
if (isLocal && _local == null) {
|
|
|
|
_local = AssetEntity(
|
2023-03-04 00:38:30 +02:00
|
|
|
id: localId.toString(),
|
2023-02-04 22:42:42 +02:00
|
|
|
typeInt: isImage ? 1 : 2,
|
|
|
|
width: width!,
|
|
|
|
height: height!,
|
|
|
|
duration: durationInSeconds,
|
2023-02-19 18:44:53 +02:00
|
|
|
createDateSecond: fileCreatedAt.millisecondsSinceEpoch ~/ 1000,
|
|
|
|
modifiedDateSecond: fileModifiedAt.millisecondsSinceEpoch ~/ 1000,
|
2023-02-04 22:42:42 +02:00
|
|
|
title: fileName,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return _local;
|
2022-11-08 19:00:24 +02:00
|
|
|
}
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
Id id = Isar.autoIncrement;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
@Index(unique: false, replace: false, type: IndexType.hash)
|
2023-02-04 22:42:42 +02:00
|
|
|
String? remoteId;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
@Index(
|
|
|
|
unique: true,
|
|
|
|
replace: false,
|
|
|
|
type: IndexType.hash,
|
|
|
|
composite: [CompositeIndex('deviceId')],
|
|
|
|
)
|
|
|
|
String localId;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
int deviceId;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
int ownerId;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-02-19 18:44:53 +02:00
|
|
|
DateTime fileCreatedAt;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-02-19 18:44:53 +02:00
|
|
|
DateTime fileModifiedAt;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
DateTime updatedAt;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-02-04 22:42:42 +02:00
|
|
|
int durationInSeconds;
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
short? width;
|
2023-02-04 22:42:42 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
short? height;
|
2023-02-04 22:42:42 +02:00
|
|
|
|
|
|
|
String fileName;
|
|
|
|
|
|
|
|
String? livePhotoVideoId;
|
|
|
|
|
2023-02-05 05:25:11 +02:00
|
|
|
bool isFavorite;
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
bool isLocal;
|
2023-02-04 22:42:42 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
@ignore
|
|
|
|
ExifInfo? exifInfo;
|
|
|
|
|
|
|
|
@ignore
|
|
|
|
bool get isInDb => id != Isar.autoIncrement;
|
|
|
|
|
|
|
|
@ignore
|
2023-02-04 22:42:42 +02:00
|
|
|
String get name => p.withoutExtension(fileName);
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
@ignore
|
2023-02-04 22:42:42 +02:00
|
|
|
bool get isRemote => remoteId != null;
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
@ignore
|
2023-02-04 22:42:42 +02:00
|
|
|
bool get isImage => durationInSeconds == 0;
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
@ignore
|
2023-02-04 22:42:42 +02:00
|
|
|
Duration get duration => Duration(seconds: durationInSeconds);
|
2022-11-08 19:00:24 +02:00
|
|
|
|
2022-11-08 19:47:39 +02:00
|
|
|
@override
|
|
|
|
bool operator ==(other) {
|
|
|
|
if (other is! Asset) return false;
|
2023-03-04 00:38:30 +02:00
|
|
|
return id == other.id;
|
2022-11-08 19:47:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2023-03-04 00:38:30 +02:00
|
|
|
@ignore
|
2022-11-08 19:47:39 +02:00
|
|
|
int get hashCode => id.hashCode;
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
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;
|
2022-11-08 19:00:24 +02:00
|
|
|
}
|
2023-03-04 00:38:30 +02:00
|
|
|
return hasChanges;
|
2022-11-08 19:00:24 +02:00
|
|
|
}
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
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;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> put(Isar db) async {
|
|
|
|
await db.assets.put(this);
|
|
|
|
if (exifInfo != null) {
|
|
|
|
exifInfo!.id = id;
|
|
|
|
await db.exifInfos.put(exifInfo!);
|
2022-11-08 19:00:24 +02:00
|
|
|
}
|
2023-03-04 00:38:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int compareByDeviceIdLocalId(Asset a, Asset b) {
|
|
|
|
final int order = a.deviceId.compareTo(b.deviceId);
|
|
|
|
return order == 0 ? a.localId.compareTo(b.localId) : order;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compareById(Asset a, Asset b) => a.id.compareTo(b.id);
|
|
|
|
|
|
|
|
static int compareByLocalId(Asset a, Asset b) =>
|
|
|
|
a.localId.compareTo(b.localId);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)),
|
|
|
|
);
|
2022-11-08 19:00:24 +02:00
|
|
|
}
|
|
|
|
}
|