1
0
mirror of https://github.com/immich-app/immich.git synced 2025-08-09 23:17:29 +02:00

refactor(server): library syncing (#12220)

* refactor: library scanning

fix tests

remove offline files step

cleanup library service

improve tests

cleanup tests

add db migration

fix e2e

cleanup openapi

fix tests

fix tests

update docs

update docs

update mobile code

fix formatting

don't remove assets from library with invalid import path

use trash for offline files

add migration

simplify scan endpoint

cleanup library panel

fix library tests

e2e lint

fix e2e

trash e2e

fix lint

add asset trash tests

add more tests

ensure thumbs are generated

cleanup svelte

cleanup queue names

fix tests

fix lint

add warning due to trash

fix trash tests

fix lint

fix tests

Admin message for offline asset

fix comments

Update web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

add permission to library scan endpoint

revert asset interface sort

add trash reason to shared link stub

improve path view in offline

update docs

improve trash performance

fix comments

remove stray comment

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

* chore(server): drop coverage to 80% for functions

* chore: rebase and generated files

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
This commit is contained in:
Jonathan Jogenfors
2024-09-25 19:26:19 +02:00
committed by GitHub
parent 1ef2834603
commit b2f2be3485
85 changed files with 941 additions and 1926 deletions

View File

@ -197,7 +197,6 @@ part 'model/ratings_update.dart';
part 'model/reaction_level.dart';
part 'model/reaction_type.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_asset_response_dto.dart';
part 'model/search_explore_item.dart';

View File

@ -833,14 +833,12 @@ class AssetsApi {
///
/// * [bool] isFavorite:
///
/// * [bool] isOffline:
///
/// * [bool] isVisible:
///
/// * [String] livePhotoVideoId:
///
/// * [MultipartFile] sidecarData:
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isOffline, bool? isVisible, String? livePhotoVideoId, MultipartFile? sidecarData, }) async {
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isVisible, String? livePhotoVideoId, MultipartFile? sidecarData, }) async {
// ignore: prefer_const_declarations
final path = r'/assets';
@ -896,10 +894,6 @@ class AssetsApi {
hasFields = true;
mp.fields[r'isFavorite'] = parameterToString(isFavorite);
}
if (isOffline != null) {
hasFields = true;
mp.fields[r'isOffline'] = parameterToString(isOffline);
}
if (isVisible != null) {
hasFields = true;
mp.fields[r'isVisible'] = parameterToString(isVisible);
@ -951,15 +945,13 @@ class AssetsApi {
///
/// * [bool] isFavorite:
///
/// * [bool] isOffline:
///
/// * [bool] isVisible:
///
/// * [String] livePhotoVideoId:
///
/// * [MultipartFile] sidecarData:
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isOffline, bool? isVisible, String? livePhotoVideoId, MultipartFile? sidecarData, }) async {
final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, xImmichChecksum: xImmichChecksum, duration: duration, isArchived: isArchived, isFavorite: isFavorite, isOffline: isOffline, isVisible: isVisible, livePhotoVideoId: livePhotoVideoId, sidecarData: sidecarData, );
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isVisible, String? livePhotoVideoId, MultipartFile? sidecarData, }) async {
final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, xImmichChecksum: xImmichChecksum, duration: duration, isArchived: isArchived, isFavorite: isFavorite, isVisible: isVisible, livePhotoVideoId: livePhotoVideoId, sidecarData: sidecarData, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@ -243,13 +243,13 @@ class LibrariesApi {
return null;
}
/// Performs an HTTP 'POST /libraries/{id}/removeOffline' operation and returns the [Response].
/// Performs an HTTP 'POST /libraries/{id}/scan' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> removeOfflineFilesWithHttpInfo(String id,) async {
Future<Response> scanLibraryWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final path = r'/libraries/{id}/removeOffline'
final path = r'/libraries/{id}/scan'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
@ -276,52 +276,8 @@ class LibrariesApi {
/// Parameters:
///
/// * [String] id (required):
Future<void> removeOfflineFiles(String id,) async {
final response = await removeOfflineFilesWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'POST /libraries/{id}/scan' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [ScanLibraryDto] scanLibraryDto (required):
Future<Response> scanLibraryWithHttpInfo(String id, ScanLibraryDto scanLibraryDto,) async {
// ignore: prefer_const_declarations
final path = r'/libraries/{id}/scan'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = scanLibraryDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [ScanLibraryDto] scanLibraryDto (required):
Future<void> scanLibrary(String id, ScanLibraryDto scanLibraryDto,) async {
final response = await scanLibraryWithHttpInfo(id, scanLibraryDto,);
Future<void> scanLibrary(String id,) async {
final response = await scanLibraryWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

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

View File

@ -1,125 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class ScanLibraryDto {
/// Returns a new [ScanLibraryDto] instance.
ScanLibraryDto({
this.refreshAllFiles,
this.refreshModifiedFiles,
});
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? refreshAllFiles;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? refreshModifiedFiles;
@override
bool operator ==(Object other) => identical(this, other) || other is ScanLibraryDto &&
other.refreshAllFiles == refreshAllFiles &&
other.refreshModifiedFiles == refreshModifiedFiles;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(refreshAllFiles == null ? 0 : refreshAllFiles!.hashCode) +
(refreshModifiedFiles == null ? 0 : refreshModifiedFiles!.hashCode);
@override
String toString() => 'ScanLibraryDto[refreshAllFiles=$refreshAllFiles, refreshModifiedFiles=$refreshModifiedFiles]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.refreshAllFiles != null) {
json[r'refreshAllFiles'] = this.refreshAllFiles;
} else {
// json[r'refreshAllFiles'] = null;
}
if (this.refreshModifiedFiles != null) {
json[r'refreshModifiedFiles'] = this.refreshModifiedFiles;
} else {
// json[r'refreshModifiedFiles'] = null;
}
return json;
}
/// Returns a new [ScanLibraryDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static ScanLibraryDto? fromJson(dynamic value) {
upgradeDto(value, "ScanLibraryDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return ScanLibraryDto(
refreshAllFiles: mapValueOfType<bool>(json, r'refreshAllFiles'),
refreshModifiedFiles: mapValueOfType<bool>(json, r'refreshModifiedFiles'),
);
}
return null;
}
static List<ScanLibraryDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ScanLibraryDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = ScanLibraryDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, ScanLibraryDto> mapFromJson(dynamic json) {
final map = <String, ScanLibraryDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = ScanLibraryDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of ScanLibraryDto-objects as value to a dart map
static Map<String, List<ScanLibraryDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<ScanLibraryDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = ScanLibraryDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}