mirror of
https://github.com/immich-app/immich.git
synced 2025-12-14 08:40:42 -08:00
Compare commits
1 Commits
hash-on-up
...
sqlite-flu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94e315c845 |
@@ -125,7 +125,7 @@ When `DB_URL` is defined, the `DB_HOSTNAME`, `DB_PORT`, `DB_USERNAME`, `DB_PASSW
|
|||||||
All `REDIS_` variables must be provided to all Immich workers, including `api` and `microservices`.
|
All `REDIS_` variables must be provided to all Immich workers, including `api` and `microservices`.
|
||||||
|
|
||||||
`REDIS_URL` must start with `ioredis://` and then include a `base64` encoded JSON string for the configuration.
|
`REDIS_URL` must start with `ioredis://` and then include a `base64` encoded JSON string for the configuration.
|
||||||
More info can be found in the upstream [ioredis] documentation.
|
More info can be found in the upstream [ioredis][redis-api] documentation.
|
||||||
|
|
||||||
When `REDIS_URL` or `REDIS_SOCKET` are defined, the `REDIS_HOSTNAME`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`, and `REDIS_DBINDEX` variables are ignored.
|
When `REDIS_URL` or `REDIS_SOCKET` are defined, the `REDIS_HOSTNAME`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`, and `REDIS_DBINDEX` variables are ignored.
|
||||||
:::
|
:::
|
||||||
@@ -226,4 +226,4 @@ to use use a Docker secret for the password in the Redis container.
|
|||||||
[docker-secrets-example]: https://github.com/docker-library/redis/issues/46#issuecomment-335326234
|
[docker-secrets-example]: https://github.com/docker-library/redis/issues/46#issuecomment-335326234
|
||||||
[docker-secrets-docs]: https://github.com/docker-library/docs/tree/master/postgres#docker-secrets
|
[docker-secrets-docs]: https://github.com/docker-library/docs/tree/master/postgres#docker-secrets
|
||||||
[docker-secrets]: https://docs.docker.com/engine/swarm/secrets/
|
[docker-secrets]: https://docs.docker.com/engine/swarm/secrets/
|
||||||
[ioredis]: https://ioredis.readthedocs.io/en/latest/README/#connect-to-redis
|
[redis-api]: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
|
||||||
|
|||||||
7
docs/package-lock.json
generated
7
docs/package-lock.json
generated
@@ -13698,10 +13698,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prism-react-renderer": {
|
"node_modules/prism-react-renderer": {
|
||||||
"version": "2.4.0",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz",
|
||||||
"integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==",
|
"integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prismjs": "^1.26.0",
|
"@types/prismjs": "^1.26.0",
|
||||||
"clsx": "^2.0.0"
|
"clsx": "^2.0.0"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/utils/sqlite.dart';
|
||||||
import 'package:timezone/data/latest.dart';
|
import 'package:timezone/data/latest.dart';
|
||||||
import 'package:immich_mobile/constants/locales.dart';
|
import 'package:immich_mobile/constants/locales.dart';
|
||||||
import 'package:immich_mobile/services/background.service.dart';
|
import 'package:immich_mobile/services/background.service.dart';
|
||||||
@@ -54,6 +55,7 @@ void main() async {
|
|||||||
|
|
||||||
Future<void> initApp() async {
|
Future<void> initApp() async {
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
|
await openSqliteDatabase();
|
||||||
|
|
||||||
if (kReleaseMode && Platform.isAndroid) {
|
if (kReleaseMode && Platform.isAndroid) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
|
|
||||||
class RejectResult {
|
|
||||||
final String localId;
|
|
||||||
final String remoteId;
|
|
||||||
|
|
||||||
RejectResult({
|
|
||||||
required this.localId,
|
|
||||||
required this.remoteId,
|
|
||||||
});
|
|
||||||
|
|
||||||
RejectResult copyWith({
|
|
||||||
String? localId,
|
|
||||||
String? remoteId,
|
|
||||||
}) {
|
|
||||||
return RejectResult(
|
|
||||||
localId: localId ?? this.localId,
|
|
||||||
remoteId: remoteId ?? this.remoteId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
|
||||||
return <String, dynamic>{
|
|
||||||
'localId': localId,
|
|
||||||
'remoteId': remoteId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
factory RejectResult.fromMap(Map<String, dynamic> map) {
|
|
||||||
return RejectResult(
|
|
||||||
localId: map['localId'] as String,
|
|
||||||
remoteId: map['remoteId'] as String,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
|
||||||
|
|
||||||
factory RejectResult.fromJson(String source) =>
|
|
||||||
RejectResult.fromMap(json.decode(source) as Map<String, dynamic>);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'RejectResult(localId: $localId, remoteId: $remoteId)';
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(covariant RejectResult other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other.localId == localId && other.remoteId == remoteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => localId.hashCode ^ remoteId.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AcceptResult {
|
|
||||||
final String localId;
|
|
||||||
|
|
||||||
AcceptResult({
|
|
||||||
required this.localId,
|
|
||||||
});
|
|
||||||
|
|
||||||
AcceptResult copyWith({
|
|
||||||
String? localId,
|
|
||||||
}) {
|
|
||||||
return AcceptResult(
|
|
||||||
localId: localId ?? this.localId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
|
||||||
return <String, dynamic>{
|
|
||||||
'localId': localId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
factory AcceptResult.fromMap(Map<String, dynamic> map) {
|
|
||||||
return AcceptResult(
|
|
||||||
localId: map['localId'] as String,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
|
||||||
|
|
||||||
factory AcceptResult.fromJson(String source) =>
|
|
||||||
AcceptResult.fromMap(json.decode(source) as Map<String, dynamic>);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'AcceptResult(localId: $localId)';
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(covariant AcceptResult other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other.localId == localId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => localId.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BulkUploadCheckResult {
|
|
||||||
List<RejectResult> rejects;
|
|
||||||
List<AcceptResult> accepts;
|
|
||||||
|
|
||||||
BulkUploadCheckResult({
|
|
||||||
required this.rejects,
|
|
||||||
required this.accepts,
|
|
||||||
});
|
|
||||||
|
|
||||||
BulkUploadCheckResult copyWith({
|
|
||||||
List<RejectResult>? rejects,
|
|
||||||
List<AcceptResult>? accepts,
|
|
||||||
}) {
|
|
||||||
return BulkUploadCheckResult(
|
|
||||||
rejects: rejects ?? this.rejects,
|
|
||||||
accepts: accepts ?? this.accepts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
|
||||||
return <String, dynamic>{
|
|
||||||
'rejects': rejects.map((x) => x.toMap()).toList(),
|
|
||||||
'accepts': accepts.map((x) => x.toMap()).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
factory BulkUploadCheckResult.fromMap(Map<String, dynamic> map) {
|
|
||||||
return BulkUploadCheckResult(
|
|
||||||
rejects: List<RejectResult>.from(
|
|
||||||
(map['rejects'] as List<int>).map<RejectResult>(
|
|
||||||
(x) => RejectResult.fromMap(x as Map<String, dynamic>),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
accepts: List<AcceptResult>.from(
|
|
||||||
(map['accepts'] as List<int>).map<AcceptResult>(
|
|
||||||
(x) => AcceptResult.fromMap(x as Map<String, dynamic>),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
|
||||||
|
|
||||||
factory BulkUploadCheckResult.fromJson(String source) =>
|
|
||||||
BulkUploadCheckResult.fromMap(
|
|
||||||
json.decode(source) as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'BulkUploadCheckResult(rejects: $rejects, accepts: $accepts)';
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(covariant BulkUploadCheckResult other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
final listEquals = const DeepCollectionEquality().equals;
|
|
||||||
|
|
||||||
return listEquals(other.rejects, rejects) &&
|
|
||||||
listEquals(other.accepts, accepts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => rejects.hashCode ^ accepts.hashCode;
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cancellation_token_http/http.dart';
|
import 'package:cancellation_token_http/http.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@@ -460,39 +462,36 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<BackupCandidate> candidates = Set.from(state.allUniqueAssets);
|
Set<BackupCandidate> assetsWillBeBackup = Set.from(state.allUniqueAssets);
|
||||||
// Remove item that has already been backed up
|
// Remove item that has already been backed up
|
||||||
for (final assetId in state.allAssetsInDatabase) {
|
for (final assetId in state.allAssetsInDatabase) {
|
||||||
candidates.removeWhere((e) => e.asset.id == assetId);
|
assetsWillBeBackup.removeWhere((e) => e.asset.id == assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candidates.isEmpty) {
|
if (assetsWillBeBackup.isEmpty) {
|
||||||
state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
|
state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check with server for hash duplication
|
// Perform Backup
|
||||||
final bulkCheckResult = await _backupService.checkBulkUpload(candidates);
|
state = state.copyWith(cancelToken: CancellationToken());
|
||||||
|
|
||||||
// // Perform Backup
|
final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null;
|
||||||
// state = state.copyWith(cancelToken: CancellationToken());
|
|
||||||
|
|
||||||
// final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null;
|
pmProgressHandler?.stream.listen((event) {
|
||||||
|
final double progress = event.progress;
|
||||||
|
state = state.copyWith(iCloudDownloadProgress: progress);
|
||||||
|
});
|
||||||
|
|
||||||
// pmProgressHandler?.stream.listen((event) {
|
await _backupService.backupAsset(
|
||||||
// final double progress = event.progress;
|
assetsWillBeBackup,
|
||||||
// state = state.copyWith(iCloudDownloadProgress: progress);
|
state.cancelToken,
|
||||||
// });
|
pmProgressHandler: pmProgressHandler,
|
||||||
|
onSuccess: _onAssetUploaded,
|
||||||
// await _backupService.backupAsset(
|
onProgress: _onUploadProgress,
|
||||||
// candidates,
|
onCurrentAsset: _onSetCurrentBackupAsset,
|
||||||
// state.cancelToken,
|
onError: _onBackupError,
|
||||||
// pmProgressHandler: pmProgressHandler,
|
);
|
||||||
// onSuccess: _onAssetUploaded,
|
await notifyBackgroundServiceCanRun();
|
||||||
// onProgress: _onUploadProgress,
|
|
||||||
// onCurrentAsset: _onSetCurrentBackupAsset,
|
|
||||||
// onError: _onBackupError,
|
|
||||||
// );
|
|
||||||
// await notifyBackgroundServiceCanRun();
|
|
||||||
} else {
|
} else {
|
||||||
openAppSettings();
|
openAppSettings();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -361,13 +361,8 @@ class BackgroundService {
|
|||||||
UserService(apiService, db, syncSerive, partnerService);
|
UserService(apiService, db, syncSerive, partnerService);
|
||||||
AlbumService albumService =
|
AlbumService albumService =
|
||||||
AlbumService(apiService, userService, syncSerive, db);
|
AlbumService(apiService, userService, syncSerive, db);
|
||||||
BackupService backupService = BackupService(
|
BackupService backupService =
|
||||||
apiService,
|
BackupService(apiService, db, settingService, albumService);
|
||||||
db,
|
|
||||||
settingService,
|
|
||||||
albumService,
|
|
||||||
hashService,
|
|
||||||
);
|
|
||||||
|
|
||||||
final selectedAlbums = backupService.selectedAlbumsQuery().findAllSync();
|
final selectedAlbums = backupService.selectedAlbumsQuery().findAllSync();
|
||||||
final excludedAlbums = backupService.excludedAlbumsQuery().findAllSync();
|
final excludedAlbums = backupService.excludedAlbumsQuery().findAllSync();
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import 'package:immich_mobile/entities/backup_album.entity.dart';
|
|||||||
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
|
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
||||||
import 'package:immich_mobile/models/backup/bulk_upload_check_result.model.dart';
|
|
||||||
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
|
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
|
||||||
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
|
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
|
||||||
import 'package:immich_mobile/models/backup/success_upload_asset.model.dart';
|
import 'package:immich_mobile/models/backup/success_upload_asset.model.dart';
|
||||||
@@ -20,7 +19,6 @@ import 'package:immich_mobile/providers/db.provider.dart';
|
|||||||
import 'package:immich_mobile/services/album.service.dart';
|
import 'package:immich_mobile/services/album.service.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/services/hash.service.dart';
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@@ -34,7 +32,6 @@ final backupServiceProvider = Provider(
|
|||||||
ref.watch(dbProvider),
|
ref.watch(dbProvider),
|
||||||
ref.watch(appSettingsServiceProvider),
|
ref.watch(appSettingsServiceProvider),
|
||||||
ref.watch(albumServiceProvider),
|
ref.watch(albumServiceProvider),
|
||||||
ref.watch(hashServiceProvider),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -45,71 +42,14 @@ class BackupService {
|
|||||||
final Logger _log = Logger("BackupService");
|
final Logger _log = Logger("BackupService");
|
||||||
final AppSettingsService _appSetting;
|
final AppSettingsService _appSetting;
|
||||||
final AlbumService _albumService;
|
final AlbumService _albumService;
|
||||||
final HashService _hashService;
|
|
||||||
|
|
||||||
BackupService(
|
BackupService(
|
||||||
this._apiService,
|
this._apiService,
|
||||||
this._db,
|
this._db,
|
||||||
this._appSetting,
|
this._appSetting,
|
||||||
this._albumService,
|
this._albumService,
|
||||||
this._hashService,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<BulkUploadCheckResult> checkBulkUpload(
|
|
||||||
Set<BackupCandidate> candidates,
|
|
||||||
) async {
|
|
||||||
List<AssetBulkUploadCheckItem> assets = [];
|
|
||||||
|
|
||||||
final assetEntities = candidates.map((c) => c.asset).toList();
|
|
||||||
final hashedDeviceAssets =
|
|
||||||
await _hashService.getHashedAssetsFromAssetEntity(assetEntities);
|
|
||||||
|
|
||||||
for (final hashedAsset in hashedDeviceAssets) {
|
|
||||||
final AssetBulkUploadCheckItem item = AssetBulkUploadCheckItem(
|
|
||||||
id: hashedAsset.id.toString(),
|
|
||||||
checksum: hashedAsset.checksum,
|
|
||||||
);
|
|
||||||
|
|
||||||
assets.add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await _apiService.assetsApi.checkBulkUpload(
|
|
||||||
AssetBulkUploadCheckDto(assets: assets),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response == null) {
|
|
||||||
return BulkUploadCheckResult(
|
|
||||||
rejects: [],
|
|
||||||
accepts: [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<RejectResult> rejects = [];
|
|
||||||
final List<AcceptResult> accepts = [];
|
|
||||||
|
|
||||||
for (final result in response.results) {
|
|
||||||
if (result.action == AssetBulkUploadCheckResultActionEnum.reject) {
|
|
||||||
rejects.add(
|
|
||||||
RejectResult(
|
|
||||||
localId: result.id,
|
|
||||||
remoteId: result.assetId ?? "",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
accepts.add(
|
|
||||||
AcceptResult(
|
|
||||||
localId: result.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return BulkUploadCheckResult(
|
|
||||||
rejects: rejects,
|
|
||||||
accepts: accepts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<String>?> getDeviceBackupAsset() async {
|
Future<List<String>?> getDeviceBackupAsset() async {
|
||||||
final String deviceId = Store.get(StoreKey.deviceId);
|
final String deviceId = Store.get(StoreKey.deviceId);
|
||||||
|
|
||||||
|
|||||||
@@ -19,20 +19,8 @@ class HashService {
|
|||||||
final BackgroundService _backgroundService;
|
final BackgroundService _backgroundService;
|
||||||
final _log = Logger('HashService');
|
final _log = Logger('HashService');
|
||||||
|
|
||||||
Future<List<Asset>> getHashedAssetsFromAssetEntity(
|
|
||||||
List<AssetEntity> assets,
|
|
||||||
) async {
|
|
||||||
final ids = assets
|
|
||||||
.map(Platform.isAndroid ? (a) => a.id.toInt() : (a) => a.id)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final List<DeviceAsset?> hashes = await lookupHashes(ids);
|
|
||||||
|
|
||||||
return _mapAllHashedAssets(assets, hashes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns all assets that were successfully hashed
|
/// Returns all assets that were successfully hashed
|
||||||
Future<List<Asset>> getHashedAssetsFromDeviceAlbum(
|
Future<List<Asset>> getHashedAssets(
|
||||||
AssetPathEntity album, {
|
AssetPathEntity album, {
|
||||||
int start = 0,
|
int start = 0,
|
||||||
int end = 0x7fffffffffffffff,
|
int end = 0x7fffffffffffffff,
|
||||||
@@ -56,7 +44,7 @@ class HashService {
|
|||||||
final ids = assetEntities
|
final ids = assetEntities
|
||||||
.map(Platform.isAndroid ? (a) => a.id.toInt() : (a) => a.id)
|
.map(Platform.isAndroid ? (a) => a.id.toInt() : (a) => a.id)
|
||||||
.toList();
|
.toList();
|
||||||
final List<DeviceAsset?> hashes = await lookupHashes(ids);
|
final List<DeviceAsset?> hashes = await _lookupHashes(ids);
|
||||||
final List<DeviceAsset> toAdd = [];
|
final List<DeviceAsset> toAdd = [];
|
||||||
final List<String> toHash = [];
|
final List<String> toHash = [];
|
||||||
|
|
||||||
@@ -102,7 +90,7 @@ class HashService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lookup hashes of assets by their local ID
|
/// Lookup hashes of assets by their local ID
|
||||||
Future<List<DeviceAsset?>> lookupHashes(List<Object> ids) =>
|
Future<List<DeviceAsset?>> _lookupHashes(List<Object> ids) =>
|
||||||
Platform.isAndroid
|
Platform.isAndroid
|
||||||
? _db.androidDeviceAssets.getAll(ids.cast())
|
? _db.androidDeviceAssets.getAll(ids.cast())
|
||||||
: _db.iOSDeviceAssets.getAllById(ids.cast());
|
: _db.iOSDeviceAssets.getAllById(ids.cast());
|
||||||
|
|||||||
@@ -566,8 +566,8 @@ class SyncService {
|
|||||||
.findAll();
|
.findAll();
|
||||||
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
|
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
|
||||||
final int assetCountOnDevice = await ape.assetCountAsync;
|
final int assetCountOnDevice = await ape.assetCountAsync;
|
||||||
final List<Asset> onDevice = await _hashService
|
final List<Asset> onDevice =
|
||||||
.getHashedAssetsFromDeviceAlbum(ape, excludedAssets: excludedAssets);
|
await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets);
|
||||||
_removeDuplicates(onDevice);
|
_removeDuplicates(onDevice);
|
||||||
// _removeDuplicates sorts `onDevice` by checksum
|
// _removeDuplicates sorts `onDevice` by checksum
|
||||||
final (toAdd, toUpdate, toDelete) = _diffAssets(onDevice, inDb);
|
final (toAdd, toUpdate, toDelete) = _diffAssets(onDevice, inDb);
|
||||||
@@ -649,8 +649,7 @@ class SyncService {
|
|||||||
if (modified == null) {
|
if (modified == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final List<Asset> newAssets =
|
final List<Asset> newAssets = await _hashService.getHashedAssets(modified);
|
||||||
await _hashService.getHashedAssetsFromDeviceAlbum(modified);
|
|
||||||
|
|
||||||
if (totalOnDevice != lastKnownTotal + newAssets.length) {
|
if (totalOnDevice != lastKnownTotal + newAssets.length) {
|
||||||
return false;
|
return false;
|
||||||
@@ -684,8 +683,8 @@ class SyncService {
|
|||||||
]) async {
|
]) async {
|
||||||
_log.info("Syncing a new local album to DB: ${ape.name}");
|
_log.info("Syncing a new local album to DB: ${ape.name}");
|
||||||
final Album a = Album.local(ape);
|
final Album a = Album.local(ape);
|
||||||
final assets = await _hashService.getHashedAssetsFromDeviceAlbum(ape,
|
final assets =
|
||||||
excludedAssets: excludedAssets);
|
await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets);
|
||||||
_removeDuplicates(assets);
|
_removeDuplicates(assets);
|
||||||
final (existingInDb, updated) = await _linkWithExistingFromDb(assets);
|
final (existingInDb, updated) = await _linkWithExistingFromDb(assets);
|
||||||
_log.info(
|
_log.info(
|
||||||
|
|||||||
22
mobile/lib/utils/sqlite.dart
Normal file
22
mobile/lib/utils/sqlite.dart
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:path/path.dart';
|
||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
|
||||||
|
Future<void> openSqliteDatabase() async {
|
||||||
|
final database = openDatabase(
|
||||||
|
// Set the path to the database. Note: Using the `join` function from the
|
||||||
|
// `path` package is best practice to ensure the path is correctly
|
||||||
|
// constructed for each platform.
|
||||||
|
join(await getDatabasesPath(), 'immich_database.db'),
|
||||||
|
|
||||||
|
// When the database is first created, create a table to store dogs.
|
||||||
|
onCreate: (db, version) {
|
||||||
|
// Run the CREATE TABLE statement on the database.
|
||||||
|
return db.execute(
|
||||||
|
'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// Set the version. This executes the onCreate function and provides a
|
||||||
|
// path to perform database upgrades and downgrades.
|
||||||
|
version: 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1454,7 +1454,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.0"
|
||||||
sqflite:
|
sqflite:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
|
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ dependencies:
|
|||||||
share_plus: ^10.0.0
|
share_plus: ^10.0.0
|
||||||
flutter_displaymode: ^0.6.0
|
flutter_displaymode: ^0.6.0
|
||||||
scrollable_positioned_list: ^0.3.8
|
scrollable_positioned_list: ^0.3.8
|
||||||
path: ^1.8.3
|
path: ^1.9.0
|
||||||
path_provider: ^2.1.2
|
path_provider: ^2.1.2
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
http_parser: ^4.0.2
|
http_parser: ^4.0.2
|
||||||
@@ -67,6 +67,7 @@ dependencies:
|
|||||||
image_picker: ^1.0.7 # only used to select user profile image from system gallery -> we can simply select an image from within immich?
|
image_picker: ^1.0.7 # only used to select user profile image from system gallery -> we can simply select an image from within immich?
|
||||||
logging: ^1.2.0
|
logging: ^1.2.0
|
||||||
file_picker: ^8.0.0+1
|
file_picker: ^8.0.0+1
|
||||||
|
sqflite: ^2.3.3+1
|
||||||
|
|
||||||
# This is uncommented in F-Droid build script
|
# This is uncommented in F-Droid build script
|
||||||
# Taken from https://github.com/Myzel394/locus/blob/445013d22ec1d759027d4303bd65b30c5c8588c8/pubspec.yaml#L105
|
# Taken from https://github.com/Myzel394/locus/blob/445013d22ec1d759027d4303bd65b30c5c8588c8/pubspec.yaml#L105
|
||||||
|
|||||||
46
server/package-lock.json
generated
46
server/package-lock.json
generated
@@ -24,7 +24,7 @@
|
|||||||
"@opentelemetry/context-async-hooks": "^1.24.0",
|
"@opentelemetry/context-async-hooks": "^1.24.0",
|
||||||
"@opentelemetry/exporter-prometheus": "^0.53.0",
|
"@opentelemetry/exporter-prometheus": "^0.53.0",
|
||||||
"@opentelemetry/sdk-node": "^0.53.0",
|
"@opentelemetry/sdk-node": "^0.53.0",
|
||||||
"@react-email/components": "^0.0.24",
|
"@react-email/components": "^0.0.23",
|
||||||
"@socket.io/redis-adapter": "^8.3.0",
|
"@socket.io/redis-adapter": "^8.3.0",
|
||||||
"archiver": "^7.0.0",
|
"archiver": "^7.0.0",
|
||||||
"async-lock": "^1.4.0",
|
"async-lock": "^1.4.0",
|
||||||
@@ -5070,9 +5070,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-email/code-block": {
|
"node_modules/@react-email/code-block": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.7.tgz",
|
||||||
"integrity": "sha512-WbuAEpTnB262i9C3SGPmmErgZ4iU5KIpqLUjr7uBJijqldLqZc5x39e8wPWaRdF7NLcShmrc/+G7GJgI1bdC5w==",
|
"integrity": "sha512-3lYLwn9rK16I4JmTR/sTzAJMVHzUmmcT1PT27+TXnQyBCfpfDV+VockSg1qhsgCusA/u6j0C97BMsa96AWEbbw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prismjs": "1.29.0"
|
"prismjs": "1.29.0"
|
||||||
},
|
},
|
||||||
@@ -5106,13 +5106,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-email/components": {
|
"node_modules/@react-email/components": {
|
||||||
"version": "0.0.24",
|
"version": "0.0.23",
|
||||||
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.24.tgz",
|
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.23.tgz",
|
||||||
"integrity": "sha512-/DNmfTREaT59UFdkHoIK3BewJ214LfRxmduiil3m7POj+gougkItANu1+BMmgbUATxjf7jH1WoBxo9x/rhFEFw==",
|
"integrity": "sha512-RcBoffx2IZG6quLBXo5sj3fF47rKmmkiMhG1ZBua4nFjHYlmW8j1uUMyO5HNglxIF9E52NYq4sF7XeZRp9jYjg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-email/body": "0.0.10",
|
"@react-email/body": "0.0.10",
|
||||||
"@react-email/button": "0.0.17",
|
"@react-email/button": "0.0.17",
|
||||||
"@react-email/code-block": "0.0.8",
|
"@react-email/code-block": "0.0.7",
|
||||||
"@react-email/code-inline": "0.0.4",
|
"@react-email/code-inline": "0.0.4",
|
||||||
"@react-email/column": "0.0.12",
|
"@react-email/column": "0.0.12",
|
||||||
"@react-email/container": "0.0.14",
|
"@react-email/container": "0.0.14",
|
||||||
@@ -5125,7 +5125,7 @@
|
|||||||
"@react-email/link": "0.0.10",
|
"@react-email/link": "0.0.10",
|
||||||
"@react-email/markdown": "0.0.12",
|
"@react-email/markdown": "0.0.12",
|
||||||
"@react-email/preview": "0.0.11",
|
"@react-email/preview": "0.0.11",
|
||||||
"@react-email/render": "1.0.1",
|
"@react-email/render": "1.0.0",
|
||||||
"@react-email/row": "0.0.10",
|
"@react-email/row": "0.0.10",
|
||||||
"@react-email/section": "0.0.14",
|
"@react-email/section": "0.0.14",
|
||||||
"@react-email/tailwind": "0.1.0",
|
"@react-email/tailwind": "0.1.0",
|
||||||
@@ -5249,9 +5249,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-email/render": {
|
"node_modules/@react-email/render": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.0.tgz",
|
||||||
"integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==",
|
"integrity": "sha512-seN2p3JRUSZhwIUiymh9N6ZfhRZ14ywOraQqAokY63DkDeHZW2pA2a6nWpNc/igfOcNyt09Wsoi1Aj0esxhdzw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"html-to-text": "9.0.5",
|
"html-to-text": "9.0.5",
|
||||||
"js-beautify": "^1.14.11",
|
"js-beautify": "^1.14.11",
|
||||||
@@ -19280,9 +19280,9 @@
|
|||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@react-email/code-block": {
|
"@react-email/code-block": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.7.tgz",
|
||||||
"integrity": "sha512-WbuAEpTnB262i9C3SGPmmErgZ4iU5KIpqLUjr7uBJijqldLqZc5x39e8wPWaRdF7NLcShmrc/+G7GJgI1bdC5w==",
|
"integrity": "sha512-3lYLwn9rK16I4JmTR/sTzAJMVHzUmmcT1PT27+TXnQyBCfpfDV+VockSg1qhsgCusA/u6j0C97BMsa96AWEbbw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"prismjs": "1.29.0"
|
"prismjs": "1.29.0"
|
||||||
}
|
}
|
||||||
@@ -19300,13 +19300,13 @@
|
|||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@react-email/components": {
|
"@react-email/components": {
|
||||||
"version": "0.0.24",
|
"version": "0.0.23",
|
||||||
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.24.tgz",
|
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.23.tgz",
|
||||||
"integrity": "sha512-/DNmfTREaT59UFdkHoIK3BewJ214LfRxmduiil3m7POj+gougkItANu1+BMmgbUATxjf7jH1WoBxo9x/rhFEFw==",
|
"integrity": "sha512-RcBoffx2IZG6quLBXo5sj3fF47rKmmkiMhG1ZBua4nFjHYlmW8j1uUMyO5HNglxIF9E52NYq4sF7XeZRp9jYjg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@react-email/body": "0.0.10",
|
"@react-email/body": "0.0.10",
|
||||||
"@react-email/button": "0.0.17",
|
"@react-email/button": "0.0.17",
|
||||||
"@react-email/code-block": "0.0.8",
|
"@react-email/code-block": "0.0.7",
|
||||||
"@react-email/code-inline": "0.0.4",
|
"@react-email/code-inline": "0.0.4",
|
||||||
"@react-email/column": "0.0.12",
|
"@react-email/column": "0.0.12",
|
||||||
"@react-email/container": "0.0.14",
|
"@react-email/container": "0.0.14",
|
||||||
@@ -19319,7 +19319,7 @@
|
|||||||
"@react-email/link": "0.0.10",
|
"@react-email/link": "0.0.10",
|
||||||
"@react-email/markdown": "0.0.12",
|
"@react-email/markdown": "0.0.12",
|
||||||
"@react-email/preview": "0.0.11",
|
"@react-email/preview": "0.0.11",
|
||||||
"@react-email/render": "1.0.1",
|
"@react-email/render": "1.0.0",
|
||||||
"@react-email/row": "0.0.10",
|
"@react-email/row": "0.0.10",
|
||||||
"@react-email/section": "0.0.14",
|
"@react-email/section": "0.0.14",
|
||||||
"@react-email/tailwind": "0.1.0",
|
"@react-email/tailwind": "0.1.0",
|
||||||
@@ -19389,9 +19389,9 @@
|
|||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@react-email/render": {
|
"@react-email/render": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.0.tgz",
|
||||||
"integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==",
|
"integrity": "sha512-seN2p3JRUSZhwIUiymh9N6ZfhRZ14ywOraQqAokY63DkDeHZW2pA2a6nWpNc/igfOcNyt09Wsoi1Aj0esxhdzw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"html-to-text": "9.0.5",
|
"html-to-text": "9.0.5",
|
||||||
"js-beautify": "^1.14.11",
|
"js-beautify": "^1.14.11",
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
"@opentelemetry/context-async-hooks": "^1.24.0",
|
"@opentelemetry/context-async-hooks": "^1.24.0",
|
||||||
"@opentelemetry/exporter-prometheus": "^0.53.0",
|
"@opentelemetry/exporter-prometheus": "^0.53.0",
|
||||||
"@opentelemetry/sdk-node": "^0.53.0",
|
"@opentelemetry/sdk-node": "^0.53.0",
|
||||||
"@react-email/components": "^0.0.24",
|
"@react-email/components": "^0.0.23",
|
||||||
"@socket.io/redis-adapter": "^8.3.0",
|
"@socket.io/redis-adapter": "^8.3.0",
|
||||||
"archiver": "^7.0.0",
|
"archiver": "^7.0.0",
|
||||||
"async-lock": "^1.4.0",
|
"async-lock": "^1.4.0",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export class AddAssetChecksum1661881837496 implements MigrationInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_64c507300988dd1764f9a6530c"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_64c507300988dd1764f9a6530c"`);
|
||||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "checksum"`);
|
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "checksum"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export class CreateTagsTable1670257571385 implements MigrationInterface {
|
|||||||
await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42"`);
|
await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42"`);
|
||||||
await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9"`);
|
await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9"`);
|
||||||
await queryRunner.query(`ALTER TABLE "tags" DROP CONSTRAINT "FK_92e67dc508c705dd66c94615576"`);
|
await queryRunner.query(`ALTER TABLE "tags" DROP CONSTRAINT "FK_92e67dc508c705dd66c94615576"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_e99f31ea4cdf3a2c35c7287eb4"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_e99f31ea4cdf3a2c35c7287eb4"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_f8e8a9e893cb5c54907f1b798e"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_f8e8a9e893cb5c54907f1b798e"`);
|
||||||
await queryRunner.query(`DROP TABLE "tag_asset"`);
|
await queryRunner.query(`DROP TABLE "tag_asset"`);
|
||||||
await queryRunner.query(`DROP TABLE "tags"`);
|
await queryRunner.query(`DROP TABLE "tags"`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ export class AddSharedLinkTable1673150490490 implements MigrationInterface {
|
|||||||
await queryRunner.query(`ALTER TABLE "shared_link__asset" DROP CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab"`);
|
await queryRunner.query(`ALTER TABLE "shared_link__asset" DROP CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab"`);
|
||||||
await queryRunner.query(`ALTER TABLE "shared_link__asset" DROP CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66"`);
|
await queryRunner.query(`ALTER TABLE "shared_link__asset" DROP CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66"`);
|
||||||
await queryRunner.query(`ALTER TABLE "shared_links" DROP CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66"`);
|
await queryRunner.query(`ALTER TABLE "shared_links" DROP CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_c9fab4aa97ffd1b034f3d6581a"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_c9fab4aa97ffd1b034f3d6581a"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_5b7decce6c8d3db9593d6111a6"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_5b7decce6c8d3db9593d6111a6"`);
|
||||||
await queryRunner.query(`DROP TABLE "shared_link__asset"`);
|
await queryRunner.query(`DROP TABLE "shared_link__asset"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_sharedlink_key"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_sharedlink_key"`);
|
||||||
await queryRunner.query(`DROP TABLE "shared_links"`);
|
await queryRunner.query(`DROP TABLE "shared_links"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ export class FixAlbumEntityTypeORM1675812532822 implements MigrationInterface {
|
|||||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621"`);
|
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621"`);
|
||||||
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "FK_427c350ad49bd3935a50baab737"`);
|
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "FK_427c350ad49bd3935a50baab737"`);
|
||||||
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06"`);
|
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_427c350ad49bd3935a50baab73"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_427c350ad49bd3935a50baab73"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_f48513bf9bccefd6ff3ad30bd0"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_f48513bf9bccefd6ff3ad30bd0"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_e590fa396c6898fcd4a50e4092"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_e590fa396c6898fcd4a50e4092"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_4bd1303d199f4e72ccdf998c62"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_4bd1303d199f4e72ccdf998c62"`);
|
||||||
|
|
||||||
await queryRunner.query(`ALTER TABLE "albums" DROP CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4"`);
|
await queryRunner.query(`ALTER TABLE "albums" DROP CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4"`);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class AppleContentIdentifier1676437878377 implements MigrationInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_live_photo_cid"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_live_photo_cid"`);
|
||||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "livePhotoCID"`);
|
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "livePhotoCID"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export class ExifEntityDefinitionFixes1676848629119 implements MigrationInterfac
|
|||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "description" SET NOT NULL`);
|
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "description" SET NOT NULL`);
|
||||||
|
|
||||||
await queryRunner.query(`DROP INDEX "IDX_c0117fdbc50b917ef9067740c4"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_c0117fdbc50b917ef9067740c4"`);
|
||||||
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "PK_28663352d85078ad0046dafafaa"`);
|
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "PK_28663352d85078ad0046dafafaa"`);
|
||||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "id"`);
|
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "id"`);
|
||||||
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "FK_c0117fdbc50b917ef9067740c44"`);
|
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "FK_c0117fdbc50b917ef9067740c44"`);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export class SmartInfoEntityDefinitionFixes1676852143506 implements MigrationInt
|
|||||||
name = 'SmartInfoEntityDefinitionFixes1676852143506'
|
name = 'SmartInfoEntityDefinitionFixes1676852143506'
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_5e3753aadd956110bf3ec0244a"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_5e3753aadd956110bf3ec0244a"`);
|
||||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "PK_0beace66440e9713f5c40470e46"`);
|
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "PK_0beace66440e9713f5c40470e46"`);
|
||||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP COLUMN "id"`);
|
await queryRunner.query(`ALTER TABLE "smart_info" DROP COLUMN "id"`);
|
||||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "FK_5e3753aadd956110bf3ec0244ac"`);
|
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "FK_5e3753aadd956110bf3ec0244ac"`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class AddIndexForAlbumInSharedLinkTable1677535643119 implements Migration
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_sharedlink_albumId"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_sharedlink_albumId"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ export class RequireChecksumNotNull1684328185099 implements MigrationInterface {
|
|||||||
name = 'removeNotNullFromChecksumIndex1684328185099';
|
name = 'removeNotNullFromChecksumIndex1684328185099';
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_64c507300988dd1764f9a6530c"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_64c507300988dd1764f9a6530c"`);
|
||||||
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "checksum" SET NOT NULL`);
|
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "checksum" SET NOT NULL`);
|
||||||
await queryRunner.query(`CREATE INDEX "IDX_8d3efe36c0755849395e6ea866" ON "assets" ("checksum") `);
|
await queryRunner.query(`CREATE INDEX "IDX_8d3efe36c0755849395e6ea866" ON "assets" ("checksum") `);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_8d3efe36c0755849395e6ea866"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_8d3efe36c0755849395e6ea866"`);
|
||||||
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "checksum" DROP NOT NULL`);
|
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "checksum" DROP NOT NULL`);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`CREATE INDEX "IDX_64c507300988dd1764f9a6530c" ON "assets" ("checksum") WHERE ('checksum' IS NOT NULL)`,
|
`CREATE INDEX "IDX_64c507300988dd1764f9a6530c" ON "assets" ("checksum") WHERE ('checksum' IS NOT NULL)`,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class AddAuditTable1692804658140 implements MigrationInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_ownerId_createdAt"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_ownerId_createdAt"`);
|
||||||
await queryRunner.query(`DROP TABLE "audit"`);
|
await queryRunner.query(`DROP TABLE "audit"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ export class AddOriginalPathIndex1696888644031 implements MigrationInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_originalPath_libraryId"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_originalPath_libraryId"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class AddActivity1698693294632 implements MigrationInterface {
|
|||||||
await queryRunner.query(`ALTER TABLE "activity" DROP CONSTRAINT "FK_1af8519996fbfb3684b58df280b"`);
|
await queryRunner.query(`ALTER TABLE "activity" DROP CONSTRAINT "FK_1af8519996fbfb3684b58df280b"`);
|
||||||
await queryRunner.query(`ALTER TABLE "activity" DROP CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea"`);
|
await queryRunner.query(`ALTER TABLE "activity" DROP CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea"`);
|
||||||
await queryRunner.query(`ALTER TABLE "activity" DROP CONSTRAINT "FK_8091ea76b12338cb4428d33d782"`);
|
await queryRunner.query(`ALTER TABLE "activity" DROP CONSTRAINT "FK_8091ea76b12338cb4428d33d782"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_activity_like"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_activity_like"`);
|
||||||
await queryRunner.query(`DROP TABLE "activity"`);
|
await queryRunner.query(`DROP TABLE "activity"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export class AddAssetFaceIndicies1700752078178 implements MigrationInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_b463c8edb01364bf2beba08ef1"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_b463c8edb01364bf2beba08ef1"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_bf339a24070dac7e71304ec530"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_bf339a24070dac7e71304ec530"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class AddExifCityIndex1701665867595 implements MigrationInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "exif_city"`);
|
await queryRunner.query(`DROP INDEX "public"."exif_city"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class AddAutoStackId1703035138085 implements MigrationInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_auto_stack_id"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_auto_stack_id"`);
|
||||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "autoStackId"`);
|
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "autoStackId"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ export class AddOriginalFileNameIndex1705306747072 implements MigrationInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`DROP INDEX "IDX_4d66e76dada1ca180f67a205dc"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_4d66e76dada1ca180f67a205dc"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export class CreateAssetStackTable1705197515600 implements MigrationInterface {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// update constraints
|
// update constraints
|
||||||
await queryRunner.query(`DROP INDEX "IDX_b463c8edb01364bf2beba08ef1"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_b463c8edb01364bf2beba08ef1"`);
|
||||||
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_b463c8edb01364bf2beba08ef19"`);
|
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_b463c8edb01364bf2beba08ef19"`);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`ALTER TABLE "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack"("id") ON DELETE SET NULL ON UPDATE CASCADE`,
|
`ALTER TABLE "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack"("id") ON DELETE SET NULL ON UPDATE CASCADE`,
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export class AddMemoryTable1711637874206 implements MigrationInterface {
|
|||||||
await queryRunner.query(`ALTER TABLE "memories_assets_assets" DROP CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f"`);
|
await queryRunner.query(`ALTER TABLE "memories_assets_assets" DROP CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f"`);
|
||||||
await queryRunner.query(`ALTER TABLE "memories_assets_assets" DROP CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e"`);
|
await queryRunner.query(`ALTER TABLE "memories_assets_assets" DROP CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e"`);
|
||||||
await queryRunner.query(`ALTER TABLE "memories" DROP CONSTRAINT "FK_575842846f0c28fa5da46c99b19"`);
|
await queryRunner.query(`ALTER TABLE "memories" DROP CONSTRAINT "FK_575842846f0c28fa5da46c99b19"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_6942ecf52d75d4273de19d2c16"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_6942ecf52d75d4273de19d2c16"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_984e5c9ab1f04d34538cd32334"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_984e5c9ab1f04d34538cd32334"`);
|
||||||
await queryRunner.query(`DROP TABLE "memories_assets_assets"`);
|
await queryRunner.query(`DROP TABLE "memories_assets_assets"`);
|
||||||
await queryRunner.query(`DROP TABLE "memories"`);
|
await queryRunner.query(`DROP TABLE "memories"`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ export class RemoveLibraryType1715804005643 implements MigrationInterface {
|
|||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c"`);
|
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c"`);
|
||||||
await queryRunner.query(`DROP INDEX "UQ_assets_owner_library_checksum"`);
|
await queryRunner.query(`DROP INDEX "public"."UQ_assets_owner_library_checksum"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_originalPath_libraryId"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_originalPath_libraryId"`);
|
||||||
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "libraryId" DROP NOT NULL`);
|
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "libraryId" DROP NOT NULL`);
|
||||||
await queryRunner.query(`
|
await queryRunner.query(`
|
||||||
UPDATE "assets"
|
UPDATE "assets"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class AddAssetFilesTable1724101822106 implements MigrationInterface {
|
|||||||
await queryRunner.query(`UPDATE "assets" SET "thumbnailPath" = "asset_files".path FROM "asset_files" WHERE "assets".id = "asset_files".assetId AND "asset_files".type = 'thumbnail'`);
|
await queryRunner.query(`UPDATE "assets" SET "thumbnailPath" = "asset_files".path FROM "asset_files" WHERE "assets".id = "asset_files".assetId AND "asset_files".type = 'thumbnail'`);
|
||||||
|
|
||||||
await queryRunner.query(`ALTER TABLE "asset_files" DROP CONSTRAINT "FK_e3e103a5f1d8bc8402999286040"`);
|
await queryRunner.query(`ALTER TABLE "asset_files" DROP CONSTRAINT "FK_e3e103a5f1d8bc8402999286040"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_asset_files_assetId"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_asset_files_assetId"`);
|
||||||
await queryRunner.query(`DROP TABLE "asset_files"`);
|
await queryRunner.query(`DROP TABLE "asset_files"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ export class NestedTagTable1724790460210 implements MigrationInterface {
|
|||||||
await queryRunner.query(`ALTER TABLE "tags" ADD "name" character varying NOT NULL`);
|
await queryRunner.query(`ALTER TABLE "tags" ADD "name" character varying NOT NULL`);
|
||||||
await queryRunner.query(`ALTER TABLE "tags" ADD "type" character varying NOT NULL`);
|
await queryRunner.query(`ALTER TABLE "tags" ADD "type" character varying NOT NULL`);
|
||||||
await queryRunner.query(`ALTER TABLE "tags" ADD "renameTagId" uuid`);
|
await queryRunner.query(`ALTER TABLE "tags" ADD "renameTagId" uuid`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_b1a2a7ed45c29179b5ad51548a"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_b1a2a7ed45c29179b5ad51548a"`);
|
||||||
await queryRunner.query(`DROP INDEX "IDX_15fbcbc67663c6bfc07b354c22"`);
|
await queryRunner.query(`DROP INDEX "public"."IDX_15fbcbc67663c6bfc07b354c22"`);
|
||||||
await queryRunner.query(`DROP TABLE "tags_closure"`);
|
await queryRunner.query(`DROP TABLE "tags_closure"`);
|
||||||
await queryRunner.query(`ALTER TABLE "tags" ADD CONSTRAINT "UQ_tag_name_userId" UNIQUE ("name", "userId")`);
|
await queryRunner.query(`ALTER TABLE "tags" ADD CONSTRAINT "UQ_tag_name_userId" UNIQUE ("name", "userId")`);
|
||||||
await queryRunner.query(`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
await queryRunner.query(`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
export class RemoveThumbailAtForMissingThumbnails1725327902980 implements MigrationInterface {
|
export class RemoveThumbailAtForMissingThumbnails1725327902980 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(
|
|
||||||
`UPDATE "asset_job_status" j SET "thumbnailAt" = NULL WHERE j."thumbnailAt" IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM asset_files f WHERE j."assetId" = f."assetId" AND f."type" = 'thumbnail' AND f."path" IS NOT NULL )`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
// do nothing
|
await queryRunner.query(`UPDATE "asset_job_status" j SET "thumbnailAt" = NULL WHERE j."thumbnailAt" IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM public.asset_files f WHERE j."assetId" = f."assetId" AND f."type" = 'thumbnail' AND f."path" IS NOT NULL )`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async down(): Promise<void> {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export class MediaService {
|
|||||||
async handleGeneratePreview({ id }: IEntityJob): Promise<JobStatus> {
|
async handleGeneratePreview({ id }: IEntityJob): Promise<JobStatus> {
|
||||||
const [{ image }, [asset]] = await Promise.all([
|
const [{ image }, [asset]] = await Promise.all([
|
||||||
this.configCore.getConfig({ withCache: true }),
|
this.configCore.getConfig({ withCache: true }),
|
||||||
this.assetRepository.getByIds([id], { exifInfo: true, files: true }),
|
this.assetRepository.getByIds([id], { exifInfo: true }),
|
||||||
]);
|
]);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
return JobStatus.FAILED;
|
return JobStatus.FAILED;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BinaryField, ExifDateTime } from 'exiftool-vendored';
|
import { BinaryField } from 'exiftool-vendored';
|
||||||
import { randomBytes } from 'node:crypto';
|
import { randomBytes } from 'node:crypto';
|
||||||
import { Stats } from 'node:fs';
|
import { Stats } from 'node:fs';
|
||||||
import { constants } from 'node:fs/promises';
|
import { constants } from 'node:fs/promises';
|
||||||
@@ -434,66 +434,6 @@ describe(MetadataService.name, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore Keywords when TagsList is present', async () => {
|
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
|
||||||
metadataMock.readTags.mockResolvedValue({ Keywords: 'Child', TagsList: ['Parent/Child'] });
|
|
||||||
tagMock.upsertValue.mockResolvedValue(tagStub.parent);
|
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
|
||||||
|
|
||||||
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(1, { userId: 'user-id', value: 'Parent', parent: undefined });
|
|
||||||
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(2, {
|
|
||||||
userId: 'user-id',
|
|
||||||
value: 'Parent/Child',
|
|
||||||
parent: tagStub.parent,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract hierarchy from HierarchicalSubject', async () => {
|
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
|
||||||
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Parent|Child'] });
|
|
||||||
tagMock.upsertValue.mockResolvedValueOnce(tagStub.parent);
|
|
||||||
tagMock.upsertValue.mockResolvedValueOnce(tagStub.child);
|
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
|
||||||
|
|
||||||
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(1, { userId: 'user-id', value: 'Parent', parent: undefined });
|
|
||||||
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(2, {
|
|
||||||
userId: 'user-id',
|
|
||||||
value: 'Parent/Child',
|
|
||||||
parent: tagStub.parent,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract ignore / characters in a HierarchicalSubject tag', async () => {
|
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
|
||||||
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Mom/Dad'] });
|
|
||||||
tagMock.upsertValue.mockResolvedValueOnce(tagStub.parent);
|
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
|
||||||
|
|
||||||
expect(tagMock.upsertValue).toHaveBeenCalledWith({
|
|
||||||
userId: 'user-id',
|
|
||||||
value: 'Mom|Dad',
|
|
||||||
parent: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should ignore HierarchicalSubject when TagsList is present', async () => {
|
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
|
||||||
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Parent2|Child2'], TagsList: ['Parent/Child'] });
|
|
||||||
tagMock.upsertValue.mockResolvedValue(tagStub.parent);
|
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
|
||||||
|
|
||||||
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(1, { userId: 'user-id', value: 'Parent', parent: undefined });
|
|
||||||
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(2, {
|
|
||||||
userId: 'user-id',
|
|
||||||
value: 'Parent/Child',
|
|
||||||
parent: tagStub.parent,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not apply motion photos if asset is video', async () => {
|
it('should not apply motion photos if asset is video', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoMotionAsset, isVisible: true }]);
|
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoMotionAsset, isVisible: true }]);
|
||||||
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||||
@@ -746,8 +686,6 @@ describe(MetadataService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should save all metadata', async () => {
|
it('should save all metadata', async () => {
|
||||||
const dateForTest = new Date('1970-01-01T00:00:00.000-11:30');
|
|
||||||
|
|
||||||
const tags: ImmichTags = {
|
const tags: ImmichTags = {
|
||||||
BitsPerSample: 1,
|
BitsPerSample: 1,
|
||||||
ComponentBitDepth: 1,
|
ComponentBitDepth: 1,
|
||||||
@@ -755,7 +693,7 @@ describe(MetadataService.name, () => {
|
|||||||
BitDepth: 1,
|
BitDepth: 1,
|
||||||
ColorBitDepth: 1,
|
ColorBitDepth: 1,
|
||||||
ColorSpace: '1',
|
ColorSpace: '1',
|
||||||
DateTimeOriginal: ExifDateTime.fromISO(dateForTest.toISOString()),
|
DateTimeOriginal: new Date('1970-01-01').toISOString(),
|
||||||
ExposureTime: '100ms',
|
ExposureTime: '100ms',
|
||||||
FocalLength: 20,
|
FocalLength: 20,
|
||||||
ImageDescription: 'test description',
|
ImageDescription: 'test description',
|
||||||
@@ -764,11 +702,11 @@ describe(MetadataService.name, () => {
|
|||||||
MediaGroupUUID: 'livePhoto',
|
MediaGroupUUID: 'livePhoto',
|
||||||
Make: 'test-factory',
|
Make: 'test-factory',
|
||||||
Model: "'mockel'",
|
Model: "'mockel'",
|
||||||
ModifyDate: ExifDateTime.fromISO(dateForTest.toISOString()),
|
ModifyDate: new Date('1970-01-01').toISOString(),
|
||||||
Orientation: 0,
|
Orientation: 0,
|
||||||
ProfileDescription: 'extensive description',
|
ProfileDescription: 'extensive description',
|
||||||
ProjectionType: 'equirectangular',
|
ProjectionType: 'equirectangular',
|
||||||
tz: 'UTC-11:30',
|
tz: '+02:00',
|
||||||
Rating: 3,
|
Rating: 3,
|
||||||
};
|
};
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
@@ -781,7 +719,7 @@ describe(MetadataService.name, () => {
|
|||||||
bitsPerSample: expect.any(Number),
|
bitsPerSample: expect.any(Number),
|
||||||
autoStackId: null,
|
autoStackId: null,
|
||||||
colorspace: tags.ColorSpace,
|
colorspace: tags.ColorSpace,
|
||||||
dateTimeOriginal: dateForTest,
|
dateTimeOriginal: new Date('1970-01-01'),
|
||||||
description: tags.ImageDescription,
|
description: tags.ImageDescription,
|
||||||
exifImageHeight: null,
|
exifImageHeight: null,
|
||||||
exifImageWidth: null,
|
exifImageWidth: null,
|
||||||
@@ -807,37 +745,11 @@ describe(MetadataService.name, () => {
|
|||||||
expect(assetMock.update).toHaveBeenCalledWith({
|
expect(assetMock.update).toHaveBeenCalledWith({
|
||||||
id: assetStub.image.id,
|
id: assetStub.image.id,
|
||||||
duration: null,
|
duration: null,
|
||||||
fileCreatedAt: dateForTest,
|
fileCreatedAt: new Date('1970-01-01'),
|
||||||
localDateTime: dateForTest,
|
localDateTime: new Date('1970-01-01'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract +00:00 timezone from raw value', async () => {
|
|
||||||
// exiftool-vendored returns "no timezone" information even though "+00:00" might be set explicitly
|
|
||||||
// https://github.com/photostructure/exiftool-vendored.js/issues/203
|
|
||||||
|
|
||||||
// this only tests our assumptions of exiftool-vendored, demonstrating the issue
|
|
||||||
const someDate = '2024-09-01T00:00:00.000';
|
|
||||||
expect(ExifDateTime.fromISO(someDate + 'Z')?.zone).toBe('UTC');
|
|
||||||
expect(ExifDateTime.fromISO(someDate + '+00:00')?.zone).toBe('UTC'); // this is the issue, should be UTC+0
|
|
||||||
expect(ExifDateTime.fromISO(someDate + '+04:00')?.zone).toBe('UTC+4');
|
|
||||||
|
|
||||||
const tags: ImmichTags = {
|
|
||||||
DateTimeOriginal: ExifDateTime.fromISO(someDate + '+00:00'),
|
|
||||||
tz: undefined,
|
|
||||||
};
|
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
|
||||||
metadataMock.readTags.mockResolvedValue(tags);
|
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
|
||||||
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
|
|
||||||
expect(assetMock.upsertExif).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
timeZone: 'UTC+0',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract duration', async () => {
|
it('should extract duration', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([{ ...assetStub.video }]);
|
assetMock.getByIds.mockResolvedValue([{ ...assetStub.video }]);
|
||||||
mediaMock.probe.mockResolvedValue({
|
mediaMock.probe.mockResolvedValue({
|
||||||
|
|||||||
@@ -355,17 +355,9 @@ export class MetadataService {
|
|||||||
const tags: unknown[] = [];
|
const tags: unknown[] = [];
|
||||||
if (exifTags.TagsList) {
|
if (exifTags.TagsList) {
|
||||||
tags.push(...exifTags.TagsList);
|
tags.push(...exifTags.TagsList);
|
||||||
} else if (exifTags.HierarchicalSubject) {
|
}
|
||||||
tags.push(
|
|
||||||
exifTags.HierarchicalSubject.map((tag) =>
|
if (exifTags.Keywords) {
|
||||||
tag
|
|
||||||
// convert | to /
|
|
||||||
.replaceAll('/', '<PLACEHOLDER>')
|
|
||||||
.replaceAll('|', '/')
|
|
||||||
.replaceAll('<PLACEHOLDER>', '|'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (exifTags.Keywords) {
|
|
||||||
let keywords = exifTags.Keywords;
|
let keywords = exifTags.Keywords;
|
||||||
if (!Array.isArray(keywords)) {
|
if (!Array.isArray(keywords)) {
|
||||||
keywords = [keywords];
|
keywords = [keywords];
|
||||||
@@ -531,16 +523,12 @@ export class MetadataService {
|
|||||||
|
|
||||||
this.logger.verbose('Exif Tags', exifTags);
|
this.logger.verbose('Exif Tags', exifTags);
|
||||||
|
|
||||||
const dateTimeOriginalWithRawValue = this.getDateTimeOriginalWithRawValue(exifTags);
|
|
||||||
const dateTimeOriginal = dateTimeOriginalWithRawValue.exifDate ?? asset.fileCreatedAt;
|
|
||||||
const timeZone = this.getTimeZone(exifTags, dateTimeOriginalWithRawValue.rawValue);
|
|
||||||
|
|
||||||
const exifData = {
|
const exifData = {
|
||||||
// altitude: tags.GPSAltitude ?? null,
|
// altitude: tags.GPSAltitude ?? null,
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
bitsPerSample: this.getBitsPerSample(exifTags),
|
bitsPerSample: this.getBitsPerSample(exifTags),
|
||||||
colorspace: exifTags.ColorSpace ?? null,
|
colorspace: exifTags.ColorSpace ?? null,
|
||||||
dateTimeOriginal,
|
dateTimeOriginal: this.getDateTimeOriginal(exifTags) ?? asset.fileCreatedAt,
|
||||||
description: String(exifTags.ImageDescription || exifTags.Description || '').trim(),
|
description: String(exifTags.ImageDescription || exifTags.Description || '').trim(),
|
||||||
exifImageHeight: validate(exifTags.ImageHeight),
|
exifImageHeight: validate(exifTags.ImageHeight),
|
||||||
exifImageWidth: validate(exifTags.ImageWidth),
|
exifImageWidth: validate(exifTags.ImageWidth),
|
||||||
@@ -561,7 +549,7 @@ export class MetadataService {
|
|||||||
orientation: validate(exifTags.Orientation)?.toString() ?? null,
|
orientation: validate(exifTags.Orientation)?.toString() ?? null,
|
||||||
profileDescription: exifTags.ProfileDescription || null,
|
profileDescription: exifTags.ProfileDescription || null,
|
||||||
projectionType: exifTags.ProjectionType ? String(exifTags.ProjectionType).toUpperCase() : null,
|
projectionType: exifTags.ProjectionType ? String(exifTags.ProjectionType).toUpperCase() : null,
|
||||||
timeZone,
|
timeZone: exifTags.tz ?? null,
|
||||||
rating: exifTags.Rating ?? null,
|
rating: exifTags.Rating ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -582,25 +570,10 @@ export class MetadataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getDateTimeOriginal(tags: ImmichTags | Tags | null) {
|
private getDateTimeOriginal(tags: ImmichTags | Tags | null) {
|
||||||
return this.getDateTimeOriginalWithRawValue(tags).exifDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDateTimeOriginalWithRawValue(tags: ImmichTags | Tags | null): { exifDate: Date | null; rawValue: string } {
|
|
||||||
if (!tags) {
|
if (!tags) {
|
||||||
return { exifDate: null, rawValue: '' };
|
return null;
|
||||||
}
|
}
|
||||||
const first = firstDateTime(tags as Tags, EXIF_DATE_TAGS);
|
return exifDate(firstDateTime(tags as Tags, EXIF_DATE_TAGS));
|
||||||
return { exifDate: exifDate(first), rawValue: first?.rawValue ?? '' };
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTimeZone(exifTags: ImmichTags, rawValue: string) {
|
|
||||||
const timeZone = exifTags.tz ?? null;
|
|
||||||
if (timeZone == null && rawValue.endsWith('+00:00')) {
|
|
||||||
// exiftool-vendored returns "no timezone" information even though "+00:00" might be set explicitly
|
|
||||||
// https://github.com/photostructure/exiftool-vendored.js/issues/203
|
|
||||||
return 'UTC+0';
|
|
||||||
}
|
|
||||||
return timeZone;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getBitsPerSample(tags: ImmichTags): number | null {
|
private getBitsPerSample(tags: ImmichTags): number | null {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export type ShortcutOptions<T = HTMLElement> = {
|
|||||||
preventDefault?: boolean;
|
preventDefault?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const shouldIgnoreEvent = (event: KeyboardEvent | ClipboardEvent): boolean => {
|
export const shouldIgnoreShortcut = (event: KeyboardEvent): boolean => {
|
||||||
if (event.target === event.currentTarget) {
|
if (event.target === event.currentTarget) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ export const shortcuts = <T extends HTMLElement>(
|
|||||||
options: ShortcutOptions<T>[],
|
options: ShortcutOptions<T>[],
|
||||||
): ActionReturn<ShortcutOptions<T>[]> => {
|
): ActionReturn<ShortcutOptions<T>[]> => {
|
||||||
function onKeydown(event: KeyboardEvent) {
|
function onKeydown(event: KeyboardEvent) {
|
||||||
const ignoreShortcut = shouldIgnoreEvent(event);
|
const ignoreShortcut = shouldIgnoreShortcut(event);
|
||||||
for (const { shortcut, onShortcut, ignoreInputFields = true, preventDefault = true } of options) {
|
for (const { shortcut, onShortcut, ignoreInputFields = true, preventDefault = true } of options) {
|
||||||
if (ignoreInputFields && ignoreShortcut) {
|
if (ignoreInputFields && ignoreShortcut) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -10,25 +10,18 @@
|
|||||||
|
|
||||||
type ZoneOption = {
|
type ZoneOption = {
|
||||||
/**
|
/**
|
||||||
* Timezone name with offset
|
* Timezone name
|
||||||
*
|
*
|
||||||
* e.g. Asia/Jerusalem (+03:00)
|
* e.g. Asia/Jerusalem (+03:00)
|
||||||
*/
|
*/
|
||||||
label: string;
|
label: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timezone name
|
* Timezone offset
|
||||||
*
|
*
|
||||||
* e.g. Asia/Jerusalem
|
* e.g. UTC+01:00
|
||||||
*/
|
*/
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Timezone offset in minutes
|
|
||||||
*
|
|
||||||
* e.g. 300
|
|
||||||
*/
|
|
||||||
offsetMinutes: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const timezones: ZoneOption[] = Intl.supportedValuesOf('timeZone')
|
const timezones: ZoneOption[] = Intl.supportedValuesOf('timeZone')
|
||||||
@@ -44,23 +37,21 @@
|
|||||||
const offset = zone.toFormat('ZZ');
|
const offset = zone.toFormat('ZZ');
|
||||||
return {
|
return {
|
||||||
label: `${zone.zoneName} (${offset})`,
|
label: `${zone.zoneName} (${offset})`,
|
||||||
value: zone.zoneName,
|
value: 'UTC' + offset,
|
||||||
offsetMinutes: zone.offset,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialOption = timezones.find((item) => item.offsetMinutes === initialDate.offset);
|
const initialOption = timezones.find((item) => item.value === 'UTC' + initialDate.toFormat('ZZ'));
|
||||||
|
|
||||||
let selectedOption = initialOption && {
|
let selectedOption = initialOption && {
|
||||||
label: initialOption?.label || '',
|
label: initialOption?.label || '',
|
||||||
offsetMinutes: initialOption?.offsetMinutes || 0,
|
|
||||||
value: initialOption?.value || '',
|
value: initialOption?.value || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
let selectedDate = initialDate.toFormat("yyyy-MM-dd'T'HH:mm");
|
let selectedDate = initialDate.toFormat("yyyy-MM-dd'T'HH:mm");
|
||||||
|
|
||||||
// when changing the time zone, assume the configured date/time is meant for that time zone (instead of updating it)
|
// Keep local time if not it's really confusing
|
||||||
$: date = DateTime.fromISO(selectedDate, { zone: selectedOption?.value, setZone: true });
|
$: date = DateTime.fromISO(selectedDate).setZone(selectedOption?.value, { keepLocalTime: true });
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
cancel: void;
|
cancel: void;
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import ImmichLogo from './immich-logo.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { shouldIgnoreEvent } from '$lib/actions/shortcut';
|
|
||||||
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
||||||
import { fileUploadHandler } from '$lib/utils/file-uploader';
|
import { fileUploadHandler } from '$lib/utils/file-uploader';
|
||||||
import { isAlbumsRoute, isSharedLinkRoute } from '$lib/utils/navigation';
|
import { isAlbumsRoute, isSharedLinkRoute } from '$lib/utils/navigation';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import ImmichLogo from './immich-logo.svelte';
|
|
||||||
|
|
||||||
$: albumId = isAlbumsRoute($page.route?.id) ? $page.params.albumId : undefined;
|
$: albumId = isAlbumsRoute($page.route?.id) ? $page.params.albumId : undefined;
|
||||||
$: isShare = isSharedLinkRoute($page.route?.id);
|
$: isShare = isSharedLinkRoute($page.route?.id);
|
||||||
@@ -30,13 +29,7 @@
|
|||||||
await handleDataTransfer(e.dataTransfer);
|
await handleDataTransfer(e.dataTransfer);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPaste = (event: ClipboardEvent) => {
|
const onPaste = ({ clipboardData }: ClipboardEvent) => handleDataTransfer(clipboardData);
|
||||||
if (shouldIgnoreEvent(event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleDataTransfer(event.clipboardData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDataTransfer = async (dataTransfer?: DataTransfer | null) => {
|
const handleDataTransfer = async (dataTransfer?: DataTransfer | null) => {
|
||||||
if (!dataTransfer) {
|
if (!dataTransfer) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import SideBarLink from '$lib/components/shared-components/side-bar/side-bar-link.svelte';
|
import SideBarLink from '$lib/components/shared-components/side-bar/side-bar-link.svelte';
|
||||||
import SideBarSection from '$lib/components/shared-components/side-bar/side-bar-section.svelte';
|
import SideBarSection from '$lib/components/shared-components/side-bar/side-bar-section.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiSync } from '@mdi/js';
|
import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiSync, mdiTools } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
<SideBarLink title={$t('settings')} routeId={AppRoute.ADMIN_SETTINGS} icon={mdiCog} />
|
<SideBarLink title={$t('settings')} routeId={AppRoute.ADMIN_SETTINGS} icon={mdiCog} />
|
||||||
<SideBarLink title={$t('external_libraries')} routeId={AppRoute.ADMIN_LIBRARY_MANAGEMENT} icon={mdiBookshelf} />
|
<SideBarLink title={$t('external_libraries')} routeId={AppRoute.ADMIN_LIBRARY_MANAGEMENT} icon={mdiBookshelf} />
|
||||||
<SideBarLink title={$t('server_stats')} routeId={AppRoute.ADMIN_STATS} icon={mdiServer} />
|
<SideBarLink title={$t('server_stats')} routeId={AppRoute.ADMIN_STATS} icon={mdiServer} />
|
||||||
|
<SideBarLink title={$t('repair')} routeId={AppRoute.ADMIN_REPAIR} icon={mdiTools} preloadData={false} />
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<BottomInfo />
|
<BottomInfo />
|
||||||
|
|||||||
@@ -284,7 +284,6 @@ export const langs = [
|
|||||||
{ name: 'Lithuanian', code: 'lt', loader: () => import('$lib/i18n/lt.json') },
|
{ name: 'Lithuanian', code: 'lt', loader: () => import('$lib/i18n/lt.json') },
|
||||||
{ name: 'Latvian', code: 'lv', loader: () => import('$lib/i18n/lv.json') },
|
{ name: 'Latvian', code: 'lv', loader: () => import('$lib/i18n/lv.json') },
|
||||||
{ name: 'Mongolian', code: 'mn', loader: () => import('$lib/i18n/mn.json') },
|
{ name: 'Mongolian', code: 'mn', loader: () => import('$lib/i18n/mn.json') },
|
||||||
{ name: 'Malay', code: 'ms', loader: () => import('$lib/i18n/ms.json') },
|
|
||||||
{ name: 'Norwegian Bokmål', code: 'nb-NO', weblateCode: 'nb_NO', loader: () => import('$lib/i18n/nb_NO.json') },
|
{ name: 'Norwegian Bokmål', code: 'nb-NO', weblateCode: 'nb_NO', loader: () => import('$lib/i18n/nb_NO.json') },
|
||||||
{ name: 'Dutch', code: 'nl', loader: () => import('$lib/i18n/nl.json') },
|
{ name: 'Dutch', code: 'nl', loader: () => import('$lib/i18n/nl.json') },
|
||||||
{ name: 'Polish', code: 'pl', loader: () => import('$lib/i18n/pl.json') },
|
{ name: 'Polish', code: 'pl', loader: () => import('$lib/i18n/pl.json') },
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
$: pathSegments = data.path ? data.path.split('/') : [];
|
$: pathSegments = data.path ? data.path.split('/') : [];
|
||||||
$: tree = buildTree($foldersStore?.uniquePaths || []);
|
$: tree = buildTree($foldersStore?.uniquePaths || []);
|
||||||
$: currentPath = $page.url.searchParams.get(QueryParameter.PATH) || '';
|
$: currentPath = $page.url.searchParams.get(QueryParameter.PATH) || '';
|
||||||
$: currentTreeItems = currentPath ? data.currentFolders : Object.keys(tree);
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await foldersStore.fetchUniquePaths();
|
await foldersStore.fetchUniquePaths();
|
||||||
@@ -64,7 +63,7 @@
|
|||||||
<Breadcrumbs {pathSegments} icon={mdiFolderHome} title={$t('folders')} {getLink} />
|
<Breadcrumbs {pathSegments} icon={mdiFolderHome} title={$t('folders')} {getLink} />
|
||||||
|
|
||||||
<section class="mt-2">
|
<section class="mt-2">
|
||||||
<TreeItemThumbnails items={currentTreeItems} icon={mdiFolder} onClick={handleNavigation} />
|
<TreeItemThumbnails items={data.currentFolders} icon={mdiFolder} onClick={handleNavigation} />
|
||||||
|
|
||||||
<!-- Assets -->
|
<!-- Assets -->
|
||||||
{#if data.pathAssets && data.pathAssets.length > 0}
|
{#if data.pathAssets && data.pathAssets.length > 0}
|
||||||
|
|||||||
Reference in New Issue
Block a user