refactor: add back isOffline and remove trashReason from asset, change sync job flow

This commit is contained in:
Zack Pollard 2024-09-17 17:55:27 +01:00
parent e071cc9ca8
commit ba0d5410cd
35 changed files with 253 additions and 557 deletions

View File

@ -1,4 +1,4 @@
import { LibraryResponseDto, LoginResponseDto, TrashReason, getAllLibraries, scanLibrary } from '@immich/sdk'; import { LibraryResponseDto, LoginResponseDto, getAllLibraries, scanLibrary } from '@immich/sdk';
import { cpSync, existsSync } from 'node:fs'; import { cpSync, existsSync } from 'node:fs';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { userDto, uuidDto } from 'src/fixtures'; import { userDto, uuidDto } from 'src/fixtures';
@ -411,7 +411,7 @@ describe('/libraries', () => {
expect(assets.count).toBe(0); expect(assets.count).toBe(0);
}); });
it('should trash an asset if its file is missing', async () => { it('should set an asset offline if its file is missing', async () => {
const library = await utils.createLibrary(admin.accessToken, { const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId, ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/offline`], importPaths: [`${testAssetDirInternal}/temp/offline`],
@ -436,15 +436,14 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'library'); await utils.waitForQueueFinish(admin.accessToken, 'library');
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(trashedAsset.isTrashed).toBe(true);
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
expect(trashedAsset.trashReason).toEqual(TrashReason.Offline); expect(trashedAsset.isOffline).toEqual(true);
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id }); const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
expect(newAssets.items).toEqual([]); expect(newAssets.items).toEqual([]);
}); });
it('should trash an asset if its file is not in any import path', async () => { it('should set an asset offline its file is not in any import path', async () => {
const library = await utils.createLibrary(admin.accessToken, { const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId, ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/offline`], importPaths: [`${testAssetDirInternal}/temp/offline`],
@ -474,9 +473,8 @@ describe('/libraries', () => {
await utils.waitForQueueFinish(admin.accessToken, 'library'); await utils.waitForQueueFinish(admin.accessToken, 'library');
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(trashedAsset.isTrashed).toBe(true);
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
expect(trashedAsset.trashReason).toEqual(TrashReason.Offline); expect(trashedAsset.isOffline).toBe(true);
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id }); const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
@ -486,7 +484,7 @@ describe('/libraries', () => {
utils.removeDirectory(`${testAssetDir}/temp/another-path/`); utils.removeDirectory(`${testAssetDir}/temp/another-path/`);
}); });
it('should trash an asset if its file is covered by an exclusion pattern', async () => { it('should set an asset offline if its file is covered by an exclusion pattern', async () => {
const library = await utils.createLibrary(admin.accessToken, { const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId, ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp`], importPaths: [`${testAssetDirInternal}/temp`],
@ -512,7 +510,7 @@ describe('/libraries', () => {
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(trashedAsset.isTrashed).toBe(true); expect(trashedAsset.isTrashed).toBe(true);
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/directoryB/assetB.png`); expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/directoryB/assetB.png`);
expect(trashedAsset.trashReason).toEqual(TrashReason.Offline); expect(trashedAsset.isOffline).toBe(true);
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id }); const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });

View File

@ -1,4 +1,4 @@
import { LoginResponseDto, TrashReason, getAssetInfo, getAssetStatistics, scanLibrary } from '@immich/sdk'; import { LoginResponseDto, getAssetInfo, getAssetStatistics, scanLibrary } from '@immich/sdk';
import { existsSync } from 'node:fs'; import { existsSync } from 'node:fs';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { errorDto } from 'src/responses'; import { errorDto } from 'src/responses';
@ -35,9 +35,7 @@ describe('/trash', () => {
await utils.deleteAssets(admin.accessToken, [assetId]); await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) }); const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual( expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: true }));
expect.objectContaining({ id: assetId, isTrashed: true, trashReason: TrashReason.Deleted }),
);
const { status, body } = await request(app) const { status, body } = await request(app)
.post('/trash/empty') .post('/trash/empty')
@ -59,9 +57,7 @@ describe('/trash', () => {
await utils.deleteAssets(admin.accessToken, [assetId]); await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) }); const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual( expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: true, isArchived: true }));
expect.objectContaining({ id: assetId, isTrashed: true, isArchived: true, trashReason: TrashReason.Deleted }),
);
const { status, body } = await request(app) const { status, body } = await request(app)
.post('/trash/empty') .post('/trash/empty')
@ -160,17 +156,13 @@ describe('/trash', () => {
await utils.waitForQueueFinish(admin.accessToken, 'library'); await utils.waitForQueueFinish(admin.accessToken, 'library');
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) }); const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual( expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
expect.objectContaining({ id: assetId, isTrashed: true, trashReason: TrashReason.Offline }),
);
const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`); const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204); expect(status).toBe(204);
const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) }); const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
expect(after).toStrictEqual( expect(after).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
expect.objectContaining({ id: assetId, isTrashed: true, trashReason: TrashReason.Offline }),
);
}); });
}); });

View File

