1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-19 00:32:49 +02:00
immich/mobile/lib/shared/models/asset.dart

513 lines
15 KiB
Dart
Raw Normal View History

import 'dart:convert';
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/extensions/string_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,
checksum = remote.checksum,
fileCreatedAt = remote.fileCreatedAt,
fileModifiedAt = remote.fileModifiedAt,
updatedAt = 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,
ownerId = fastHash(remote.ownerId),
exifInfo =
remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null,
isFavorite = remote.isFavorite,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
isArchived = remote.isArchived,
2023-10-22 04:38:07 +02:00
isTrashed = remote.isTrashed,
isReadOnly = remote.isReadOnly,
isOffline = remote.isOffline,
// workaround to nullify stackParentId for the parent asset until we refactor the mobile app
// stack handling to properly handle it
stackParentId =
remote.stackParentId == remote.id ? null : remote.stackParentId,
2023-10-22 04:38:07 +02:00
stackCount = remote.stackCount;
Asset.local(AssetEntity local, List<int> hash)
: localId = local.id,
checksum = base64.encode(hash),
durationInSeconds = local.duration,
type = AssetType.values[local.typeInt],
height = local.height,
width = local.width,
fileName = local.title!,
ownerId = Store.get(StoreKey.currentUser).isarId,
fileModifiedAt = local.modifiedDateTime,
updatedAt = local.modifiedDateTime,
isFavorite = local.isFavorite,
isArchived = false,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
isTrashed = false,
isReadOnly = false,
isOffline = false,
2023-10-22 04:38:07 +02:00
stackCount = 0,
fileCreatedAt = local.createDateTime {
if (fileCreatedAt.year == 1970) {
fileCreatedAt = fileModifiedAt;
}
if (local.latitude != null) {
exifInfo = ExifInfo(lat: local.latitude, long: local.longitude);
}
_local = local;
assert(hash.length == 20, "invalid SHA1 hash");
}
Asset({
this.id = Isar.autoIncrement,
required this.checksum,
this.remoteId,
required this.localId,
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,
this.isFavorite = false,
this.isArchived = false,
this.isTrashed = false,
2023-10-22 04:38:07 +02:00
this.stackParentId,
this.stackCount = 0,
this.isReadOnly = false,
this.isOffline = false,
});
@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;
/// stores the raw SHA1 bytes as a base64 String
/// because Isar cannot sort lists of byte arrays
String checksum;
@Index(unique: false, replace: false, type: IndexType.hash)
String? remoteId;
@Index(unique: false, replace: false, type: IndexType.hash)
String? localId;
@Index(
unique: true,
replace: false,
composite: [CompositeIndex("checksum", type: IndexType.hash)],
)
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 isArchived;
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
bool isTrashed;
bool isReadOnly;
bool isOffline;
@ignore
ExifInfo? exifInfo;
2023-10-22 04:38:07 +02:00
String? stackParentId;
@ignore
int get stackChildrenCount => stackCount ?? 0;
int? stackCount;
2023-10-22 04:38:07 +02:00
/// `true` if this [Asset] is present on the device
@ignore
bool get isLocal => localId != null;
@ignore
bool get isInDb => id != Isar.autoIncrement;
@ignore
String get name => p.withoutExtension(fileName);
/// `true` if this [Asset] is present on the server
@ignore
bool get isRemote => remoteId != null;
@ignore
bool get isImage => type == AssetType.image;
@ignore
AssetState get storage {
if (isRemote && isLocal) {
return AssetState.merged;
} else if (isRemote) {
return AssetState.remote;
} else if (isLocal) {
return AssetState.local;
} else {
throw Exception("Asset has illegal state: $this");
}
}
@ignore
Duration get duration => Duration(seconds: durationInSeconds);
2022-11-08 19:47:39 +02:00
@override
bool operator ==(other) {
if (other is! Asset) return false;
if (identical(this, other)) return true;
return id == other.id &&
checksum == other.checksum &&
remoteId == other.remoteId &&
localId == other.localId &&
ownerId == other.ownerId &&
fileCreatedAt.isAtSameMomentAs(other.fileCreatedAt) &&
fileModifiedAt.isAtSameMomentAs(other.fileModifiedAt) &&
updatedAt.isAtSameMomentAs(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 &&
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
isArchived == other.isArchived &&
2023-10-22 04:38:07 +02:00
isTrashed == other.isTrashed &&
stackCount == other.stackCount &&
stackParentId == other.stackParentId;
2022-11-08 19:47:39 +02:00
}
@override
@ignore
int get hashCode =>
id.hashCode ^
checksum.hashCode ^
remoteId.hashCode ^
localId.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 ^
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
isArchived.hashCode ^
2023-10-22 04:38:07 +02:00
isTrashed.hashCode ^
stackCount.hashCode ^
stackParentId.hashCode;
2022-11-08 19:47:39 +02:00
/// Returns `true` if this [Asset] can updated with values from parameter [a]
bool canUpdate(Asset a) {
assert(isInDb);
assert(checksum == a.checksum);
assert(a.storage != AssetState.merged);
return a.updatedAt.isAfter(updatedAt) ||
a.isRemote && !isRemote ||
a.isLocal && !isLocal ||
width == null && a.width != null ||
height == null && a.height != null ||
livePhotoVideoId == null && a.livePhotoVideoId != null ||
2023-10-22 04:38:07 +02:00
stackParentId == null && a.stackParentId != null ||
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
isFavorite != a.isFavorite ||
isArchived != a.isArchived ||
2023-10-22 04:38:07 +02:00
isTrashed != a.isTrashed ||
isReadOnly != a.isReadOnly ||
isOffline != a.isOffline ||
a.exifInfo?.latitude != exifInfo?.latitude ||
a.exifInfo?.longitude != exifInfo?.longitude ||
// no local stack count or different count from remote
((stackCount == null && a.stackCount != null) ||
(stackCount != null &&
a.stackCount != null &&
stackCount != a.stackCount));
}
/// Returns a new [Asset] with values from this and merged & updated with [a]
Asset updatedCopy(Asset a) {
assert(canUpdate(a));
if (a.updatedAt.isAfter(updatedAt)) {
// take most values from newer asset
// keep vales that can never be set by the asset not in DB
if (a.isRemote) {
return a._copyWith(
id: id,
localId: localId,
width: a.width ?? width,
height: a.height ?? height,
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
);
} else if (isRemote) {
return _copyWith(
localId: localId ?? a.localId,
width: width ?? a.width,
height: height ?? a.height,
exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id),
);
} else {
// TODO: Revisit this and remove all bool field assignments
return a._copyWith(
id: id,
remoteId: remoteId,
livePhotoVideoId: livePhotoVideoId,
// workaround to nullify stackParentId for the parent asset until we refactor the mobile app
// stack handling to properly handle it
stackParentId: stackParentId == remoteId ? null : stackParentId,
2023-10-22 04:38:07 +02:00
stackCount: stackCount,
isFavorite: isFavorite,
isArchived: isArchived,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
isTrashed: isTrashed,
isReadOnly: isReadOnly,
isOffline: isOffline,
);
}
} else {
// fill in potentially missing values, i.e. merge assets
if (a.isRemote) {
// values from remote take precedence
return _copyWith(
remoteId: a.remoteId,
width: a.width,
height: a.height,
livePhotoVideoId: a.livePhotoVideoId,
// workaround to nullify stackParentId for the parent asset until we refactor the mobile app
// stack handling to properly handle it
stackParentId: a.stackParentId == a.remoteId ? null : a.stackParentId,
stackCount: a.stackCount,
// isFavorite + isArchived are not set by device-only assets
isFavorite: a.isFavorite,
isArchived: a.isArchived,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
isTrashed: a.isTrashed,
isReadOnly: a.isReadOnly,
isOffline: a.isOffline,
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
);
} else {
// add only missing values (and set isLocal to true)
return _copyWith(
localId: localId ?? a.localId,
width: width ?? a.width,
height: height ?? a.height,
exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id),
);
}
}
}
Asset _copyWith({
Id? id,
String? checksum,
String? remoteId,
String? localId,
int? ownerId,
DateTime? fileCreatedAt,
DateTime? fileModifiedAt,
DateTime? updatedAt,
int? durationInSeconds,
AssetType? type,
short? width,
short? height,
String? fileName,
String? livePhotoVideoId,
bool? isFavorite,
bool? isArchived,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
bool? isTrashed,
bool? isReadOnly,
bool? isOffline,
ExifInfo? exifInfo,
2023-10-22 04:38:07 +02:00
String? stackParentId,
int? stackCount,
}) =>
Asset(
id: id ?? this.id,
checksum: checksum ?? this.checksum,
remoteId: remoteId ?? this.remoteId,
localId: localId ?? this.localId,
ownerId: ownerId ?? this.ownerId,
fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt,
fileModifiedAt: fileModifiedAt ?? this.fileModifiedAt,
updatedAt: updatedAt ?? this.updatedAt,
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
type: type ?? this.type,
width: width ?? this.width,
height: height ?? this.height,
fileName: fileName ?? this.fileName,
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
isFavorite: isFavorite ?? this.isFavorite,
isArchived: isArchived ?? this.isArchived,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
isTrashed: isTrashed ?? this.isTrashed,
isReadOnly: isReadOnly ?? this.isReadOnly,
isOffline: isOffline ?? this.isOffline,
exifInfo: exifInfo ?? this.exifInfo,
2023-10-22 04:38:07 +02:00
stackParentId: stackParentId ?? this.stackParentId,
stackCount: stackCount ?? this.stackCount,
);
Future<void> put(Isar db) async {
await db.assets.put(this);
if (exifInfo != null) {
exifInfo!.id = id;
await db.exifInfos.put(exifInfo!);
}
}
static int compareById(Asset a, Asset b) => a.id.compareTo(b.id);
static int compareByChecksum(Asset a, Asset b) =>
a.checksum.compareTo(b.checksum);
static int compareByOwnerChecksum(Asset a, Asset b) {
final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
if (ownerIdOrder != 0) return ownerIdOrder;
return compareByChecksum(a, b);
}
static int compareByOwnerChecksumCreatedModified(Asset a, Asset b) {
final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
if (ownerIdOrder != 0) return ownerIdOrder;
final int checksumOrder = compareByChecksum(a, b);
if (checksumOrder != 0) return checksumOrder;
final int createdOrder = a.fileCreatedAt.compareTo(b.fileCreatedAt);
if (createdOrder != 0) return createdOrder;
return a.fileModifiedAt.compareTo(b.fileModifiedAt);
}
@override
String toString() {
return """
{
"id": ${id == Isar.autoIncrement ? '"N/A"' : id},
"remoteId": "${remoteId ?? "N/A"}",
"localId": "${localId ?? "N/A"}",
"checksum": "$checksum",
refactor(mobile): Immich image provider (#7016) * Adds image provider * uses image provider * wip load preview * wip everything but activity asset thumbnail needs some help with a remote id * Immich provider used in gallery * First draft of the immich image provider, working nicely! * Removed OriginalImageProvider * Fixes for thumbnails * feat(mobile): thumbhash support (#7028) * feat(mobile): thumbhash support * perf(mobile): store bmp thumbhash bytes in Isar --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> * Uses octoimage for fade in and placeholders * fixes thumbnails, removes unused values, adds better thumbnail size * removes thumbhash support for now * Forgot one thumbhash removal * Use big thumbnail for local image on ios * fix(mobile): Multipart image loading for iOS double swipe (#7064) * uses local thumb first * Multipart thumbnail * Clean up file delete * await file delete * Fynn's comments, made thumbnail smaller and doesn't crash on erroring out on thumbnail * lint --------- Co-authored-by: Marty Fuhry <marty@fuhry.farm> Co-authored-by: Alex <alex.tran1502@gmail.com> * Moves http client to global private place for reuse * Got rid of usePreview for local image providers since we always show a thumbnail anyway first * linter --------- Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com> Co-authored-by: Marty Fuhry <marty@fuhry.farm>
2024-02-13 23:30:32 +02:00
"ownerId": $ownerId,
"livePhotoVideoId": "${livePhotoVideoId ?? "N/A"}",
2023-10-22 04:38:07 +02:00
"stackCount": "$stackCount",
"stackParentId": "${stackParentId ?? "N/A"}",
"fileCreatedAt": "$fileCreatedAt",
refactor(mobile): Immich image provider (#7016) * Adds image provider * uses image provider * wip load preview * wip everything but activity asset thumbnail needs some help with a remote id * Immich provider used in gallery * First draft of the immich image provider, working nicely! * Removed OriginalImageProvider * Fixes for thumbnails * feat(mobile): thumbhash support (#7028) * feat(mobile): thumbhash support * perf(mobile): store bmp thumbhash bytes in Isar --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> * Uses octoimage for fade in and placeholders * fixes thumbnails, removes unused values, adds better thumbnail size * removes thumbhash support for now * Forgot one thumbhash removal * Use big thumbnail for local image on ios * fix(mobile): Multipart image loading for iOS double swipe (#7064) * uses local thumb first * Multipart thumbnail * Clean up file delete * await file delete * Fynn's comments, made thumbnail smaller and doesn't crash on erroring out on thumbnail * lint --------- Co-authored-by: Marty Fuhry <marty@fuhry.farm> Co-authored-by: Alex <alex.tran1502@gmail.com> * Moves http client to global private place for reuse * Got rid of usePreview for local image providers since we always show a thumbnail anyway first * linter --------- Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com> Co-authored-by: Marty Fuhry <marty@fuhry.farm>
2024-02-13 23:30:32 +02:00
"fileModifiedAt": "$fileModifiedAt",
"updatedAt": "$updatedAt",
"durationInSeconds": $durationInSeconds,
"type": "$type",
refactor(mobile): Immich image provider (#7016) * Adds image provider * uses image provider * wip load preview * wip everything but activity asset thumbnail needs some help with a remote id * Immich provider used in gallery * First draft of the immich image provider, working nicely! * Removed OriginalImageProvider * Fixes for thumbnails * feat(mobile): thumbhash support (#7028) * feat(mobile): thumbhash support * perf(mobile): store bmp thumbhash bytes in Isar --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> * Uses octoimage for fade in and placeholders * fixes thumbnails, removes unused values, adds better thumbnail size * removes thumbhash support for now * Forgot one thumbhash removal * Use big thumbnail for local image on ios * fix(mobile): Multipart image loading for iOS double swipe (#7064) * uses local thumb first * Multipart thumbnail * Clean up file delete * await file delete * Fynn's comments, made thumbnail smaller and doesn't crash on erroring out on thumbnail * lint --------- Co-authored-by: Marty Fuhry <marty@fuhry.farm> Co-authored-by: Alex <alex.tran1502@gmail.com> * Moves http client to global private place for reuse * Got rid of usePreview for local image providers since we always show a thumbnail anyway first * linter --------- Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com> Co-authored-by: Marty Fuhry <marty@fuhry.farm>
2024-02-13 23:30:32 +02:00
"fileName": "$fileName",
"isFavorite": $isFavorite,
"isRemote": $isRemote,
"storage": "$storage",
"width": ${width ?? "N/A"},
"height": ${height ?? "N/A"},
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
"isArchived": $isArchived,
"isTrashed": $isTrashed,
"isReadOnly": $isReadOnly,
"isOffline": $isOffline,
}""";
}
}
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();
}
}
/// Describes where the information of this asset came from:
/// only from the local device, only from the remote server or merged from both
enum AssetState {
local,
remote,
merged,
}
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();
Future<Asset?> getByRemoteId(String id) =>
where().remoteIdEqualTo(id).findFirst();
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.localIdEqualTo(e));
}
}