mirror of
https://github.com/immich-app/immich.git
synced 2026-01-11 12:45:28 -08:00
Compare commits
1 Commits
perf/optim
...
feat/enabl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92421eb045 |
@@ -171,8 +171,16 @@ class RemoteAlbumService {
|
||||
|
||||
Future<List<RemoteAlbum>> _sortByNewestAsset(List<RemoteAlbum> albums) async {
|
||||
// map album IDs to their newest asset dates
|
||||
final albumIds = albums.map((e) => e.id).toList();
|
||||
final assetTimestamps = await _repository.getNewestAssetTimestampForAlbums(albumIds);
|
||||
final Map<String, Future<DateTime?>> assetTimestampFutures = {};
|
||||
for (final album in albums) {
|
||||
assetTimestampFutures[album.id] = _repository.getNewestAssetTimestamp(album.id);
|
||||
}
|
||||
|
||||
// await all database queries
|
||||
final entries = await Future.wait(
|
||||
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
|
||||
);
|
||||
final assetTimestamps = Map.fromEntries(entries);
|
||||
|
||||
final sorted = albums.sorted((a, b) {
|
||||
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
|
||||
@@ -185,8 +193,15 @@ class RemoteAlbumService {
|
||||
|
||||
Future<List<RemoteAlbum>> _sortByOldestAsset(List<RemoteAlbum> albums) async {
|
||||
// map album IDs to their oldest asset dates
|
||||
final albumIds = albums.map((e) => e.id).toList();
|
||||
final assetTimestamps = await _repository.getOldestAssetTimestampForAlbums(albumIds);
|
||||
final Map<String, Future<DateTime?>> assetTimestampFutures = {
|
||||
for (final album in albums) album.id: _repository.getOldestAssetTimestamp(album.id),
|
||||
};
|
||||
|
||||
// await all database queries
|
||||
final entries = await Future.wait(
|
||||
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
|
||||
);
|
||||
final assetTimestamps = Map.fromEntries(entries);
|
||||
|
||||
final sorted = albums.sorted((a, b) {
|
||||
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
|
||||
|
||||
@@ -321,64 +321,26 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
||||
}).watchSingleOrNull();
|
||||
}
|
||||
|
||||
Future<Map<String, DateTime?>> getNewestAssetTimestampForAlbums(List<String> albumIds) async {
|
||||
if (albumIds.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
Future<DateTime?> getNewestAssetTimestamp(String albumId) {
|
||||
final query = _db.remoteAlbumAssetEntity.selectOnly()
|
||||
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
|
||||
..addColumns([_db.remoteAssetEntity.localDateTime.max()])
|
||||
..join([
|
||||
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
|
||||
]);
|
||||
|
||||
final results = <String, DateTime?>{};
|
||||
|
||||
// Chunk calls to avoid SQLite limit (default 999 variables)
|
||||
const chunkSize = 900;
|
||||
for (var i = 0; i < albumIds.length; i += chunkSize) {
|
||||
final end = (i + chunkSize < albumIds.length) ? i + chunkSize : albumIds.length;
|
||||
final subList = albumIds.sublist(i, end);
|
||||
|
||||
final query = _db.remoteAlbumAssetEntity.selectOnly()
|
||||
..where(_db.remoteAlbumAssetEntity.albumId.isIn(subList))
|
||||
..addColumns([_db.remoteAlbumAssetEntity.albumId, _db.remoteAssetEntity.localDateTime.max()])
|
||||
..join([
|
||||
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
|
||||
])
|
||||
..groupBy([_db.remoteAlbumAssetEntity.albumId]);
|
||||
|
||||
final rows = await query.get();
|
||||
for (final row in rows) {
|
||||
results[row.read(_db.remoteAlbumAssetEntity.albumId)!] = row.read(_db.remoteAssetEntity.localDateTime.max());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
return query.map((row) => row.read(_db.remoteAssetEntity.localDateTime.max())).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<Map<String, DateTime?>> getOldestAssetTimestampForAlbums(List<String> albumIds) async {
|
||||
if (albumIds.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
Future<DateTime?> getOldestAssetTimestamp(String albumId) {
|
||||
final query = _db.remoteAlbumAssetEntity.selectOnly()
|
||||
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
|
||||
..addColumns([_db.remoteAssetEntity.localDateTime.min()])
|
||||
..join([
|
||||
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
|
||||
]);
|
||||
|
||||
final results = <String, DateTime?>{};
|
||||
|
||||
// Chunk calls to avoid SQLite limit (default 999 variables)
|
||||
const chunkSize = 900;
|
||||
for (var i = 0; i < albumIds.length; i += chunkSize) {
|
||||
final end = (i + chunkSize < albumIds.length) ? i + chunkSize : albumIds.length;
|
||||
final subList = albumIds.sublist(i, end);
|
||||
|
||||
final query = _db.remoteAlbumAssetEntity.selectOnly()
|
||||
..where(_db.remoteAlbumAssetEntity.albumId.isIn(subList))
|
||||
..addColumns([_db.remoteAlbumAssetEntity.albumId, _db.remoteAssetEntity.localDateTime.min()])
|
||||
..join([
|
||||
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
|
||||
])
|
||||
..groupBy([_db.remoteAlbumAssetEntity.albumId]);
|
||||
|
||||
final rows = await query.get();
|
||||
for (final row in rows) {
|
||||
results[row.read(_db.remoteAlbumAssetEntity.albumId)!] = row.read(_db.remoteAssetEntity.localDateTime.min());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
return query.map((row) => row.read(_db.remoteAssetEntity.localDateTime.min())).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<int> getCount() {
|
||||
|
||||
@@ -18,34 +18,30 @@ void main() {
|
||||
mockAlbumApiRepo = MockDriftAlbumApiRepository();
|
||||
sut = RemoteAlbumService(mockRemoteAlbumRepo, mockAlbumApiRepo);
|
||||
|
||||
when(() => mockRemoteAlbumRepo.getNewestAssetTimestampForAlbums(any())).thenAnswer((invocation) async {
|
||||
final albumIds = invocation.positionalArguments[0] as List<String>;
|
||||
final result = <String, DateTime?>{};
|
||||
for (final id in albumIds) {
|
||||
if (id == '1') {
|
||||
result[id] = DateTime(2023, 1, 1);
|
||||
} else if (id == '2') {
|
||||
result[id] = DateTime(2023, 2, 1);
|
||||
} else {
|
||||
result[id] = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
when(() => mockRemoteAlbumRepo.getNewestAssetTimestamp(any())).thenAnswer((invocation) {
|
||||
// Simulate a timestamp for the newest asset in the album
|
||||
final albumID = invocation.positionalArguments[0] as String;
|
||||
|
||||
if (albumID == '1') {
|
||||
return Future.value(DateTime(2023, 1, 1));
|
||||
} else if (albumID == '2') {
|
||||
return Future.value(DateTime(2023, 2, 1));
|
||||
}
|
||||
return result;
|
||||
|
||||
return Future.value(DateTime.fromMillisecondsSinceEpoch(0));
|
||||
});
|
||||
|
||||
when(() => mockRemoteAlbumRepo.getOldestAssetTimestampForAlbums(any())).thenAnswer((invocation) async {
|
||||
final albumIds = invocation.positionalArguments[0] as List<String>;
|
||||
final result = <String, DateTime?>{};
|
||||
for (final id in albumIds) {
|
||||
if (id == '1') {
|
||||
result[id] = DateTime(2019, 1, 1);
|
||||
} else if (id == '2') {
|
||||
result[id] = DateTime(2019, 2, 1);
|
||||
} else {
|
||||
result[id] = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
when(() => mockRemoteAlbumRepo.getOldestAssetTimestamp(any())).thenAnswer((invocation) {
|
||||
// Simulate a timestamp for the oldest asset in the album
|
||||
final albumID = invocation.positionalArguments[0] as String;
|
||||
|
||||
if (albumID == '1') {
|
||||
return Future.value(DateTime(2019, 1, 1));
|
||||
} else if (albumID == '2') {
|
||||
return Future.value(DateTime(2019, 2, 1));
|
||||
}
|
||||
return result;
|
||||
|
||||
return Future.value(DateTime.fromMillisecondsSinceEpoch(0));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import AddToStackAction from '$lib/components/asset-viewer/actions/add-to-stack-action.svelte';
|
||||
import ArchiveAction from '$lib/components/asset-viewer/actions/archive-action.svelte';
|
||||
import DeleteAction from '$lib/components/asset-viewer/actions/delete-action.svelte';
|
||||
import EditAction from '$lib/components/asset-viewer/actions/edit-action.svelte';
|
||||
import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte';
|
||||
import RatingAction from '$lib/components/asset-viewer/actions/rating-action.svelte';
|
||||
import RemoveAssetFromStack from '$lib/components/asset-viewer/actions/remove-asset-from-stack.svelte';
|
||||
@@ -20,7 +21,7 @@
|
||||
import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { AppRoute, ProjectionType } from '$lib/constants';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { getGlobalActions } from '$lib/services/app.service';
|
||||
import { getAssetActions, handleReplaceAsset } from '$lib/services/asset.service';
|
||||
@@ -72,7 +73,7 @@
|
||||
onUndoDelete?: OnUndoDelete;
|
||||
onRunJob: (name: AssetJobName) => void;
|
||||
onPlaySlideshow: () => void;
|
||||
// onEdit: () => void;
|
||||
onEdit: () => void;
|
||||
onClose?: () => void;
|
||||
playOriginalVideo: boolean;
|
||||
setPlayOriginalVideo: (value: boolean) => void;
|
||||
@@ -92,7 +93,7 @@
|
||||
onRunJob,
|
||||
onPlaySlideshow,
|
||||
onClose,
|
||||
// onEdit,
|
||||
onEdit,
|
||||
playOriginalVideo = false,
|
||||
setPlayOriginalVideo,
|
||||
}: Props = $props();
|
||||
@@ -115,18 +116,17 @@
|
||||
const { Share, Download, SharedLinkDownload, Offline, Favorite, Unfavorite, PlayMotionPhoto, StopMotionPhoto, Info } =
|
||||
$derived(getAssetActions($t, asset));
|
||||
|
||||
// TODO: Enable when edits are ready for release
|
||||
// let showEditorButton = $derived(
|
||||
// isOwner &&
|
||||
// asset.type === AssetTypeEnum.Image &&
|
||||
// !(
|
||||
// asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR ||
|
||||
// (asset.originalPath && asset.originalPath.toLowerCase().endsWith('.insp'))
|
||||
// ) &&
|
||||
// !(asset.originalPath && asset.originalPath.toLowerCase().endsWith('.gif')) &&
|
||||
// !(asset.originalPath && asset.originalPath.toLowerCase().endsWith('.svg')) &&
|
||||
// !asset.livePhotoVideoId,
|
||||
// );
|
||||
let showEditorButton = $derived(
|
||||
isOwner &&
|
||||
asset.type === AssetTypeEnum.Image &&
|
||||
!(
|
||||
asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR ||
|
||||
(asset.originalPath && asset.originalPath.toLowerCase().endsWith('.insp'))
|
||||
) &&
|
||||
!(asset.originalPath && asset.originalPath.toLowerCase().endsWith('.gif')) &&
|
||||
!(asset.originalPath && asset.originalPath.toLowerCase().endsWith('.svg')) &&
|
||||
!asset.livePhotoVideoId,
|
||||
);
|
||||
</script>
|
||||
|
||||
<CommandPaletteDefaultProvider
|
||||
@@ -179,9 +179,9 @@
|
||||
<RatingAction {asset} {onAction} />
|
||||
{/if}
|
||||
|
||||
<!-- {#if showEditorButton}
|
||||
{#if showEditorButton}
|
||||
<EditAction onAction={onEdit} />
|
||||
{/if} -->
|
||||
{/if}
|
||||
|
||||
{#if isOwner}
|
||||
<DeleteAction {asset} {onAction} {preAction} {onUndoDelete} />
|
||||
|
||||
@@ -255,12 +255,12 @@
|
||||
});
|
||||
};
|
||||
|
||||
// const showEditor = () => {
|
||||
// if (assetViewerManager.isShowActivityPanel) {
|
||||
// assetViewerManager.isShowActivityPanel = false;
|
||||
// }
|
||||
// isShowEditor = !isShowEditor;
|
||||
// };
|
||||
const showEditor = () => {
|
||||
if (assetViewerManager.isShowActivityPanel) {
|
||||
assetViewerManager.isShowActivityPanel = false;
|
||||
}
|
||||
isShowEditor = !isShowEditor;
|
||||
};
|
||||
|
||||
const handleRunJob = async (name: AssetJobName) => {
|
||||
try {
|
||||
@@ -431,6 +431,7 @@
|
||||
onCopyImage={copyImage}
|
||||
preAction={handlePreAction}
|
||||
onAction={handleAction}
|
||||
onEdit={showEditor}
|
||||
{onUndoDelete}
|
||||
onRunJob={handleRunJob}
|
||||
onPlaySlideshow={() => ($slideshowState = SlideshowState.PlaySlideshow)}
|
||||
|
||||
Reference in New Issue
Block a user