@ -132,7 +132,6 @@ Class | Method | HTTP request | Description
*LibrariesApi* | [**getAllLibraries**](doc//LibrariesApi.md#getalllibraries) | **GET** /libraries | *LibrariesApi* | [**getAllLibraries**](doc//LibrariesApi.md#getalllibraries) | **GET** /libraries |
*LibrariesApi* | [**getLibrary**](doc//LibrariesApi.md#getlibrary) | **GET** /libraries/{id} | *LibrariesApi* | [**getLibrary**](doc//LibrariesApi.md#getlibrary) | **GET** /libraries/{id} |
*LibrariesApi* | [**getLibraryStatistics**](doc//LibrariesApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics | *LibrariesApi* | [**getLibraryStatistics**](doc//LibrariesApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics |
*LibrariesApi* | [**removeOfflineFiles**](doc//LibrariesApi.md#removeofflinefiles) | **POST** /libraries/{id}/removeOffline |
*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | *LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan |
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | *LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} |
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | *LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate |
@ -384,7 +383,6 @@ Class | Method | HTTP request | Description
- [ReactionLevel](doc//ReactionLevel.md) - [ReactionLevel](doc//ReactionLevel.md)
- [ReactionType](doc//ReactionType.md) - [ReactionType](doc//ReactionType.md)
- [ReverseGeocodingStateResponseDto](doc//ReverseGeocodingStateResponseDto.md) - [ReverseGeocodingStateResponseDto](doc//ReverseGeocodingStateResponseDto.md)
- [ScanLibraryDto](doc//ScanLibraryDto.md)
- [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md) - [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md)
- [SearchAssetResponseDto](doc//SearchAssetResponseDto.md) - [SearchAssetResponseDto](doc//SearchAssetResponseDto.md)
- [SearchExploreItem](doc//SearchExploreItem.md) - [SearchExploreItem](doc//SearchExploreItem.md)

View File

@ -197,7 +197,6 @@ part 'model/ratings_update.dart';
part 'model/reaction_level.dart'; part 'model/reaction_level.dart';
part 'model/reaction_type.dart'; part 'model/reaction_type.dart';
part 'model/reverse_geocoding_state_response_dto.dart'; part 'model/reverse_geocoding_state_response_dto.dart';
part 'model/scan_library_dto.dart';
part 'model/search_album_response_dto.dart'; part 'model/search_album_response_dto.dart';
part 'model/search_asset_response_dto.dart'; part 'model/search_asset_response_dto.dart';
part 'model/search_explore_item.dart'; part 'model/search_explore_item.dart';

View File

@ -449,8 +449,6 @@ class ApiClient {
return ReactionTypeTypeTransformer().decode(value); return ReactionTypeTypeTransformer().decode(value);
case 'ReverseGeocodingStateResponseDto': case 'ReverseGeocodingStateResponseDto':
return ReverseGeocodingStateResponseDto.fromJson(value); return ReverseGeocodingStateResponseDto.fromJson(value);
case 'ScanLibraryDto':
return ScanLibraryDto.fromJson(value);
case 'SearchAlbumResponseDto': case 'SearchAlbumResponseDto':
return SearchAlbumResponseDto.fromJson(value); return SearchAlbumResponseDto.fromJson(value);
case 'SearchAssetResponseDto': case 'SearchAssetResponseDto':

View File

@ -15,7 +15,6 @@ class AssetBulkDeleteDto {
AssetBulkDeleteDto({ AssetBulkDeleteDto({
this.force, this.force,
this.ids = const [], this.ids = const [],
this.trashReason,
}); });
/// ///
@ -28,23 +27,19 @@ class AssetBulkDeleteDto {
List<String> ids; List<String> ids;
AssetBulkDeleteDtoTrashReasonEnum? trashReason;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetBulkDeleteDto && bool operator ==(Object other) => identical(this, other) || other is AssetBulkDeleteDto &&
other.force == force && other.force == force &&
_deepEquality.equals(other.ids, ids) && _deepEquality.equals(other.ids, ids);
other.trashReason == trashReason;
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(force == null ? 0 : force!.hashCode) + (force == null ? 0 : force!.hashCode) +
(ids.hashCode) + (ids.hashCode);
(trashReason == null ? 0 : trashReason!.hashCode);
@override @override
String toString() => 'AssetBulkDeleteDto[force=$force, ids=$ids, trashReason=$trashReason]'; String toString() => 'AssetBulkDeleteDto[force=$force, ids=$ids]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -54,11 +49,6 @@ class AssetBulkDeleteDto {
// json[r'force'] = null; // json[r'force'] = null;
} }
json[r'ids'] = this.ids; json[r'ids'] = this.ids;
if (this.trashReason != null) {
json[r'trashReason'] = this.trashReason;
} else {
// json[r'trashReason'] = null;
}
return json; return json;
} }
@ -74,7 +64,6 @@ class AssetBulkDeleteDto {
ids: json[r'ids'] is Iterable ids: json[r'ids'] is Iterable
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false) ? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
: const [], : const [],
trashReason: AssetBulkDeleteDtoTrashReasonEnum.fromJson(json[r'trashReason']),
); );
} }
return null; return null;
@ -126,77 +115,3 @@ class AssetBulkDeleteDto {
}; };
} }
class AssetBulkDeleteDtoTrashReasonEnum {
/// Instantiate a new enum with the provided [value].
const AssetBulkDeleteDtoTrashReasonEnum._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const deleted = AssetBulkDeleteDtoTrashReasonEnum._(r'deleted');
static const offline = AssetBulkDeleteDtoTrashReasonEnum._(r'offline');
/// List of all possible values in this [enum][AssetBulkDeleteDtoTrashReasonEnum].
static const values = <AssetBulkDeleteDtoTrashReasonEnum>[
deleted,
offline,
];
static AssetBulkDeleteDtoTrashReasonEnum? fromJson(dynamic value) => AssetBulkDeleteDtoTrashReasonEnumTypeTransformer().decode(value);
static List<AssetBulkDeleteDtoTrashReasonEnum> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetBulkDeleteDtoTrashReasonEnum>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetBulkDeleteDtoTrashReasonEnum.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [AssetBulkDeleteDtoTrashReasonEnum] to String,
/// and [decode] dynamic data back to [AssetBulkDeleteDtoTrashReasonEnum].
class AssetBulkDeleteDtoTrashReasonEnumTypeTransformer {
factory AssetBulkDeleteDtoTrashReasonEnumTypeTransformer() => _instance ??= const AssetBulkDeleteDtoTrashReasonEnumTypeTransformer._();
const AssetBulkDeleteDtoTrashReasonEnumTypeTransformer._();
String encode(AssetBulkDeleteDtoTrashReasonEnum data) => data.value;
/// Decodes a [dynamic value][data] to a AssetBulkDeleteDtoTrashReasonEnum.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetBulkDeleteDtoTrashReasonEnum? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'deleted': return AssetBulkDeleteDtoTrashReasonEnum.deleted;
case r'offline': return AssetBulkDeleteDtoTrashReasonEnum.offline;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [AssetBulkDeleteDtoTrashReasonEnumTypeTransformer] instance.
static AssetBulkDeleteDtoTrashReasonEnumTypeTransformer? _instance;
}

View File

@ -25,6 +25,7 @@ class AssetResponseDto {
required this.id, required this.id,
required this.isArchived, required this.isArchived,
required this.isFavorite, required this.isFavorite,
required this.isOffline,
required this.isTrashed, required this.isTrashed,
this.libraryId, this.libraryId,
this.livePhotoVideoId, this.livePhotoVideoId,
@ -40,7 +41,6 @@ class AssetResponseDto {
this.stack, this.stack,
this.tags = const [], this.tags = const [],
required this.thumbhash, required this.thumbhash,
this.trashReason,
required this.type, required this.type,
this.unassignedFaces = const [], this.unassignedFaces = const [],
required this.updatedAt, required this.updatedAt,
@ -77,6 +77,8 @@ class AssetResponseDto {
bool isFavorite; bool isFavorite;
bool isOffline;
bool isTrashed; bool isTrashed;
/// This property was deprecated in v1.106.0 /// This property was deprecated in v1.106.0
@ -133,8 +135,6 @@ class AssetResponseDto {
String? thumbhash; String? thumbhash;
String? trashReason;
AssetTypeEnum type; AssetTypeEnum type;
List<AssetFaceWithoutPersonResponseDto> unassignedFaces; List<AssetFaceWithoutPersonResponseDto> unassignedFaces;
@ -155,6 +155,7 @@ class AssetResponseDto {
other.id == id && other.id == id &&
other.isArchived == isArchived && other.isArchived == isArchived &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.isOffline == isOffline &&
other.isTrashed == isTrashed && other.isTrashed == isTrashed &&
other.libraryId == libraryId && other.libraryId == libraryId &&
other.livePhotoVideoId == livePhotoVideoId && other.livePhotoVideoId == livePhotoVideoId &&
@ -170,7 +171,6 @@ class AssetResponseDto {
other.stack == stack && other.stack == stack &&
_deepEquality.equals(other.tags, tags) && _deepEquality.equals(other.tags, tags) &&
other.thumbhash == thumbhash && other.thumbhash == thumbhash &&
other.trashReason == trashReason &&
other.type == type && other.type == type &&
_deepEquality.equals(other.unassignedFaces, unassignedFaces) && _deepEquality.equals(other.unassignedFaces, unassignedFaces) &&
other.updatedAt == updatedAt; other.updatedAt == updatedAt;
@ -190,6 +190,7 @@ class AssetResponseDto {
(id.hashCode) + (id.hashCode) +
(isArchived.hashCode) + (isArchived.hashCode) +
(isFavorite.hashCode) + (isFavorite.hashCode) +
(isOffline.hashCode) +
(isTrashed.hashCode) + (isTrashed.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) + (libraryId == null ? 0 : libraryId!.hashCode) +
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
@ -205,13 +206,12 @@ class AssetResponseDto {
(stack == null ? 0 : stack!.hashCode) + (stack == null ? 0 : stack!.hashCode) +
(tags.hashCode) + (tags.hashCode) +
(thumbhash == null ? 0 : thumbhash!.hashCode) + (thumbhash == null ? 0 : thumbhash!.hashCode) +
(trashReason == null ? 0 : trashReason!.hashCode) +
(type.hashCode) + (type.hashCode) +
(unassignedFaces.hashCode) + (unassignedFaces.hashCode) +
(updatedAt.hashCode); (updatedAt.hashCode);
@override @override
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, tags=$tags, thumbhash=$thumbhash, trashReason=$trashReason, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt]'; String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -235,6 +235,7 @@ class AssetResponseDto {
json[r'id'] = this.id; json[r'id'] = this.id;
json[r'isArchived'] = this.isArchived; json[r'isArchived'] = this.isArchived;
json[r'isFavorite'] = this.isFavorite; json[r'isFavorite'] = this.isFavorite;
json[r'isOffline'] = this.isOffline;
json[r'isTrashed'] = this.isTrashed; json[r'isTrashed'] = this.isTrashed;
if (this.libraryId != null) { if (this.libraryId != null) {
json[r'libraryId'] = this.libraryId; json[r'libraryId'] = this.libraryId;
@ -281,11 +282,6 @@ class AssetResponseDto {
json[r'thumbhash'] = this.thumbhash; json[r'thumbhash'] = this.thumbhash;
} else { } else {
// json[r'thumbhash'] = null; // json[r'thumbhash'] = null;
}
if (this.trashReason != null) {
json[r'trashReason'] = this.trashReason;
} else {
// json[r'trashReason'] = null;
} }
json[r'type'] = this.type; json[r'type'] = this.type;
json[r'unassignedFaces'] = this.unassignedFaces; json[r'unassignedFaces'] = this.unassignedFaces;
@ -313,6 +309,7 @@ class AssetResponseDto {
id: mapValueOfType<String>(json, r'id')!, id: mapValueOfType<String>(json, r'id')!,
isArchived: mapValueOfType<bool>(json, r'isArchived')!, isArchived: mapValueOfType<bool>(json, r'isArchived')!,
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!, isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
isOffline: mapValueOfType<bool>(json, r'isOffline')!,
isTrashed: mapValueOfType<bool>(json, r'isTrashed')!, isTrashed: mapValueOfType<bool>(json, r'isTrashed')!,
libraryId: mapValueOfType<String>(json, r'libraryId'), libraryId: mapValueOfType<String>(json, r'libraryId'),
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'), livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
@ -328,7 +325,6 @@ class AssetResponseDto {
stack: AssetStackResponseDto.fromJson(json[r'stack']), stack: AssetStackResponseDto.fromJson(json[r'stack']),
tags: TagResponseDto.listFromJson(json[r'tags']), tags: TagResponseDto.listFromJson(json[r'tags']),
thumbhash: mapValueOfType<String>(json, r'thumbhash'), thumbhash: mapValueOfType<String>(json, r'thumbhash'),
trashReason: mapValueOfType<String>(json, r'trashReason'),
type: AssetTypeEnum.fromJson(json[r'type'])!, type: AssetTypeEnum.fromJson(json[r'type'])!,
unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']), unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']),
updatedAt: mapDateTime(json, r'updatedAt', r'')!, updatedAt: mapDateTime(json, r'updatedAt', r'')!,
@ -389,6 +385,7 @@ class AssetResponseDto {
'id', 'id',
'isArchived', 'isArchived',
'isFavorite', 'isFavorite',
'isOffline',
'isTrashed', 'isTrashed',
'localDateTime', 'localDateTime',
'originalFileName', 'originalFileName',

View File

@ -7832,13 +7832,6 @@
"type": "string" "type": "string"
}, },
"type": "array" "type": "array"
},
"trashReason": {
"enum": [
"deleted",
"offline"
],
"type": "string"
} }
}, },
"required": [ "required": [
@ -8376,6 +8369,9 @@
"isFavorite": { "isFavorite": {
"type": "boolean" "type": "boolean"
}, },
"isOffline": {
"type": "boolean"
},
"isTrashed": { "isTrashed": {
"type": "boolean" "type": "boolean"
}, },
@ -8440,10 +8436,6 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"trashReason": {
"nullable": true,
"type": "string"
},
"type": { "type": {
"$ref": "#/components/schemas/AssetTypeEnum" "$ref": "#/components/schemas/AssetTypeEnum"
}, },
@ -8469,6 +8461,7 @@
"id", "id",
"isArchived", "isArchived",
"isFavorite", "isFavorite",
"isOffline",
"isTrashed", "isTrashed",
"localDateTime", "localDateTime",
"originalFileName", "originalFileName",

View File

@ -253,6 +253,7 @@ export type AssetResponseDto = {
id: string; id: string;
isArchived: boolean; isArchived: boolean;
isFavorite: boolean; isFavorite: boolean;
isOffline: boolean;
isTrashed: boolean; isTrashed: boolean;
/** This property was deprecated in v1.106.0 */ /** This property was deprecated in v1.106.0 */
libraryId?: string | null; libraryId?: string | null;
@ -270,7 +271,6 @@ export type AssetResponseDto = {
stack?: (AssetStackResponseDto) | null; stack?: (AssetStackResponseDto) | null;
tags?: TagResponseDto[]; tags?: TagResponseDto[];
thumbhash: string | null; thumbhash: string | null;
trashReason?: string | null;
"type": AssetTypeEnum; "type": AssetTypeEnum;
unassignedFaces?: AssetFaceWithoutPersonResponseDto[]; unassignedFaces?: AssetFaceWithoutPersonResponseDto[];
updatedAt: string; updatedAt: string;
@ -356,7 +356,6 @@ export type ApiKeyUpdateDto = {
export type AssetBulkDeleteDto = { export type AssetBulkDeleteDto = {
force?: boolean; force?: boolean;
ids: string[]; ids: string[];
trashReason?: TrashReason;
}; };
export type AssetMediaCreateDto = { export type AssetMediaCreateDto = {
assetData: Blob; assetData: Blob;
@ -3350,10 +3349,6 @@ export enum Permission {
AdminUserUpdate = "admin.user.update", AdminUserUpdate = "admin.user.update",
AdminUserDelete = "admin.user.delete" AdminUserDelete = "admin.user.delete"
} }
export enum TrashReason {
Deleted = "deleted",
Offline = "offline"
}
export enum AssetMediaStatus { export enum AssetMediaStatus {
Created = "created", Created = "created",
Replaced = "replaced", Replaced = "replaced",

View File

@ -43,7 +43,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
isFavorite!: boolean; isFavorite!: boolean;
isArchived!: boolean; isArchived!: boolean;
isTrashed!: boolean; isTrashed!: boolean;
trashReason?: string | null; isOffline!: boolean;
exifInfo?: ExifResponseDto; exifInfo?: ExifResponseDto;
smartInfo?: SmartInfoResponseDto; smartInfo?: SmartInfoResponseDto;
tags?: TagResponseDto[]; tags?: TagResponseDto[];
@ -139,7 +139,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false, isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false,
isArchived: entity.isArchived, isArchived: entity.isArchived,
isTrashed: !!entity.deletedAt, isTrashed: !!entity.deletedAt,
trashReason: entity.trashReason, isOffline: entity.isOffline,
duration: entity.duration ?? '0:00:00.00000', duration: entity.duration ?? '0:00:00.00000',
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined, exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined, smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,

View File

@ -84,9 +84,6 @@ export class RandomAssetsDto {
export class AssetBulkDeleteDto extends BulkIdsDto { export class AssetBulkDeleteDto extends BulkIdsDto {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
force?: boolean; force?: boolean;
@Optional()
trashReason?: AssetTrashReason;
} }
export class AssetIdsDto { export class AssetIdsDto {
@ -94,11 +91,6 @@ export class AssetIdsDto {
assetIds!: string[]; assetIds!: string[];
} }
export enum AssetTrashReason {
DELETED = 'deleted',
OFFLINE = 'offline',
}
export enum AssetJobName { export enum AssetJobName {
REGENERATE_THUMBNAIL = 'regenerate-thumbnail', REGENERATE_THUMBNAIL = 'regenerate-thumbnail',
REFRESH_METADATA = 'refresh-metadata', REFRESH_METADATA = 'refresh-metadata',

View File

@ -1,4 +1,3 @@
import { AssetTrashReason } from 'src/dtos/asset.dto';
import { AlbumEntity } from 'src/entities/album.entity'; import { AlbumEntity } from 'src/entities/album.entity';
import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { AssetFileEntity } from 'src/entities/asset-files.entity'; import { AssetFileEntity } from 'src/entities/asset-files.entity';
@ -171,12 +170,8 @@ export class AssetEntity {
@OneToOne(() => AssetJobStatusEntity, (jobStatus) => jobStatus.asset, { nullable: true }) @OneToOne(() => AssetJobStatusEntity, (jobStatus) => jobStatus.asset, { nullable: true })
jobStatus?: AssetJobStatusEntity; jobStatus?: AssetJobStatusEntity;
@Column({ @Column({ type: 'boolean', default: false })
type: 'enum', isOffline!: boolean;
enum: AssetTrashReason,
nullable: true,
})
trashReason!: AssetTrashReason | null;
@Index('IDX_assets_duplicateId') @Index('IDX_assets_duplicateId')
@Column({ type: 'uuid', nullable: true }) @Column({ type: 'uuid', nullable: true })

View File

@ -146,8 +146,6 @@ export type AssetPathEntity = Pick<AssetEntity, 'id' | 'originalPath'>;
export const IAssetRepository = 'IAssetRepository'; export const IAssetRepository = 'IAssetRepository';
export interface IAssetRepository { export interface IAssetRepository {
getAssetsByOriginalPath(userId: string, partialPath: string): Promise<AssetEntity[]>;
getUniqueOriginalPaths(userId: string): Promise<string[]>;
create(asset: AssetCreate): Promise<AssetEntity>; create(asset: AssetCreate): Promise<AssetEntity>;
getByIds( getByIds(
ids: string[], ids: string[],
@ -156,13 +154,6 @@ export interface IAssetRepository {
): Promise<AssetEntity[]>; ): Promise<AssetEntity[]>;
getByIdsWithAllRelations(ids: string[]): Promise<AssetEntity[]>; getByIdsWithAllRelations(ids: string[]): Promise<AssetEntity[]>;
getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise<AssetEntity[]>; getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise<AssetEntity[]>;
getByIds(
ids: string[],
relations?: FindOptionsRelations<AssetEntity>,
select?: FindOptionsSelect<AssetEntity>,
): Promise<AssetEntity[]>;
getByIdsWithAllRelations(ids: string[]): Promise<AssetEntity[]>;
getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise<AssetEntity[]>;
getByChecksum(options: { ownerId: string; checksum: Buffer; libraryId?: string }): Promise<AssetEntity | null>; getByChecksum(options: { ownerId: string; checksum: Buffer; libraryId?: string }): Promise<AssetEntity | null>;
getByChecksums(userId: string, checksums: Buffer[]): Promise<AssetEntity[]>; getByChecksums(userId: string, checksums: Buffer[]): Promise<AssetEntity[]>;
getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined>; getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined>;
@ -181,15 +172,6 @@ export interface IAssetRepository {
libraryId?: string, libraryId?: string,
withDeleted?: boolean, withDeleted?: boolean,
): Paginated<AssetEntity>; ): Paginated<AssetEntity>;
getRandom(userId: string, count: number): Promise<AssetEntity[]>;
getFirstAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
getWith(
pagination: PaginationOptions,
property: WithProperty,
libraryId?: string,
withDeleted?: boolean,
): Paginated<AssetEntity>;
getRandom(userIds: string[], count: number): Promise<AssetEntity[]>; getRandom(userIds: string[], count: number): Promise<AssetEntity[]>;
getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | null>; getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
getExternalLibraryAssetPaths(pagination: PaginationOptions, libraryId: string): Paginated<AssetPathEntity>; getExternalLibraryAssetPaths(pagination: PaginationOptions, libraryId: string): Paginated<AssetPathEntity>;
@ -198,31 +180,12 @@ export interface IAssetRepository {
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>; getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>; getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
getLivePhotoCount(motionId: string): Promise<number>; getLivePhotoCount(motionId: string): Promise<number>;
getRandom(userId: string, count: number): Promise<AssetEntity[]>;
updateAll(ids: string[], options: Partial<AssetUpdateAllOptions>): Promise<void>; updateAll(ids: string[], options: Partial<AssetUpdateAllOptions>): Promise<void>;
updateDuplicates(options: AssetUpdateDuplicateOptions): Promise<void>; updateDuplicates(options: AssetUpdateDuplicateOptions): Promise<void>;
update(asset: AssetUpdateOptions): Promise<void>; update(asset: AssetUpdateOptions): Promise<void>;
remove(asset: AssetEntity): Promise<void>; remove(asset: AssetEntity): Promise<void>;
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>; findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>; getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
getUniqueOriginalPaths(userId: string): Promise<string[]>;
getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined>;
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
getWith(pagination: PaginationOptions, property: WithProperty, libraryId?: string): Paginated<AssetEntity>;
remove(asset: AssetEntity): Promise<void>;
restoreAll(ids: string[]): Promise<void>;
softDeleteAll(ids: string[]): Promise<void>;
update(asset: AssetUpdateOptions): Promise<void>;
updateAll(ids: string[], options: Partial<AssetUpdateAllOptions>): Promise<void>;
updateDuplicates(options: AssetUpdateDuplicateOptions): Promise<void>;
update(asset: AssetUpdateOptions): Promise<void>;
remove(asset: AssetEntity): Promise<void>;
softDeleteAll(ids: string[]): Promise<void>;
restoreAll(ids: string[]): Promise<void>;
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>; getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>; getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
upsertExif(exif: Partial<ExifEntity>): Promise<void>; upsertExif(exif: Partial<ExifEntity>): Promise<void>;
@ -233,6 +196,4 @@ export interface IAssetRepository {
getAllForUserFullSync(options: AssetFullSyncOptions): Promise<AssetEntity[]>; getAllForUserFullSync(options: AssetFullSyncOptions): Promise<AssetEntity[]>;
getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise<AssetEntity[]>; getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise<AssetEntity[]>;
upsertFile(options: { assetId: string; type: AssetFileType; path: string }): Promise<void>; upsertFile(options: { assetId: string; type: AssetFileType; path: string }): Promise<void>;
restoreAllDeleted(userId: string): Promise<void>;
restoreAllDeletedById(ids: string[]): Promise<void>;
} }

View File

@ -76,12 +76,12 @@ export enum JobName {
FACIAL_RECOGNITION = 'facial-recognition', FACIAL_RECOGNITION = 'facial-recognition',
// library management // library management
LIBRARY_QUEUE_SCAN = 'library-scan-new', LIBRARY_QUEUE_SYNC_FILES = 'library-scan-new',
LIBRARY_QUEUE_OFFLINE_CHECK = 'library-queue-remove-deleted', LIBRARY_QUEUE_SYNC_ASSETS = 'library-queue-remove-deleted',
LIBRARY_REFRESH_ASSET = 'library-refresh-asset', LIBRARY_SYNC_FILE = 'library-refresh-asset',
LIBRARY_OFFLINE_CHECK = 'library-remove-deleted', LIBRARY_SYNC_ASSET = 'library-remove-deleted',
LIBRARY_DELETE = 'library-delete', LIBRARY_DELETE = 'library-delete',
LIBRARY_QUEUE_SCAN_ALL = 'library-queue-all-refresh', LIBRARY_QUEUE_SYNC_ALL = 'library-queue-all-refresh',
LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup', LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup',
// cleanup // cleanup
@ -272,12 +272,12 @@ export type JobItem =
| { name: JobName.ASSET_DELETION_CHECK; data?: IBaseJob } | { name: JobName.ASSET_DELETION_CHECK; data?: IBaseJob }
// Library Management // Library Management
| { name: JobName.LIBRARY_REFRESH_ASSET; data: ILibraryFileJob } | { name: JobName.LIBRARY_SYNC_FILE; data: ILibraryFileJob }
| { name: JobName.LIBRARY_QUEUE_SCAN; data: IEntityJob } | { name: JobName.LIBRARY_QUEUE_SYNC_FILES; data: IEntityJob }
| { name: JobName.LIBRARY_QUEUE_OFFLINE_CHECK; data: IEntityJob } | { name: JobName.LIBRARY_QUEUE_SYNC_ASSETS; data: IEntityJob }
| { name: JobName.LIBRARY_OFFLINE_CHECK; data: IEntityJob } | { name: JobName.LIBRARY_SYNC_ASSET; data: IEntityJob }
| { name: JobName.LIBRARY_DELETE; data: IEntityJob } | { name: JobName.LIBRARY_DELETE; data: IEntityJob }
| { name: JobName.LIBRARY_QUEUE_SCAN_ALL; data?: IBaseJob } | { name: JobName.LIBRARY_QUEUE_SYNC_ALL; data?: IBaseJob }
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob } | { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }
// Notification // Notification

View File

@ -1,4 +1,3 @@
import { AssetTrashReason } from 'src/dtos/asset.dto';
import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { AssetEntity } from 'src/entities/asset.entity'; import { AssetEntity } from 'src/entities/asset.entity';
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
@ -58,13 +57,13 @@ export interface SearchStatusOptions {
isEncoded?: boolean; isEncoded?: boolean;
isFavorite?: boolean; isFavorite?: boolean;
isMotion?: boolean; isMotion?: boolean;
isOffline?: boolean;
isVisible?: boolean; isVisible?: boolean;
isNotInAlbum?: boolean; isNotInAlbum?: boolean;
type?: AssetType; type?: AssetType;
status?: AssetStatus; status?: AssetStatus;
withArchived?: boolean; withArchived?: boolean;
withDeleted?: boolean; withDeleted?: boolean;
trashReason?: AssetTrashReason;
} }
export interface SearchOneToOneRelationOptions { export interface SearchOneToOneRelationOptions {

View File

@ -1,14 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class RemoveOfflineField1725282595231 implements MigrationInterface {
name = 'RemoveOfflineField1725282595231'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "isOffline"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" ADD "isOffline" boolean NOT NULL DEFAULT false`);
}
}

View File

@ -1,15 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddTrashReason1725444664102 implements MigrationInterface {
name = 'AddTrashReason1725444664102';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TYPE "public"."assets_trashreason_enum" AS ENUM('deleted', 'offline')`);
await queryRunner.query(`ALTER TABLE "assets" ADD "trashReason" "public"."assets_trashreason_enum"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "trashReason"`);
await queryRunner.query(`DROP TYPE "public"."assets_trashreason_enum"`);
}
}

View File

@ -21,7 +21,6 @@ SELECT
"entity"."isFavorite" AS "entity_isFavorite", "entity"."isFavorite" AS "entity_isFavorite",
"entity"."isArchived" AS "entity_isArchived", "entity"."isArchived" AS "entity_isArchived",
"entity"."isExternal" AS "entity_isExternal", "entity"."isExternal" AS "entity_isExternal",
"entity"."isOffline" AS "entity_isOffline",
"entity"."checksum" AS "entity_checksum", "entity"."checksum" AS "entity_checksum",
"entity"."duration" AS "entity_duration", "entity"."duration" AS "entity_duration",
"entity"."isVisible" AS "entity_isVisible", "entity"."isVisible" AS "entity_isVisible",
@ -29,6 +28,7 @@ SELECT
"entity"."originalFileName" AS "entity_originalFileName", "entity"."originalFileName" AS "entity_originalFileName",
"entity"."sidecarPath" AS "entity_sidecarPath", "entity"."sidecarPath" AS "entity_sidecarPath",
"entity"."stackId" AS "entity_stackId", "entity"."stackId" AS "entity_stackId",
"entity"."isOffline" AS "entity_isOffline",
"entity"."duplicateId" AS "entity_duplicateId", "entity"."duplicateId" AS "entity_duplicateId",
"exifInfo"."assetId" AS "exifInfo_assetId", "exifInfo"."assetId" AS "exifInfo_assetId",
"exifInfo"."description" AS "exifInfo_description", "exifInfo"."description" AS "exifInfo_description",
@ -110,7 +110,6 @@ SELECT
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite", "AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
"AssetEntity"."isArchived" AS "AssetEntity_isArchived", "AssetEntity"."isArchived" AS "AssetEntity_isArchived",
"AssetEntity"."isExternal" AS "AssetEntity_isExternal", "AssetEntity"."isExternal" AS "AssetEntity_isExternal",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."checksum" AS "AssetEntity_checksum", "AssetEntity"."checksum" AS "AssetEntity_checksum",
"AssetEntity"."duration" AS "AssetEntity_duration", "AssetEntity"."duration" AS "AssetEntity_duration",
"AssetEntity"."isVisible" AS "AssetEntity_isVisible", "AssetEntity"."isVisible" AS "AssetEntity_isVisible",
@ -118,6 +117,7 @@ SELECT
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName", "AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath", "AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
"AssetEntity"."stackId" AS "AssetEntity_stackId", "AssetEntity"."stackId" AS "AssetEntity_stackId",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId" "AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
FROM FROM
"assets" "AssetEntity" "assets" "AssetEntity"
@ -145,7 +145,6 @@ SELECT
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite", "AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
"AssetEntity"."isArchived" AS "AssetEntity_isArchived", "AssetEntity"."isArchived" AS "AssetEntity_isArchived",
"AssetEntity"."isExternal" AS "AssetEntity_isExternal", "AssetEntity"."isExternal" AS "AssetEntity_isExternal",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."checksum" AS "AssetEntity_checksum", "AssetEntity"."checksum" AS "AssetEntity_checksum",
"AssetEntity"."duration" AS "AssetEntity_duration", "AssetEntity"."duration" AS "AssetEntity_duration",
"AssetEntity"."isVisible" AS "AssetEntity_isVisible", "AssetEntity"."isVisible" AS "AssetEntity_isVisible",
@ -153,6 +152,7 @@ SELECT
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName", "AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath", "AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
"AssetEntity"."stackId" AS "AssetEntity_stackId", "AssetEntity"."stackId" AS "AssetEntity_stackId",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId", "AssetEntity"."duplicateId" AS "AssetEntity_duplicateId",
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId", "AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description", "AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
@ -234,7 +234,6 @@ SELECT
"bd93d5747511a4dad4923546c51365bf1a803774"."isFavorite" AS "bd93d5747511a4dad4923546c51365bf1a803774_isFavorite", "bd93d5747511a4dad4923546c51365bf1a803774"."isFavorite" AS "bd93d5747511a4dad4923546c51365bf1a803774_isFavorite",
"bd93d5747511a4dad4923546c51365bf1a803774"."isArchived" AS "bd93d5747511a4dad4923546c51365bf1a803774_isArchived", "bd93d5747511a4dad4923546c51365bf1a803774"."isArchived" AS "bd93d5747511a4dad4923546c51365bf1a803774_isArchived",
"bd93d5747511a4dad4923546c51365bf1a803774"."isExternal" AS "bd93d5747511a4dad4923546c51365bf1a803774_isExternal", "bd93d5747511a4dad4923546c51365bf1a803774"."isExternal" AS "bd93d5747511a4dad4923546c51365bf1a803774_isExternal",
"bd93d5747511a4dad4923546c51365bf1a803774"."isOffline" AS "bd93d5747511a4dad4923546c51365bf1a803774_isOffline",
"bd93d5747511a4dad4923546c51365bf1a803774"."checksum" AS "bd93d5747511a4dad4923546c51365bf1a803774_checksum", "bd93d5747511a4dad4923546c51365bf1a803774"."checksum" AS "bd93d5747511a4dad4923546c51365bf1a803774_checksum",
"bd93d5747511a4dad4923546c51365bf1a803774"."duration" AS "bd93d5747511a4dad4923546c51365bf1a803774_duration", "bd93d5747511a4dad4923546c51365bf1a803774"."duration" AS "bd93d5747511a4dad4923546c51365bf1a803774_duration",
"bd93d5747511a4dad4923546c51365bf1a803774"."isVisible" AS "bd93d5747511a4dad4923546c51365bf1a803774_isVisible", "bd93d5747511a4dad4923546c51365bf1a803774"."isVisible" AS "bd93d5747511a4dad4923546c51365bf1a803774_isVisible",
@ -242,6 +241,7 @@ SELECT
"bd93d5747511a4dad4923546c51365bf1a803774"."originalFileName" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalFileName", "bd93d5747511a4dad4923546c51365bf1a803774"."originalFileName" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalFileName",
"bd93d5747511a4dad4923546c51365bf1a803774"."sidecarPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_sidecarPath", "bd93d5747511a4dad4923546c51365bf1a803774"."sidecarPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_sidecarPath",
"bd93d5747511a4dad4923546c51365bf1a803774"."stackId" AS "bd93d5747511a4dad4923546c51365bf1a803774_stackId", "bd93d5747511a4dad4923546c51365bf1a803774"."stackId" AS "bd93d5747511a4dad4923546c51365bf1a803774_stackId",
"bd93d5747511a4dad4923546c51365bf1a803774"."isOffline" AS "bd93d5747511a4dad4923546c51365bf1a803774_isOffline",
"bd93d5747511a4dad4923546c51365bf1a803774"."duplicateId" AS "bd93d5747511a4dad4923546c51365bf1a803774_duplicateId", "bd93d5747511a4dad4923546c51365bf1a803774"."duplicateId" AS "bd93d5747511a4dad4923546c51365bf1a803774_duplicateId",
"AssetEntity__AssetEntity_files"."id" AS "AssetEntity__AssetEntity_files_id", "AssetEntity__AssetEntity_files"."id" AS "AssetEntity__AssetEntity_files_id",
"AssetEntity__AssetEntity_files"."assetId" AS "AssetEntity__AssetEntity_files_assetId", "AssetEntity__AssetEntity_files"."assetId" AS "AssetEntity__AssetEntity_files_assetId",
@ -275,8 +275,7 @@ FROM
( (
SELECT SELECT
"AssetEntity"."id" AS "AssetEntity_id", "AssetEntity"."id" AS "AssetEntity_id",
"AssetEntity"."originalPath" AS "AssetEntity_originalPath", "AssetEntity"."originalPath" AS "AssetEntity_originalPath"
"AssetEntity"."isOffline" AS "AssetEntity_isOffline"
FROM FROM
"assets" "AssetEntity" "assets" "AssetEntity"
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId" LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
@ -322,7 +321,6 @@ FROM
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite", "AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
"AssetEntity"."isArchived" AS "AssetEntity_isArchived", "AssetEntity"."isArchived" AS "AssetEntity_isArchived",
"AssetEntity"."isExternal" AS "AssetEntity_isExternal", "AssetEntity"."isExternal" AS "AssetEntity_isExternal",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."checksum" AS "AssetEntity_checksum", "AssetEntity"."checksum" AS "AssetEntity_checksum",
"AssetEntity"."duration" AS "AssetEntity_duration", "AssetEntity"."duration" AS "AssetEntity_duration",
"AssetEntity"."isVisible" AS "AssetEntity_isVisible", "AssetEntity"."isVisible" AS "AssetEntity_isVisible",
@ -330,6 +328,7 @@ FROM
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName", "AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath", "AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
"AssetEntity"."stackId" AS "AssetEntity_stackId", "AssetEntity"."stackId" AS "AssetEntity_stackId",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId" "AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
FROM FROM
"assets" "AssetEntity" "assets" "AssetEntity"
@ -366,18 +365,6 @@ WHERE
AND "originalPath" = path AND "originalPath" = path
); );
-- AssetRepository.updateOfflineLibraryAssets
UPDATE "assets"
SET
"isOffline" = $1,
"updatedAt" = CURRENT_TIMESTAMP
WHERE
(
"libraryId" = $2
AND NOT ("originalPath" IN ($3))
AND "isOffline" = $4
)
-- AssetRepository.getAllByDeviceId -- AssetRepository.getAllByDeviceId
SELECT SELECT
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId", "AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
@ -420,7 +407,6 @@ SELECT
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite", "AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
"AssetEntity"."isArchived" AS "AssetEntity_isArchived", "AssetEntity"."isArchived" AS "AssetEntity_isArchived",
"AssetEntity"."isExternal" AS "AssetEntity_isExternal", "AssetEntity"."isExternal" AS "AssetEntity_isExternal",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."checksum" AS "AssetEntity_checksum", "AssetEntity"."checksum" AS "AssetEntity_checksum",
"AssetEntity"."duration" AS "AssetEntity_duration", "AssetEntity"."duration" AS "AssetEntity_duration",
"AssetEntity"."isVisible" AS "AssetEntity_isVisible", "AssetEntity"."isVisible" AS "AssetEntity_isVisible",
@ -428,6 +414,7 @@ SELECT
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName", "AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath", "AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
"AssetEntity"."stackId" AS "AssetEntity_stackId", "AssetEntity"."stackId" AS "AssetEntity_stackId",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId" "AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
FROM FROM
"assets" "AssetEntity" "assets" "AssetEntity"
@ -474,7 +461,6 @@ SELECT
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite", "AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
"AssetEntity"."isArchived" AS "AssetEntity_isArchived", "AssetEntity"."isArchived" AS "AssetEntity_isArchived",
"AssetEntity"."isExternal" AS "AssetEntity_isExternal", "AssetEntity"."isExternal" AS "AssetEntity_isExternal",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."checksum" AS "AssetEntity_checksum", "AssetEntity"."checksum" AS "AssetEntity_checksum",
"AssetEntity"."duration" AS "AssetEntity_duration", "AssetEntity"."duration" AS "AssetEntity_duration",
"AssetEntity"."isVisible" AS "AssetEntity_isVisible", "AssetEntity"."isVisible" AS "AssetEntity_isVisible",
@ -482,6 +468,7 @@ SELECT
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName", "AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath", "AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
"AssetEntity"."stackId" AS "AssetEntity_stackId", "AssetEntity"."stackId" AS "AssetEntity_stackId",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId" "AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
FROM FROM
"assets" "AssetEntity" "assets" "AssetEntity"
@ -547,7 +534,6 @@ SELECT
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite", "AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
"AssetEntity"."isArchived" AS "AssetEntity_isArchived", "AssetEntity"."isArchived" AS "AssetEntity_isArchived",
"AssetEntity"."isExternal" AS "AssetEntity_isExternal", "AssetEntity"."isExternal" AS "AssetEntity_isExternal",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."checksum" AS "AssetEntity_checksum", "AssetEntity"."checksum" AS "AssetEntity_checksum",
"AssetEntity"."duration" AS "AssetEntity_duration", "AssetEntity"."duration" AS "AssetEntity_duration",
"AssetEntity"."isVisible" AS "AssetEntity_isVisible", "AssetEntity"."isVisible" AS "AssetEntity_isVisible",
@ -555,6 +541,7 @@ SELECT
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName", "AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath", "AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
"AssetEntity"."stackId" AS "AssetEntity_stackId", "AssetEntity"."stackId" AS "AssetEntity_stackId",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId" "AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
FROM FROM
"assets" "AssetEntity" "assets" "AssetEntity"
@ -602,7 +589,6 @@ SELECT
"asset"."isFavorite" AS "asset_isFavorite", "asset"."isFavorite" AS "asset_isFavorite",
"asset"."isArchived" AS "asset_isArchived", "asset"."isArchived" AS "asset_isArchived",
"asset"."isExternal" AS "asset_isExternal", "asset"."isExternal" AS "asset_isExternal",
"asset"."isOffline" AS "asset_isOffline",
"asset"."checksum" AS "asset_checksum", "asset"."checksum" AS "asset_checksum",
"asset"."duration" AS "asset_duration", "asset"."duration" AS "asset_duration",
"asset"."isVisible" AS "asset_isVisible", "asset"."isVisible" AS "asset_isVisible",
@ -610,6 +596,7 @@ SELECT
"asset"."originalFileName" AS "asset_originalFileName", "asset"."originalFileName" AS "asset_originalFileName",
"asset"."sidecarPath" AS "asset_sidecarPath", "asset"."sidecarPath" AS "asset_sidecarPath",
"asset"."stackId" AS "asset_stackId", "asset"."stackId" AS "asset_stackId",
"asset"."isOffline" AS "asset_isOffline",
"asset"."duplicateId" AS "asset_duplicateId", "asset"."duplicateId" AS "asset_duplicateId",
"exifInfo"."assetId" AS "exifInfo_assetId", "exifInfo"."assetId" AS "exifInfo_assetId",
"exifInfo"."description" AS "exifInfo_description", "exifInfo"."description" AS "exifInfo_description",
@ -662,7 +649,6 @@ SELECT
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite", "stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
"stackedAssets"."isArchived" AS "stackedAssets_isArchived", "stackedAssets"."isArchived" AS "stackedAssets_isArchived",
"stackedAssets"."isExternal" AS "stackedAssets_isExternal", "stackedAssets"."isExternal" AS "stackedAssets_isExternal",
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
"stackedAssets"."checksum" AS "stackedAssets_checksum", "stackedAssets"."checksum" AS "stackedAssets_checksum",
"stackedAssets"."duration" AS "stackedAssets_duration", "stackedAssets"."duration" AS "stackedAssets_duration",
"stackedAssets"."isVisible" AS "stackedAssets_isVisible", "stackedAssets"."isVisible" AS "stackedAssets_isVisible",
@ -670,6 +656,7 @@ SELECT
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName", "stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath", "stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
"stackedAssets"."stackId" AS "stackedAssets_stackId", "stackedAssets"."stackId" AS "stackedAssets_stackId",
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId" "stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
FROM FROM
"assets" "asset" "assets" "asset"
@ -742,7 +729,6 @@ SELECT
"asset"."isFavorite" AS "asset_isFavorite", "asset"."isFavorite" AS "asset_isFavorite",
"asset"."isArchived" AS "asset_isArchived", "asset"."isArchived" AS "asset_isArchived",
"asset"."isExternal" AS "asset_isExternal", "asset"."isExternal" AS "asset_isExternal",
"asset"."isOffline" AS "asset_isOffline",
"asset"."checksum" AS "asset_checksum", "asset"."checksum" AS "asset_checksum",
"asset"."duration" AS "asset_duration", "asset"."duration" AS "asset_duration",
"asset"."isVisible" AS "asset_isVisible", "asset"."isVisible" AS "asset_isVisible",
@ -750,6 +736,7 @@ SELECT
"asset"."originalFileName" AS "asset_originalFileName", "asset"."originalFileName" AS "asset_originalFileName",
"asset"."sidecarPath" AS "asset_sidecarPath", "asset"."sidecarPath" AS "asset_sidecarPath",
"asset"."stackId" AS "asset_stackId", "asset"."stackId" AS "asset_stackId",
"asset"."isOffline" AS "asset_isOffline",
"asset"."duplicateId" AS "asset_duplicateId", "asset"."duplicateId" AS "asset_duplicateId",
"exifInfo"."assetId" AS "exifInfo_assetId", "exifInfo"."assetId" AS "exifInfo_assetId",
"exifInfo"."description" AS "exifInfo_description", "exifInfo"."description" AS "exifInfo_description",
@ -802,7 +789,6 @@ SELECT
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite", "stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
"stackedAssets"."isArchived" AS "stackedAssets_isArchived", "stackedAssets"."isArchived" AS "stackedAssets_isArchived",
"stackedAssets"."isExternal" AS "stackedAssets_isExternal", "stackedAssets"."isExternal" AS "stackedAssets_isExternal",
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
"stackedAssets"."checksum" AS "stackedAssets_checksum", "stackedAssets"."checksum" AS "stackedAssets_checksum",
"stackedAssets"."duration" AS "stackedAssets_duration", "stackedAssets"."duration" AS "stackedAssets_duration",
"stackedAssets"."isVisible" AS "stackedAssets_isVisible", "stackedAssets"."isVisible" AS "stackedAssets_isVisible",
@ -810,6 +796,7 @@ SELECT
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName", "stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath", "stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
"stackedAssets"."stackId" AS "stackedAssets_stackId", "stackedAssets"."stackId" AS "stackedAssets_stackId",
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId" "stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
FROM FROM
"assets" "asset" "assets" "asset"
@ -858,7 +845,6 @@ SELECT
"asset"."isFavorite" AS "asset_isFavorite", "asset"."isFavorite" AS "asset_isFavorite",
"asset"."isArchived" AS "asset_isArchived", "asset"."isArchived" AS "asset_isArchived",
"asset"."isExternal" AS "asset_isExternal", "asset"."isExternal" AS "asset_isExternal",
"asset"."isOffline" AS "asset_isOffline",
"asset"."checksum" AS "asset_checksum", "asset"."checksum" AS "asset_checksum",
"asset"."duration" AS "asset_duration", "asset"."duration" AS "asset_duration",
"asset"."isVisible" AS "asset_isVisible", "asset"."isVisible" AS "asset_isVisible",
@ -866,6 +852,7 @@ SELECT
"asset"."originalFileName" AS "asset_originalFileName", "asset"."originalFileName" AS "asset_originalFileName",
"asset"."sidecarPath" AS "asset_sidecarPath", "asset"."sidecarPath" AS "asset_sidecarPath",
"asset"."stackId" AS "asset_stackId", "asset"."stackId" AS "asset_stackId",
"asset"."isOffline" AS "asset_isOffline",
"asset"."duplicateId" AS "asset_duplicateId", "asset"."duplicateId" AS "asset_duplicateId",
"exifInfo"."assetId" AS "exifInfo_assetId", "exifInfo"."assetId" AS "exifInfo_assetId",
"exifInfo"."description" AS "exifInfo_description", "exifInfo"."description" AS "exifInfo_description",
@ -918,7 +905,6 @@ SELECT
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite", "stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
"stackedAssets"."isArchived" AS "stackedAssets_isArchived", "stackedAssets"."isArchived" AS "stackedAssets_isArchived",
"stackedAssets"."isExternal" AS "stackedAssets_isExternal", "stackedAssets"."isExternal" AS "stackedAssets_isExternal",
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
"stackedAssets"."checksum" AS "stackedAssets_checksum", "stackedAssets"."checksum" AS "stackedAssets_checksum",
"stackedAssets"."duration" AS "stackedAssets_duration", "stackedAssets"."duration" AS "stackedAssets_duration",
"stackedAssets"."isVisible" AS "stackedAssets_isVisible", "stackedAssets"."isVisible" AS "stackedAssets_isVisible",
@ -926,6 +912,7 @@ SELECT
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName", "stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath", "stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
"stackedAssets"."stackId" AS "stackedAssets_stackId", "stackedAssets"."stackId" AS "stackedAssets_stackId",
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId" "stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
FROM FROM
"assets" "asset" "assets" "asset"
@ -1024,7 +1011,6 @@ SELECT
"asset"."isFavorite" AS "asset_isFavorite", "asset"."isFavorite" AS "asset_isFavorite",
"asset"."isArchived" AS "asset_isArchived", "asset"."isArchived" AS "asset_isArchived",
"asset"."isExternal" AS "asset_isExternal", "asset"."isExternal" AS "asset_isExternal",
"asset"."isOffline" AS "asset_isOffline",
"asset"."checksum" AS "asset_checksum", "asset"."checksum" AS "asset_checksum",
"asset"."duration" AS "asset_duration", "asset"."duration" AS "asset_duration",
"asset"."isVisible" AS "asset_isVisible", "asset"."isVisible" AS "asset_isVisible",
@ -1032,6 +1018,7 @@ SELECT
"asset"."originalFileName" AS "asset_originalFileName", "asset"."originalFileName" AS "asset_originalFileName",
"asset"."sidecarPath" AS "asset_sidecarPath", "asset"."sidecarPath" AS "asset_sidecarPath",
"asset"."stackId" AS "asset_stackId", "asset"."stackId" AS "asset_stackId",
"asset"."isOffline" AS "asset_isOffline",
"asset"."duplicateId" AS "asset_duplicateId", "asset"."duplicateId" AS "asset_duplicateId",
"exifInfo"."assetId" AS "exifInfo_assetId", "exifInfo"."assetId" AS "exifInfo_assetId",
"exifInfo"."description" AS "exifInfo_description", "exifInfo"."description" AS "exifInfo_description",
@ -1100,7 +1087,6 @@ SELECT
"asset"."isFavorite" AS "asset_isFavorite", "asset"."isFavorite" AS "asset_isFavorite",
"asset"."isArchived" AS "asset_isArchived", "asset"."isArchived" AS "asset_isArchived",
"asset"."isExternal" AS "asset_isExternal", "asset"."isExternal" AS "asset_isExternal",
"asset"."isOffline" AS "asset_isOffline",
"asset"."checksum" AS "asset_checksum", "asset"."checksum" AS "asset_checksum",
"asset"."duration" AS "asset_duration", "asset"."duration" AS "asset_duration",
"asset"."isVisible" AS "asset_isVisible", "asset"."isVisible" AS "asset_isVisible",
@ -1108,6 +1094,7 @@ SELECT
"asset"."originalFileName" AS "asset_originalFileName", "asset"."originalFileName" AS "asset_originalFileName",
"asset"."sidecarPath" AS "asset_sidecarPath", "asset"."sidecarPath" AS "asset_sidecarPath",
"asset"."stackId" AS "asset_stackId", "asset"."stackId" AS "asset_stackId",
"asset"."isOffline" AS "asset_isOffline",
"asset"."duplicateId" AS "asset_duplicateId", "asset"."duplicateId" AS "asset_duplicateId",
"exifInfo"."assetId" AS "exifInfo_assetId", "exifInfo"."assetId" AS "exifInfo_assetId",
"exifInfo"."description" AS "exifInfo_description", "exifInfo"."description" AS "exifInfo_description",
@ -1173,15 +1160,3 @@ RETURNING
"id", "id",
"createdAt", "createdAt",
"updatedAt" "updatedAt"
-- AssetRepository.restoreAllDeleted
UPDATE "assets"
SET
"deletedAt" = $1,
"trashReason" = $2,
"updatedAt" = CURRENT_TIMESTAMP
WHERE
(
"ownerId" = $3
AND "trashReason" = $4
)

View File

@ -179,6 +179,7 @@ FROM
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName", "AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath", "AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
"AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId", "AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId",
"AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
"AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId" "AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId"
FROM FROM
"asset_faces" "AssetFaceEntity" "asset_faces" "AssetFaceEntity"
@ -280,6 +281,7 @@ FROM
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName", "AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath", "AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
"AssetEntity"."stackId" AS "AssetEntity_stackId", "AssetEntity"."stackId" AS "AssetEntity_stackId",
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId", "AssetEntity"."duplicateId" AS "AssetEntity_duplicateId",
"AssetEntity__AssetEntity_faces"."id" AS "AssetEntity__AssetEntity_faces_id", "AssetEntity__AssetEntity_faces"."id" AS "AssetEntity__AssetEntity_faces_id",
"AssetEntity__AssetEntity_faces"."assetId" AS "AssetEntity__AssetEntity_faces_assetId", "AssetEntity__AssetEntity_faces"."assetId" AS "AssetEntity__AssetEntity_faces_assetId",
@ -411,6 +413,7 @@ SELECT
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName", "AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath", "AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
"AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId", "AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId",
"AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
"AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId" "AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId"
FROM FROM
"asset_faces" "AssetFaceEntity" "asset_faces" "AssetFaceEntity"

View File

@ -33,6 +33,7 @@ FROM
"asset"."originalFileName" AS "asset_originalFileName", "asset"."originalFileName" AS "asset_originalFileName",
"asset"."sidecarPath" AS "asset_sidecarPath", "asset"."sidecarPath" AS "asset_sidecarPath",
"asset"."stackId" AS "asset_stackId", "asset"."stackId" AS "asset_stackId",
"asset"."isOffline" AS "asset_isOffline",
"asset"."duplicateId" AS "asset_duplicateId", "asset"."duplicateId" AS "asset_duplicateId",
"stack"."id" AS "stack_id", "stack"."id" AS "stack_id",
"stack"."ownerId" AS "stack_ownerId", "stack"."ownerId" AS "stack_ownerId",
@ -63,6 +64,7 @@ FROM
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName", "stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath", "stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
"stackedAssets"."stackId" AS "stackedAssets_stackId", "stackedAssets"."stackId" AS "stackedAssets_stackId",
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId" "stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
FROM FROM
"assets" "asset" "assets" "asset"
@ -126,6 +128,7 @@ SELECT
"asset"."originalFileName" AS "asset_originalFileName", "asset"."originalFileName" AS "asset_originalFileName",
"asset"."sidecarPath" AS "asset_sidecarPath", "asset"."sidecarPath" AS "asset_sidecarPath",
"asset"."stackId" AS "asset_stackId", "asset"."stackId" AS "asset_stackId",
"asset"."isOffline" AS "asset_isOffline",
"asset"."duplicateId" AS "asset_duplicateId", "asset"."duplicateId" AS "asset_duplicateId",
"stack"."id" AS "stack_id", "stack"."id" AS "stack_id",
"stack"."ownerId" AS "stack_ownerId", "stack"."ownerId" AS "stack_ownerId",
@ -156,6 +159,7 @@ SELECT
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName", "stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath", "stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
"stackedAssets"."stackId" AS "stackedAssets_stackId", "stackedAssets"."stackId" AS "stackedAssets_stackId",
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId" "stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
FROM FROM
"assets" "asset" "assets" "asset"
@ -365,6 +369,7 @@ SELECT
"asset"."originalFileName" AS "asset_originalFileName", "asset"."originalFileName" AS "asset_originalFileName",
"asset"."sidecarPath" AS "asset_sidecarPath", "asset"."sidecarPath" AS "asset_sidecarPath",
"asset"."stackId" AS "asset_stackId", "asset"."stackId" AS "asset_stackId",
"asset"."isOffline" AS "asset_isOffline",
"asset"."duplicateId" AS "asset_duplicateId", "asset"."duplicateId" AS "asset_duplicateId",
"exif"."assetId" AS "exif_assetId", "exif"."assetId" AS "exif_assetId",
"exif"."description" AS "exif_description", "exif"."description" AS "exif_description",

View File

@ -47,6 +47,7 @@ FROM
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName", "SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath", "SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
"SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId", "SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId",
"SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline",
"SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId", "SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId",
"9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_assetId", "9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_assetId",
"9b1d35b344d838023994a3233afd6ffe098be6d8"."description" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_description", "9b1d35b344d838023994a3233afd6ffe098be6d8"."description" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_description",
@ -113,6 +114,7 @@ FROM
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalFileName" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalFileName", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalFileName" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalFileName",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."sidecarPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_sidecarPath", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."sidecarPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_sidecarPath",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."stackId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_stackId", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."stackId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_stackId",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isOffline" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isOffline",
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."duplicateId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_duplicateId", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."duplicateId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_duplicateId",
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_assetId", "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_assetId",
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."description" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_description", "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."description" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_description",
@ -234,6 +236,7 @@ SELECT
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName", "SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath", "SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
"SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId", "SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId",
"SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline",
"SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId", "SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId",
"SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id", "SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id",
"SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId", "SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId",

View File

@ -21,7 +21,6 @@ SELECT
"asset"."isFavorite" AS "asset_isFavorite", "asset"."isFavorite" AS "asset_isFavorite",
"asset"."isArchived" AS "asset_isArchived", "asset"."isArchived" AS "asset_isArchived",
"asset"."isExternal" AS "asset_isExternal", "asset"."isExternal" AS "asset_isExternal",
"asset"."isOffline" AS "asset_isOffline",
"asset"."checksum" AS "asset_checksum", "asset"."checksum" AS "asset_checksum",
"asset"."duration" AS "asset_duration", "asset"."duration" AS "asset_duration",
"asset"."isVisible" AS "asset_isVisible", "asset"."isVisible" AS "asset_isVisible",
@ -29,6 +28,7 @@ SELECT
"asset"."originalFileName" AS "asset_originalFileName", "asset"."originalFileName" AS "asset_originalFileName",
"asset"."sidecarPath" AS "asset_sidecarPath", "asset"."sidecarPath" AS "asset_sidecarPath",
"asset"."stackId" AS "asset_stackId", "asset"."stackId" AS "asset_stackId",
"asset"."isOffline" AS "asset_isOffline",
"asset"."duplicateId" AS "asset_duplicateId", "asset"."duplicateId" AS "asset_duplicateId",
"exifInfo"."assetId" AS "exifInfo_assetId", "exifInfo"."assetId" AS "exifInfo_assetId",
"exifInfo"."description" AS "exifInfo_description", "exifInfo"."description" AS "exifInfo_description",

View File

@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
import { AssetTrashReason } from 'src/dtos/asset.dto';
import { AssetFileEntity } from 'src/entities/asset-files.entity'; import { AssetFileEntity } from 'src/entities/asset-files.entity';
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity'; import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
import { AssetEntity } from 'src/entities/asset.entity'; import { AssetEntity } from 'src/entities/asset.entity';
@ -199,10 +198,11 @@ export class AssetRepository implements IAssetRepository {
async getPathsNotInLibrary(libraryId: string, originalPaths: string[]): Promise<string[]> { async getPathsNotInLibrary(libraryId: string, originalPaths: string[]): Promise<string[]> {
const result = await this.repository.query( const result = await this.repository.query(
` `
WITH paths AS (SELECT unnest($2::text[]) AS path) WITH paths AS (SELECT unnest($2::text[]) AS path)
SELECT path FROM paths SELECT path
WHERE NOT EXISTS (SELECT 1 FROM assets WHERE "libraryId" = $1 AND "originalPath" = path); FROM paths
`, WHERE NOT EXISTS (SELECT 1 FROM assets WHERE "libraryId" = $1 AND "originalPath" = path);
`,
[libraryId, originalPaths], [libraryId, originalPaths],
); );
return result.map((row: { path: string }) => row.path); return result.map((row: { path: string }) => row.path);
@ -287,16 +287,6 @@ export class AssetRepository implements IAssetRepository {
.execute(); .execute();
} }
@Chunked()
async softDeleteAll(ids: string[]): Promise<void> {
await this.repository.softDelete({ id: In(ids) });
}
@Chunked()
async restoreAll(ids: string[]): Promise<void> {
await this.updateAll(ids, { trashReason: null, deletedAt: null });
}
async update(asset: AssetUpdateOptions): Promise<void> { async update(asset: AssetUpdateOptions): Promise<void> {
await this.repository.update(asset.id, asset); await this.repository.update(asset.id, asset);
} }
@ -820,32 +810,4 @@ export class AssetRepository implements IAssetRepository {
async upsertFile({ assetId, type, path }: { assetId: string; type: AssetFileType; path: string }): Promise<void> { async upsertFile({ assetId, type, path }: { assetId: string; type: AssetFileType; path: string }): Promise<void> {
await this.fileRepository.upsert({ assetId, type, path }, { conflictPaths: ['assetId', 'type'] }); await this.fileRepository.upsert({ assetId, type, path }, { conflictPaths: ['assetId', 'type'] });
} }
@GenerateSql({
params: [
{
ownerId: DummyValue.UUID,
},
],
})
async restoreAllDeleted(ownerId?: string): Promise<void> {
await this.repository.update(
{ ownerId, trashReason: AssetTrashReason.DELETED },
{ deletedAt: null, trashReason: null },
);
}
@GenerateSql({
params: [
{
ownerId: DummyValue.UUID,
},
],
})
async restoreAllDeletedById(ids: string[]): Promise<void> {
await this.repository.update(
{ id: In(ids), trashReason: AssetTrashReason.DELETED },
{ deletedAt: null, trashReason: null },
);
}
} }

View File

@ -79,12 +79,12 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
[JobName.SIDECAR_WRITE]: QueueName.SIDECAR, [JobName.SIDECAR_WRITE]: QueueName.SIDECAR,
// Library management // Library management
[JobName.LIBRARY_REFRESH_ASSET]: QueueName.LIBRARY, [JobName.LIBRARY_SYNC_FILE]: QueueName.LIBRARY,
[JobName.LIBRARY_QUEUE_SCAN]: QueueName.LIBRARY, [JobName.LIBRARY_QUEUE_SYNC_FILES]: QueueName.LIBRARY,
[JobName.LIBRARY_QUEUE_OFFLINE_CHECK]: QueueName.LIBRARY, [JobName.LIBRARY_QUEUE_SYNC_ASSETS]: QueueName.LIBRARY,
[JobName.LIBRARY_DELETE]: QueueName.LIBRARY, [JobName.LIBRARY_DELETE]: QueueName.LIBRARY,
[JobName.LIBRARY_OFFLINE_CHECK]: QueueName.LIBRARY, [JobName.LIBRARY_SYNC_ASSET]: QueueName.LIBRARY,
[JobName.LIBRARY_QUEUE_SCAN_ALL]: QueueName.LIBRARY, [JobName.LIBRARY_QUEUE_SYNC_ALL]: QueueName.LIBRARY,
[JobName.LIBRARY_QUEUE_CLEANUP]: QueueName.LIBRARY, [JobName.LIBRARY_QUEUE_CLEANUP]: QueueName.LIBRARY,
// Notification // Notification

View File

@ -300,7 +300,6 @@ export class AssetService {
async deleteAll(auth: AuthDto, dto: AssetBulkDeleteDto): Promise<void> { async deleteAll(auth: AuthDto, dto: AssetBulkDeleteDto): Promise<void> {
const { ids, force } = dto; const { ids, force } = dto;
let { trashReason } = dto;
await requireAccess(this.access, { auth, permission: Permission.ASSET_DELETE, ids }); await requireAccess(this.access, { auth, permission: Permission.ASSET_DELETE, ids });
await this.assetRepository.updateAll(ids, { await this.assetRepository.updateAll(ids, {

View File

@ -164,7 +164,7 @@ export class JobService {
} }
case QueueName.LIBRARY: { case QueueName.LIBRARY: {
return this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SCAN_ALL, data: { force } }); return this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ALL, data: { force } });
} }
default: { default: {

View File

@ -2,7 +2,6 @@ import { BadRequestException } from '@nestjs/common';
import { Stats } from 'node:fs'; import { Stats } from 'node:fs';
import { SystemConfig } from 'src/config'; import { SystemConfig } from 'src/config';
import { SystemConfigCore } from 'src/cores/system-config.core'; import { SystemConfigCore } from 'src/cores/system-config.core';
import { AssetTrashReason } from 'src/dtos/asset.dto';
import { mapLibrary } from 'src/dtos/library.dto'; import { mapLibrary } from 'src/dtos/library.dto';
import { UserEntity } from 'src/entities/user.entity'; import { UserEntity } from 'src/entities/user.entity';
import { AssetType } from 'src/enum'; import { AssetType } from 'src/enum';
@ -172,11 +171,11 @@ describe(LibraryService.name, () => {
}); });
assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false }); assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false });
await sut.handleQueueAssetRefresh({ id: libraryStub.externalLibrary1.id }); await sut.handleQueueSyncFiles({ id: libraryStub.externalLibrary1.id });
expect(jobMock.queueAll).toHaveBeenCalledWith([ expect(jobMock.queueAll).toHaveBeenCalledWith([
{ {
name: JobName.LIBRARY_REFRESH_ASSET, name: JobName.LIBRARY_SYNC_FILE,
data: { data: {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: libraryStub.externalLibrary1.owner.id, ownerId: libraryStub.externalLibrary1.owner.id,
@ -189,9 +188,7 @@ describe(LibraryService.name, () => {
it("should fail when library can't be found", async () => { it("should fail when library can't be found", async () => {
libraryMock.get.mockResolvedValue(null); libraryMock.get.mockResolvedValue(null);
await expect(sut.handleQueueAssetRefresh({ id: libraryStub.externalLibrary1.id })).resolves.toBe( await expect(sut.handleQueueSyncFiles({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED);
JobStatus.SKIPPED,
);
}); });
it('should ignore import paths that do not exist', async () => { it('should ignore import paths that do not exist', async () => {
@ -212,7 +209,7 @@ describe(LibraryService.name, () => {
libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false }); assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false });
await sut.handleQueueAssetRefresh({ id: libraryStub.externalLibraryWithImportPaths1.id }); await sut.handleQueueSyncFiles({ id: libraryStub.externalLibraryWithImportPaths1.id });
expect(storageMock.walk).toHaveBeenCalledWith({ expect(storageMock.walk).toHaveBeenCalledWith({
pathsToCrawl: [libraryStub.externalLibraryWithImportPaths1.importPaths[1]], pathsToCrawl: [libraryStub.externalLibraryWithImportPaths1.importPaths[1]],
@ -229,11 +226,11 @@ describe(LibraryService.name, () => {
storageMock.walk.mockImplementation(async function* generator() {}); storageMock.walk.mockImplementation(async function* generator() {});
assetMock.getAll.mockResolvedValue({ items: [assetStub.external], hasNextPage: false }); assetMock.getAll.mockResolvedValue({ items: [assetStub.external], hasNextPage: false });
await sut.handleQueueAssetOfflineCheck({ id: libraryStub.externalLibrary1.id }); await sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id });
expect(jobMock.queueAll).toHaveBeenCalledWith([ expect(jobMock.queueAll).toHaveBeenCalledWith([
{ {
name: JobName.LIBRARY_OFFLINE_CHECK, name: JobName.LIBRARY_SYNC_ASSET,
data: { data: {
id: assetStub.external.id, id: assetStub.external.id,
importPaths: libraryStub.externalLibrary1.importPaths, importPaths: libraryStub.externalLibrary1.importPaths,
@ -246,9 +243,7 @@ describe(LibraryService.name, () => {
it("should fail when library can't be found", async () => { it("should fail when library can't be found", async () => {
libraryMock.get.mockResolvedValue(null); libraryMock.get.mockResolvedValue(null);
await expect(sut.handleQueueAssetOfflineCheck({ id: libraryStub.externalLibrary1.id })).resolves.toBe( await expect(sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED);
JobStatus.SKIPPED,
);
}); });
}); });
@ -262,7 +257,7 @@ describe(LibraryService.name, () => {
assetMock.getById.mockResolvedValue(null); assetMock.getById.mockResolvedValue(null);
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SKIPPED); await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SKIPPED);
expect(assetMock.remove).not.toHaveBeenCalled(); expect(assetMock.remove).not.toHaveBeenCalled();
}); });
@ -276,10 +271,10 @@ describe(LibraryService.name, () => {
assetMock.getById.mockResolvedValue(assetStub.external); assetMock.getById.mockResolvedValue(assetStub.external);
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], { expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
trashReason: AssetTrashReason.OFFLINE, isOffline: true,
}); });
expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]); expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]);
}); });
@ -293,9 +288,9 @@ describe(LibraryService.name, () => {
assetMock.getById.mockResolvedValue(assetStub.external); assetMock.getById.mockResolvedValue(assetStub.external);
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], { expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
trashReason: AssetTrashReason.OFFLINE, isOffline: true,
}); });
expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]); expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]);
}); });
@ -310,10 +305,10 @@ describe(LibraryService.name, () => {
assetMock.getById.mockResolvedValue(assetStub.external); assetMock.getById.mockResolvedValue(assetStub.external);
storageMock.checkFileExists.mockResolvedValue(true); storageMock.checkFileExists.mockResolvedValue(true);
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], { expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
trashReason: AssetTrashReason.OFFLINE, isOffline: true,
}); });
expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]); expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]);
}); });
@ -328,7 +323,7 @@ describe(LibraryService.name, () => {
assetMock.getById.mockResolvedValue(assetStub.external); assetMock.getById.mockResolvedValue(assetStub.external);
storageMock.checkFileExists.mockResolvedValue(true); storageMock.checkFileExists.mockResolvedValue(true);
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.remove).not.toHaveBeenCalled(); expect(assetMock.remove).not.toHaveBeenCalled();
}); });
@ -356,7 +351,7 @@ describe(LibraryService.name, () => {
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null); assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
await expect(sut.handleAssetRefresh(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException); await expect(sut.handleSyncFile(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException);
}); });
it('should reject an unknown file type', async () => { it('should reject an unknown file type', async () => {
@ -366,7 +361,7 @@ describe(LibraryService.name, () => {
assetPath: '/data/user1/file.xyz', assetPath: '/data/user1/file.xyz',
}; };
await expect(sut.handleAssetRefresh(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException); await expect(sut.handleSyncFile(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException);
}); });
it('should import a new asset', async () => { it('should import a new asset', async () => {
@ -379,7 +374,7 @@ describe(LibraryService.name, () => {
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null); assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
assetMock.create.mockResolvedValue(assetStub.image); assetMock.create.mockResolvedValue(assetStub.image);
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.create.mock.calls).toEqual([ expect(assetMock.create.mock.calls).toEqual([
[ [
@ -425,7 +420,7 @@ describe(LibraryService.name, () => {
assetMock.create.mockResolvedValue(assetStub.image); assetMock.create.mockResolvedValue(assetStub.image);
storageMock.checkFileExists.mockResolvedValue(true); storageMock.checkFileExists.mockResolvedValue(true);
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.create.mock.calls).toEqual([ expect(assetMock.create.mock.calls).toEqual([
[ [
@ -470,7 +465,7 @@ describe(LibraryService.name, () => {
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null); assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
assetMock.create.mockResolvedValue(assetStub.video); assetMock.create.mockResolvedValue(assetStub.video);
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.create.mock.calls).toEqual([ expect(assetMock.create.mock.calls).toEqual([
[ [
@ -524,7 +519,7 @@ describe(LibraryService.name, () => {
assetMock.create.mockResolvedValue(assetStub.image); assetMock.create.mockResolvedValue(assetStub.image);
libraryMock.get.mockResolvedValue({ ...libraryStub.externalLibrary1, deletedAt: new Date() }); libraryMock.get.mockResolvedValue({ ...libraryStub.externalLibrary1, deletedAt: new Date() });
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.FAILED); await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.FAILED);
expect(assetMock.create.mock.calls).toEqual([]); expect(assetMock.create.mock.calls).toEqual([]);
}); });
@ -544,7 +539,7 @@ describe(LibraryService.name, () => {
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.hasFileExtension); assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.hasFileExtension);
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED); await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED);
expect(jobMock.queue).not.toHaveBeenCalled(); expect(jobMock.queue).not.toHaveBeenCalled();
expect(jobMock.queueAll).not.toHaveBeenCalled(); expect(jobMock.queueAll).not.toHaveBeenCalled();
@ -560,7 +555,7 @@ describe(LibraryService.name, () => {
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image);
assetMock.create.mockResolvedValue(assetStub.image); assetMock.create.mockResolvedValue(assetStub.image);
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.METADATA_EXTRACTION, name: JobName.METADATA_EXTRACTION,
@ -587,7 +582,7 @@ describe(LibraryService.name, () => {
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.trashed); assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.trashed);
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED); await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED);
expect(jobMock.queue).not.toHaveBeenCalled(); expect(jobMock.queue).not.toHaveBeenCalled();
expect(jobMock.queueAll).not.toHaveBeenCalled(); expect(jobMock.queueAll).not.toHaveBeenCalled();
@ -602,7 +597,7 @@ describe(LibraryService.name, () => {
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.trashedOffline); assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.trashedOffline);
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.restoreAll).toHaveBeenCalledWith([assetStub.trashedOffline.id]); expect(assetMock.restoreAll).toHaveBeenCalledWith([assetStub.trashedOffline.id]);
}); });
@ -619,7 +614,7 @@ describe(LibraryService.name, () => {
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null); assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
assetMock.create.mockResolvedValue(assetStub.image); assetMock.create.mockResolvedValue(assetStub.image);
await expect(sut.handleAssetRefresh(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException); await expect(sut.handleSyncFile(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException);
}); });
}); });
@ -926,7 +921,7 @@ describe(LibraryService.name, () => {
expect(jobMock.queueAll).toHaveBeenCalledWith([ expect(jobMock.queueAll).toHaveBeenCalledWith([
{ {
name: JobName.LIBRARY_REFRESH_ASSET, name: JobName.LIBRARY_SYNC_FILE,
data: { data: {
id: libraryStub.externalLibraryWithImportPaths1.id, id: libraryStub.externalLibraryWithImportPaths1.id,
assetPath: '/foo/photo.jpg', assetPath: '/foo/photo.jpg',
@ -947,7 +942,7 @@ describe(LibraryService.name, () => {
expect(jobMock.queueAll).toHaveBeenCalledWith([ expect(jobMock.queueAll).toHaveBeenCalledWith([
{ {
name: JobName.LIBRARY_REFRESH_ASSET, name: JobName.LIBRARY_SYNC_FILE,
data: { data: {
id: libraryStub.externalLibraryWithImportPaths1.id, id: libraryStub.externalLibraryWithImportPaths1.id,
assetPath: '/foo/photo.jpg', assetPath: '/foo/photo.jpg',
@ -1059,7 +1054,7 @@ describe(LibraryService.name, () => {
expect(jobMock.queue.mock.calls).toEqual([ expect(jobMock.queue.mock.calls).toEqual([
[ [
{ {
name: JobName.LIBRARY_QUEUE_SCAN, name: JobName.LIBRARY_QUEUE_SYNC_FILES,
data: { data: {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
}, },
@ -1067,24 +1062,7 @@ describe(LibraryService.name, () => {
], ],
[ [
{ {
name: JobName.LIBRARY_QUEUE_OFFLINE_CHECK, name: JobName.LIBRARY_QUEUE_SYNC_ASSETS,
data: {
id: libraryStub.externalLibrary1.id,
},
},
],
]);
});
});
describe('queueOfflineCheck', () => {
it('should queue the trash job', async () => {
await sut.queueOfflineCheck(libraryStub.externalLibrary1.id);
expect(jobMock.queue.mock.calls).toEqual([
[
{
name: JobName.LIBRARY_OFFLINE_CHECK,
data: { data: {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
}, },
@ -1098,7 +1076,7 @@ describe(LibraryService.name, () => {
it('should queue the refresh job', async () => { it('should queue the refresh job', async () => {
libraryMock.getAll.mockResolvedValue([libraryStub.externalLibrary1]); libraryMock.getAll.mockResolvedValue([libraryStub.externalLibrary1]);
await expect(sut.handleQueueAllScan()).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleQueueSyncAll()).resolves.toBe(JobStatus.SUCCESS);
expect(jobMock.queue.mock.calls).toEqual([ expect(jobMock.queue.mock.calls).toEqual([
[ [
@ -1110,7 +1088,7 @@ describe(LibraryService.name, () => {
]); ]);
expect(jobMock.queueAll).toHaveBeenCalledWith([ expect(jobMock.queueAll).toHaveBeenCalledWith([
{ {
name: JobName.LIBRARY_QUEUE_SCAN, name: JobName.LIBRARY_QUEUE_SYNC_FILES,
data: { data: {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
}, },
@ -1125,13 +1103,11 @@ describe(LibraryService.name, () => {
assetMock.getAll.mockResolvedValue({ items: [assetStub.image1], hasNextPage: false }); assetMock.getAll.mockResolvedValue({ items: [assetStub.image1], hasNextPage: false });
assetMock.getById.mockResolvedValue(assetStub.image1); assetMock.getById.mockResolvedValue(assetStub.image1);
await expect(sut.handleQueueAssetOfflineCheck({ id: libraryStub.externalLibrary1.id })).resolves.toBe( await expect(sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS);
JobStatus.SUCCESS,
);
expect(jobMock.queueAll).toHaveBeenCalledWith([ expect(jobMock.queueAll).toHaveBeenCalledWith([
{ {
name: JobName.LIBRARY_OFFLINE_CHECK, name: JobName.LIBRARY_SYNC_ASSET,
data: { data: {
id: assetStub.image1.id, id: assetStub.image1.id,
importPaths: libraryStub.externalLibrary1.importPaths, importPaths: libraryStub.externalLibrary1.importPaths,

View File

@ -5,16 +5,15 @@ import picomatch from 'picomatch';
import { StorageCore } from 'src/cores/storage.core'; import { StorageCore } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core'; import { SystemConfigCore } from 'src/cores/system-config.core';
import { OnEmit } from 'src/decorators'; import { OnEmit } from 'src/decorators';
import { AssetTrashReason } from 'src/dtos/asset.dto';
import { import {
CreateLibraryDto, CreateLibraryDto,
LibraryResponseDto, LibraryResponseDto,
LibraryStatsResponseDto, LibraryStatsResponseDto,
mapLibrary,
UpdateLibraryDto, UpdateLibraryDto,
ValidateLibraryDto, ValidateLibraryDto,
ValidateLibraryImportPathResponseDto, ValidateLibraryImportPathResponseDto,
ValidateLibraryResponseDto, ValidateLibraryResponseDto,
mapLibrary,
} from 'src/dtos/library.dto'; } from 'src/dtos/library.dto';
import { AssetEntity } from 'src/entities/asset.entity'; import { AssetEntity } from 'src/entities/asset.entity';
import { AssetType } from 'src/enum'; import { AssetType } from 'src/enum';
@ -27,8 +26,8 @@ import {
IJobRepository, IJobRepository,
ILibraryFileJob, ILibraryFileJob,
ILibraryOfflineJob, ILibraryOfflineJob,
JOBS_LIBRARY_PAGINATION_SIZE,
JobName, JobName,
JOBS_LIBRARY_PAGINATION_SIZE,
JobStatus, JobStatus,
} from 'src/interfaces/job.interface'; } from 'src/interfaces/job.interface';
import { ILibraryRepository } from 'src/interfaces/library.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface';
@ -76,7 +75,7 @@ export class LibraryService {
this.jobRepository.addCronJob( this.jobRepository.addCronJob(
'libraryScan', 'libraryScan',
scan.cronExpression, scan.cronExpression,
() => handlePromiseError(this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SCAN_ALL }), this.logger), () => handlePromiseError(this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ALL }), this.logger),
scan.enabled, scan.enabled,
); );
@ -247,7 +246,7 @@ export class LibraryService {
private async scanAssets(libraryId: string, assetPaths: string[], ownerId: string) { private async scanAssets(libraryId: string, assetPaths: string[], ownerId: string) {
await this.jobRepository.queueAll( await this.jobRepository.queueAll(
assetPaths.map((assetPath) => ({ assetPaths.map((assetPath) => ({
name: JobName.LIBRARY_REFRESH_ASSET, name: JobName.LIBRARY_SYNC_FILE,
data: { data: {
id: libraryId, id: libraryId,
assetPath, assetPath,
@ -359,134 +358,94 @@ export class LibraryService {
return JobStatus.SUCCESS; return JobStatus.SUCCESS;
} }
private async getMtime(path: string): Promise<Date> { async handleSyncFile(job: ILibraryFileJob): Promise<JobStatus> {
try { // Only needs to handle new assets
const stat = await this.storageRepository.stat(path);
return stat.mtime;
} catch (error: Error | any) {
throw new BadRequestException(`Cannot access file ${path}`, { cause: error });
}
}
private async refreshExistingAsset(asset: AssetEntity) {
if (asset.trashReason == AssetTrashReason.DELETED) {
this.logger.debug(`Asset is previously trashed by user, won't refresh: ${asset.originalPath}`);
return JobStatus.SKIPPED;
} else if (asset.trashReason == AssetTrashReason.OFFLINE) {
this.logger.debug(`Asset is previously trashed as offline, restoring from trash: ${asset.originalPath}`);
await this.assetRepository.restoreAll([asset.id]);
return JobStatus.SUCCESS;
}
const mtime = await this.getMtime(asset.originalPath);
if (mtime.toISOString() === asset.fileModifiedAt.toISOString()) {
this.logger.debug(`Asset already exists in database and on disk, will not import: ${asset.originalPath}`);
return JobStatus.SKIPPED;
}
this.logger.debug(
`File modification time has changed, re-importing asset: ${asset.originalPath}. Old mtime: ${asset.fileModifiedAt}. New mtime: ${mtime}`,
);
await this.assetRepository.updateAll([asset.id], {
fileCreatedAt: mtime,
fileModifiedAt: mtime,
originalFileName: parse(asset.originalPath).base,
deletedAt: null,
trashReason: null,
});
}
async handleAssetRefresh(job: ILibraryFileJob): Promise<JobStatus> {
const assetPath = path.normalize(job.assetPath); const assetPath = path.normalize(job.assetPath);
let asset = await this.assetRepository.getByLibraryIdAndOriginalPath(job.id, assetPath); let asset = await this.assetRepository.getByLibraryIdAndOriginalPath(job.id, assetPath);
if (asset) { if (asset) {
const status = await this.refreshExistingAsset(asset); return JobStatus.SKIPPED;
if (status) {
return status;
}
} else {
// This asset is new to us, read it from disk
this.logger.log(`Importing new library asset: ${assetPath}`);
const library = await this.repository.get(job.id, true);
if (library?.deletedAt) {
this.logger.error('Cannot import asset into deleted library');
return JobStatus.FAILED;
}
// TODO: device asset id is deprecated, remove it
const deviceAssetId = `${basename(assetPath)}`.replaceAll(/\s+/g, '');
const pathHash = this.cryptoRepository.hashSha1(`path:${assetPath}`);
// TODO: doesn't xmp replace the file extension? Will need investigation
let sidecarPath: string | null = null;
if (await this.storageRepository.checkFileExists(`${assetPath}.xmp`, R_OK)) {
sidecarPath = `${assetPath}.xmp`;
}
let assetType: AssetType;
if (mimeTypes.isImage(assetPath)) {
assetType = AssetType.IMAGE;
} else if (mimeTypes.isVideo(assetPath)) {
assetType = AssetType.VIDEO;
} else {
throw new BadRequestException(`Unsupported file type ${assetPath}`);
}
const mtime = await this.getMtime(assetPath);
// TODO: In wait of refactoring the domain asset service, this function is just manually written like this
asset = await this.assetRepository.create({
ownerId: job.ownerId,
libraryId: job.id,
checksum: pathHash,
originalPath: assetPath,
deviceAssetId,
deviceId: 'Library Import',
fileCreatedAt: mtime,
fileModifiedAt: mtime,
localDateTime: mtime,
type: assetType,
originalFileName: parse(assetPath).base,
sidecarPath,
isExternal: true,
});
} }
this.logger.debug(`Queueing metadata extraction for: ${assetPath}`); let stat;
try {
stat = await this.storageRepository.stat(assetPath);
} catch (error: any) {
if (error.code === 'ENOENT') {
this.logger.error(`File not found: ${assetPath}`);
return JobStatus.SKIPPED;
}
this.logger.error(`Error reading file: ${assetPath}. Error: ${error}`);
return JobStatus.FAILED;
}
this.logger.log(`Importing new library asset: ${assetPath}`);
const library = await this.repository.get(job.id, true);
if (library?.deletedAt) {
this.logger.error('Cannot import asset into deleted library');
return JobStatus.FAILED;
}
// TODO: device asset id is deprecated, remove it
const deviceAssetId = `${basename(assetPath)}`.replaceAll(/\s+/g, '');
const pathHash = this.cryptoRepository.hashSha1(`path:${assetPath}`);
// TODO: doesn't xmp replace the file extension? Will need investigation
let sidecarPath: string | null = null;
if (await this.storageRepository.checkFileExists(`${assetPath}.xmp`, R_OK)) {
sidecarPath = `${assetPath}.xmp`;
}
const assetType = mimeTypes.isVideo(assetPath) ? AssetType.VIDEO : AssetType.IMAGE;
const mtime = stat.mtime;
asset = await this.assetRepository.create({
ownerId: job.ownerId,
libraryId: job.id,
checksum: pathHash,
originalPath: assetPath,
deviceAssetId,
deviceId: 'Library Import',
fileCreatedAt: mtime,
fileModifiedAt: mtime,
localDateTime: mtime,
type: assetType,
originalFileName: parse(assetPath).base,
sidecarPath,
isExternal: true,
});
await this.queuePostSyncJobs(asset);
return JobStatus.SUCCESS;
}
async queuePostSyncJobs(asset: AssetEntity) {
this.logger.debug(`Queueing metadata extraction for: ${asset.originalPath}`);
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: asset.id, source: 'upload' } }); await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: asset.id, source: 'upload' } });
if (asset.type === AssetType.VIDEO) { if (asset.type === AssetType.VIDEO) {
await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id: asset.id } }); await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id: asset.id } });
} }
return JobStatus.SUCCESS;
} }
async queueScan(id: string) { async queueScan(id: string) {
await this.findOrFail(id); await this.findOrFail(id);
await this.jobRepository.queue({ await this.jobRepository.queue({
name: JobName.LIBRARY_QUEUE_SCAN, name: JobName.LIBRARY_QUEUE_SYNC_FILES,
data: { data: {
id, id,
}, },
}); });
await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_OFFLINE_CHECK, data: { id } }); await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, data: { id } });
} }
async queueOfflineCheck(id: string) { async handleQueueSyncAll(): Promise<JobStatus> {
this.logger.verbose(`Queueing offline file removal from library ${id}`);
await this.jobRepository.queue({ name: JobName.LIBRARY_OFFLINE_CHECK, data: { id } });
}
async handleQueueAllScan(): Promise<JobStatus> {
this.logger.debug(`Refreshing all external libraries`); this.logger.debug(`Refreshing all external libraries`);
await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_CLEANUP, data: {} }); await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_CLEANUP, data: {} });
@ -494,7 +453,7 @@ export class LibraryService {
const libraries = await this.repository.getAll(true); const libraries = await this.repository.getAll(true);
await this.jobRepository.queueAll( await this.jobRepository.queueAll(
libraries.map((library) => ({ libraries.map((library) => ({
name: JobName.LIBRARY_QUEUE_SCAN, name: JobName.LIBRARY_QUEUE_SYNC_FILES,
data: { data: {
id: library.id, id: library.id,
}, },
@ -502,7 +461,7 @@ export class LibraryService {
); );
await this.jobRepository.queueAll( await this.jobRepository.queueAll(
libraries.map((library) => ({ libraries.map((library) => ({
name: JobName.LIBRARY_QUEUE_OFFLINE_CHECK, name: JobName.LIBRARY_QUEUE_SYNC_ASSETS,
data: { data: {
id: library.id, id: library.id,
}, },
@ -511,20 +470,17 @@ export class LibraryService {
return JobStatus.SUCCESS; return JobStatus.SUCCESS;
} }
async handleAssetOfflineCheck(job: ILibraryOfflineJob): Promise<JobStatus> { async handleSyncAsset(job: ILibraryOfflineJob): Promise<JobStatus> {
const asset = await this.assetRepository.getById(job.id); const asset = await this.assetRepository.getById(job.id);
if (!asset) {
if (!asset || asset.trashReason) {
// Skip if asset is missing or already trashed
// We don't want to trash an asset that has already been trashed because it can otherwise re-appear on the timeline if an asset is re-imported
return JobStatus.SKIPPED; return JobStatus.SKIPPED;
} }
const markOffline = async (explanation: string) => { const markOffline = async (explanation: string) => {
this.logger.debug(`${explanation}, removing: ${asset.originalPath}`); if (!asset.isOffline) {
this.logger.debug(`${explanation}, removing: ${asset.originalPath}`);
await this.assetRepository.updateAll([asset.id], { trashReason: AssetTrashReason.OFFLINE }); await this.assetRepository.updateAll([asset.id], { isOffline: true, deletedAt: new Date() });
await this.assetRepository.softDeleteAll([asset.id]); }
}; };
const isInPath = job.importPaths.find((path) => asset.originalPath.startsWith(path)); const isInPath = job.importPaths.find((path) => asset.originalPath.startsWith(path));
@ -539,20 +495,37 @@ export class LibraryService {
return JobStatus.SUCCESS; return JobStatus.SUCCESS;
} }
const fileExists = await this.storageRepository.checkFileExists(asset.originalPath, R_OK); let stat;
if (!fileExists) { try {
await markOffline('Asset is no longer on disk'); stat = await this.storageRepository.stat(asset.originalPath);
} catch {
await markOffline('Asset is no longer on disk or is inaccessible because of permissions');
return JobStatus.SUCCESS; return JobStatus.SUCCESS;
} }
this.logger.verbose( const mtime = stat.mtime;
`Asset is found on disk, not covered by an exclusion pattern, and is in an import path, doing nothing: ${asset.originalPath}`, const isAssetModified = mtime.toISOString() !== asset.fileModifiedAt.toISOString();
);
if (asset.isOffline || isAssetModified) {
this.logger.debug(`Asset was offline or modified, updating asset record ${asset.originalPath}`);
//TODO: When we have asset status, we need to leave deletedAt as is when status is trashed
await this.assetRepository.updateAll([asset.id], {
isOffline: false,
deletedAt: null,
fileCreatedAt: mtime,
fileModifiedAt: mtime,
originalFileName: parse(asset.originalPath).base,
});
}
if (isAssetModified) {
this.logger.debug(`Asset was modified, queuing metadata extraction for: ${asset.originalPath}`);
await this.queuePostSyncJobs(asset);
}
return JobStatus.SUCCESS; return JobStatus.SUCCESS;
} }
async handleQueueAssetRefresh(job: IEntityJob): Promise<JobStatus> { async handleQueueSyncFiles(job: IEntityJob): Promise<JobStatus> {
const library = await this.repository.get(job.id); const library = await this.repository.get(job.id);
if (!library) { if (!library) {
this.logger.debug(`Library ${job.id} not found, skipping refresh`); this.logger.debug(`Library ${job.id} not found, skipping refresh`);
@ -603,7 +576,7 @@ export class LibraryService {
return JobStatus.SUCCESS; return JobStatus.SUCCESS;
} }
async handleQueueAssetOfflineCheck(job: IEntityJob): Promise<JobStatus> { async handleQueueSyncAssets(job: IEntityJob): Promise<JobStatus> {
const library = await this.repository.get(job.id); const library = await this.repository.get(job.id);
if (!library) { if (!library) {
return JobStatus.SKIPPED; return JobStatus.SKIPPED;
@ -621,7 +594,7 @@ export class LibraryService {
this.logger.debug(`Discovered ${assetCount} asset(s) in library ${library.id}...`); this.logger.debug(`Discovered ${assetCount} asset(s) in library ${library.id}...`);
await this.jobRepository.queueAll( await this.jobRepository.queueAll(
assets.map((asset) => ({ assets.map((asset) => ({
name: JobName.LIBRARY_OFFLINE_CHECK, name: JobName.LIBRARY_SYNC_ASSET,
data: { id: asset.id, importPaths: library.importPaths, exclusionPatterns: library.exclusionPatterns }, data: { id: asset.id, importPaths: library.importPaths, exclusionPatterns: library.exclusionPatterns },
})), })),
); );

View File

@ -86,12 +86,12 @@ export class MicroservicesService {
[JobName.SIDECAR_DISCOVERY]: (data) => this.metadataService.handleSidecarDiscovery(data), [JobName.SIDECAR_DISCOVERY]: (data) => this.metadataService.handleSidecarDiscovery(data),
[JobName.SIDECAR_SYNC]: (data) => this.metadataService.handleSidecarSync(data), [JobName.SIDECAR_SYNC]: (data) => this.metadataService.handleSidecarSync(data),
[JobName.SIDECAR_WRITE]: (data) => this.metadataService.handleSidecarWrite(data), [JobName.SIDECAR_WRITE]: (data) => this.metadataService.handleSidecarWrite(data),
[JobName.LIBRARY_REFRESH_ASSET]: (data) => this.libraryService.handleAssetRefresh(data), [JobName.LIBRARY_QUEUE_SYNC_ALL]: () => this.libraryService.handleQueueSyncAll(),
[JobName.LIBRARY_QUEUE_SCAN]: (data) => this.libraryService.handleQueueAssetRefresh(data), [JobName.LIBRARY_QUEUE_SYNC_FILES]: (data) => this.libraryService.handleQueueSyncFiles(data), //Queues all files paths on disk
[JobName.LIBRARY_QUEUE_OFFLINE_CHECK]: (data) => this.libraryService.handleQueueAssetOfflineCheck(data), [JobName.LIBRARY_SYNC_FILE]: (data) => this.libraryService.handleSyncFile(data), //Handles a single path on disk //Watcher calls for new files
[JobName.LIBRARY_QUEUE_SYNC_ASSETS]: (data) => this.libraryService.handleQueueSyncAssets(data), //Queues all library assets
[JobName.LIBRARY_SYNC_ASSET]: (data) => this.libraryService.handleSyncAsset(data), //Handles all library assets // Watcher calls for unlink and changed
[JobName.LIBRARY_DELETE]: (data) => this.libraryService.handleDeleteLibrary(data), [JobName.LIBRARY_DELETE]: (data) => this.libraryService.handleDeleteLibrary(data),
[JobName.LIBRARY_OFFLINE_CHECK]: (data) => this.libraryService.handleAssetOfflineCheck(data),
[JobName.LIBRARY_QUEUE_SCAN_ALL]: () => this.libraryService.handleQueueAllScan(),
[JobName.LIBRARY_QUEUE_CLEANUP]: () => this.libraryService.handleQueueCleanup(), [JobName.LIBRARY_QUEUE_CLEANUP]: () => this.libraryService.handleQueueCleanup(),
[JobName.SEND_EMAIL]: (data) => this.notificationService.handleSendEmail(data), [JobName.SEND_EMAIL]: (data) => this.notificationService.handleSendEmail(data),
[JobName.NOTIFY_ALBUM_INVITE]: (data) => this.notificationService.handleAlbumInvite(data), [JobName.NOTIFY_ALBUM_INVITE]: (data) => this.notificationService.handleAlbumInvite(data),

View File

@ -6,7 +6,7 @@ import { TrashResponseDto } from 'src/dtos/trash.dto';
import { Permission } from 'src/enum'; import { Permission } from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface'; import { IAccessRepository } from 'src/interfaces/access.interface';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/interfaces/job.interface'; import { IJobRepository, JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus } from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ITrashRepository } from 'src/interfaces/trash.interface'; import { ITrashRepository } from 'src/interfaces/trash.interface';
import { requireAccess } from 'src/utils/access'; import { requireAccess } from 'src/utils/access';

View File

@ -1,4 +1,3 @@
import { AssetTrashReason } from 'src/dtos/asset.dto';
import { AssetFileEntity } from 'src/entities/asset-files.entity'; import { AssetFileEntity } from 'src/entities/asset-files.entity';
import { AssetEntity } from 'src/entities/asset.entity'; import { AssetEntity } from 'src/entities/asset.entity';
import { ExifEntity } from 'src/entities/exif.entity'; import { ExifEntity } from 'src/entities/exif.entity';
@ -73,7 +72,7 @@ export const assetStub = {
deletedAt: null, deletedAt: null,
isExternal: false, isExternal: false,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
noWebpPath: Object.freeze<AssetEntity>({ noWebpPath: Object.freeze<AssetEntity>({
@ -111,7 +110,7 @@ export const assetStub = {
} as ExifEntity, } as ExifEntity,
deletedAt: null, deletedAt: null,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
noThumbhash: Object.freeze<AssetEntity>({ noThumbhash: Object.freeze<AssetEntity>({
@ -146,7 +145,7 @@ export const assetStub = {
sidecarPath: null, sidecarPath: null,
deletedAt: null, deletedAt: null,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
primaryImage: Object.freeze<AssetEntity>({ primaryImage: Object.freeze<AssetEntity>({
@ -191,7 +190,7 @@ export const assetStub = {
{ id: 'stack-child-asset-2' } as AssetEntity, { id: 'stack-child-asset-2' } as AssetEntity,
]), ]),
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
image: Object.freeze<AssetEntity>({ image: Object.freeze<AssetEntity>({
@ -231,7 +230,7 @@ export const assetStub = {
exifImageWidth: 2160, exifImageWidth: 2160,
} as ExifEntity, } as ExifEntity,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
trashed: Object.freeze<AssetEntity>({ trashed: Object.freeze<AssetEntity>({
@ -251,7 +250,6 @@ export const assetStub = {
createdAt: new Date('2023-02-23T05:06:29.716Z'), createdAt: new Date('2023-02-23T05:06:29.716Z'),
updatedAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'),
deletedAt: new Date('2023-02-24T05:06:29.716Z'), deletedAt: new Date('2023-02-24T05:06:29.716Z'),
trashReason: AssetTrashReason.DELETED,
localDateTime: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: false, isFavorite: false,
isArchived: false, isArchived: false,
@ -271,6 +269,8 @@ export const assetStub = {
exifImageWidth: 2160, exifImageWidth: 2160,
} as ExifEntity, } as ExifEntity,
duplicateId: null, duplicateId: null,
isOffline: false,
status: AssetStatus.TRASHED,
}), }),
trashedOffline: Object.freeze<AssetEntity>({ trashedOffline: Object.freeze<AssetEntity>({
@ -291,7 +291,6 @@ export const assetStub = {
createdAt: new Date('2023-02-23T05:06:29.716Z'), createdAt: new Date('2023-02-23T05:06:29.716Z'),
updatedAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'),
deletedAt: new Date('2023-02-24T05:06:29.716Z'), deletedAt: new Date('2023-02-24T05:06:29.716Z'),
trashReason: AssetTrashReason.OFFLINE,
localDateTime: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: false, isFavorite: false,
isArchived: false, isArchived: false,
@ -311,6 +310,7 @@ export const assetStub = {
exifImageWidth: 2160, exifImageWidth: 2160,
} as ExifEntity, } as ExifEntity,
duplicateId: null, duplicateId: null,
isOffline: true,
}), }),
archived: Object.freeze<AssetEntity>({ archived: Object.freeze<AssetEntity>({
id: 'asset-id', id: 'asset-id',
@ -349,7 +349,7 @@ export const assetStub = {
exifImageWidth: 2160, exifImageWidth: 2160,
} as ExifEntity, } as ExifEntity,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
external: Object.freeze<AssetEntity>({ external: Object.freeze<AssetEntity>({
@ -389,7 +389,7 @@ export const assetStub = {
fileSizeInByte: 5000, fileSizeInByte: 5000,
} as ExifEntity, } as ExifEntity,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
image1: Object.freeze<AssetEntity>({ image1: Object.freeze<AssetEntity>({
@ -427,7 +427,7 @@ export const assetStub = {
fileSizeInByte: 5000, fileSizeInByte: 5000,
} as ExifEntity, } as ExifEntity,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
imageFrom2015: Object.freeze<AssetEntity>({ imageFrom2015: Object.freeze<AssetEntity>({
@ -465,7 +465,7 @@ export const assetStub = {
} as ExifEntity, } as ExifEntity,
deletedAt: null, deletedAt: null,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
video: Object.freeze<AssetEntity>({ video: Object.freeze<AssetEntity>({
@ -505,7 +505,7 @@ export const assetStub = {
} as ExifEntity, } as ExifEntity,
deletedAt: null, deletedAt: null,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
livePhotoMotionAsset: Object.freeze({ livePhotoMotionAsset: Object.freeze({
@ -643,7 +643,7 @@ export const assetStub = {
} as ExifEntity, } as ExifEntity,
deletedAt: null, deletedAt: null,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
sidecar: Object.freeze<AssetEntity>({ sidecar: Object.freeze<AssetEntity>({
id: 'asset-id', id: 'asset-id',
@ -677,7 +677,7 @@ export const assetStub = {
sidecarPath: '/original/path.ext.xmp', sidecarPath: '/original/path.ext.xmp',
deletedAt: null, deletedAt: null,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
sidecarWithoutExt: Object.freeze<AssetEntity>({ sidecarWithoutExt: Object.freeze<AssetEntity>({
id: 'asset-id', id: 'asset-id',
@ -711,7 +711,7 @@ export const assetStub = {
sidecarPath: '/original/path.xmp', sidecarPath: '/original/path.xmp',
deletedAt: null, deletedAt: null,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
hasEncodedVideo: Object.freeze<AssetEntity>({ hasEncodedVideo: Object.freeze<AssetEntity>({
@ -749,7 +749,7 @@ export const assetStub = {
} as ExifEntity, } as ExifEntity,
deletedAt: null, deletedAt: null,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
missingFileExtension: Object.freeze<AssetEntity>({ missingFileExtension: Object.freeze<AssetEntity>({
id: 'asset-id', id: 'asset-id',
@ -788,7 +788,7 @@ export const assetStub = {
fileSizeInByte: 5000, fileSizeInByte: 5000,
} as ExifEntity, } as ExifEntity,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
hasFileExtension: Object.freeze<AssetEntity>({ hasFileExtension: Object.freeze<AssetEntity>({
id: 'asset-id', id: 'asset-id',
@ -827,7 +827,7 @@ export const assetStub = {
fileSizeInByte: 5000, fileSizeInByte: 5000,
} as ExifEntity, } as ExifEntity,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
imageDng: Object.freeze<AssetEntity>({ imageDng: Object.freeze<AssetEntity>({
id: 'asset-id', id: 'asset-id',
@ -866,7 +866,7 @@ export const assetStub = {
bitsPerSample: 14, bitsPerSample: 14,
} as ExifEntity, } as ExifEntity,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}), }),
hasEmbedding: Object.freeze<AssetEntity>({ hasEmbedding: Object.freeze<AssetEntity>({
id: 'asset-id-embedding', id: 'asset-id-embedding',
@ -907,7 +907,7 @@ export const assetStub = {
assetId: 'asset-id', assetId: 'asset-id',
embedding: Array.from({ length: 512 }, Math.random), embedding: Array.from({ length: 512 }, Math.random),
}, },
trashReason: null, isOffline: false,
}), }),
hasDupe: Object.freeze<AssetEntity>({ hasDupe: Object.freeze<AssetEntity>({
id: 'asset-id-dupe', id: 'asset-id-dupe',
@ -948,6 +948,6 @@ export const assetStub = {
assetId: 'asset-id', assetId: 'asset-id',
embedding: Array.from({ length: 512 }, Math.random), embedding: Array.from({ length: 512 }, Math.random),
}, },
trashReason: null, isOffline: false,
}), }),
}; };

View File

@ -74,6 +74,7 @@ const assetResponse: AssetResponseDto = {
isTrashed: false, isTrashed: false,
libraryId: 'library-id', libraryId: 'library-id',
hasMetadata: true, hasMetadata: true,
isOffline: false,
}; };
const assetResponseWithoutMetadata = { const assetResponseWithoutMetadata = {
@ -255,7 +256,7 @@ export const sharedLinkStub = {
sidecarPath: null, sidecarPath: null,
deletedAt: null, deletedAt: null,
duplicateId: null, duplicateId: null,
trashReason: null, isOffline: false,
}, },
], ],
}, },

View File

@ -42,7 +42,5 @@ export const newAssetRepositoryMock = (): Mocked<IAssetRepository> => {
upsertFile: vitest.fn(), upsertFile: vitest.fn(),
getAssetsByOriginalPath: vitest.fn(), getAssetsByOriginalPath: vitest.fn(),
getUniqueOriginalPaths: vitest.fn(), getUniqueOriginalPaths: vitest.fn(),
restoreAllDeleted: vitest.fn(),
restoreAllDeletedById: vitest.fn(),
}; };
}; };

View File

@ -24,7 +24,6 @@
import { import {
AssetJobName, AssetJobName,
AssetTypeEnum, AssetTypeEnum,
TrashReason,
type AlbumResponseDto, type AlbumResponseDto,
type AssetResponseDto, type AssetResponseDto,
type StackResponseDto, type StackResponseDto,
@ -60,7 +59,7 @@
export let onClose: () => void; export let onClose: () => void;
const sharedLink = getSharedLink(); const sharedLink = getSharedLink();
$: isOffline = asset.trashReason === TrashReason.Offline; $: isOffline = asset.isOffline;
$: isOwner = $user && asset.ownerId === $user?.id; $: isOwner = $user && asset.ownerId === $user?.id;
$: showDownloadButton = sharedLink ? sharedLink.allowDownload : !isOffline; $: showDownloadButton = sharedLink ? sharedLink.allowDownload : !isOffline;
// $: showEditorButton = // $: showEditorButton =
@ -137,7 +136,7 @@
{#if showDownloadButton} {#if showDownloadButton}
<DownloadAction {asset} menuItem /> <DownloadAction {asset} menuItem />
{/if} {/if}
{#if asset.trashReason === TrashReason.Deleted} {#if asset.status === AssetStatus.TRASHED}
<RestoreAction {asset} {onAction} /> <RestoreAction {asset} {onAction} />
{:else} {:else}
<AddToAlbumAction {asset} {onAction} /> <AddToAlbumAction {asset} {onAction} />

View File

@ -12,7 +12,6 @@
import { import {
AssetMediaSize, AssetMediaSize,
getAssetInfo, getAssetInfo,
TrashReason,
updateAsset, updateAsset,
type AlbumResponseDto, type AlbumResponseDto,
type AssetResponseDto, type AssetResponseDto,
@ -73,7 +72,7 @@
} }
} }
$: isOffline = asset.trashReason === TrashReason.Offline; $: isOffline = asset.isOffline;
$: isOwner = $user?.id === asset.ownerId; $: isOwner = $user?.id === asset.ownerId;
const handleNewAsset = async (newAsset: AssetResponseDto) => { const handleNewAsset = async (newAsset: AssetResponseDto) => {