1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

perf(web): batch asset store changes (#7974)

* perf(web): batch asset store changes

* update external calls to assetstore
This commit is contained in:
Michel Heusschen 2024-03-15 17:11:29 +01:00 committed by GitHub
parent a3dfa27a53
commit 5a6b71dda3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 118 additions and 66 deletions

View File

@ -169,12 +169,12 @@
case AssetAction.UNARCHIVE:
case AssetAction.FAVORITE:
case AssetAction.UNFAVORITE: {
assetStore.updateAsset(asset);
assetStore.updateAssets([asset]);
break;
}
case AssetAction.ADD: {
assetStore.addAsset(asset);
assetStore.addAssets([asset]);
break;
}
}

View File

@ -46,22 +46,22 @@ const THUMBNAIL_HEIGHT = 235;
interface AddAsset {
type: 'add';
value: AssetResponseDto;
values: AssetResponseDto[];
}
interface UpdateAsset {
type: 'update';
value: AssetResponseDto;
values: AssetResponseDto[];
}
interface DeleteAsset {
type: 'delete';
value: string;
values: string[];
}
interface TrashAssets {
type: 'trash';
value: string[];
values: string[];
}
export const photoViewer = writable<HTMLImageElement | null>(null);
@ -102,16 +102,16 @@ export class AssetStore {
connect() {
this.unsubscribers.push(
websocketEvents.on('on_upload_success', (asset) => {
this.addPendingChanges({ type: 'add', value: asset });
this.addPendingChanges({ type: 'add', values: [asset] });
}),
websocketEvents.on('on_asset_trash', (ids) => {
this.addPendingChanges({ type: 'trash', value: ids });
this.addPendingChanges({ type: 'trash', values: ids });
}),
websocketEvents.on('on_asset_update', (asset) => {
this.addPendingChanges({ type: 'update', value: asset });
this.addPendingChanges({ type: 'update', values: [asset] });
}),
websocketEvents.on('on_asset_delete', (id: string) => {
this.addPendingChanges({ type: 'delete', value: id });
this.addPendingChanges({ type: 'delete', values: [id] });
}),
);
}
@ -122,28 +122,55 @@ export class AssetStore {
}
}
private getPendingChangeBatches() {
const batches: PendingChange[] = [];
let batch: PendingChange | undefined;
for (const { type, values: _values } of this.pendingChanges) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const values = _values as any[];
if (batch && batch.type !== type) {
batches.push(batch);
batch = undefined;
}
if (batch) {
batch.values.push(...values);
} else {
batch = { type, values };
}
}
if (batch) {
batches.push(batch);
}
return batches;
}
processPendingChanges = throttle(() => {
for (const { type, value } of this.pendingChanges) {
for (const { type, values } of this.getPendingChangeBatches()) {
switch (type) {
case 'add': {
this.addAsset(value);
this.addAssets(values);
break;
}
case 'update': {
this.updateAsset(value);
this.updateAssets(values);
break;
}
case 'trash': {
if (!this.options.isTrashed) {
this.removeAssets(value);
this.removeAssets(values);
}
break;
}
case 'delete': {
this.removeAssets([value]);
this.removeAssets(values);
break;
}
}
@ -151,7 +178,7 @@ export class AssetStore {
this.pendingChanges = [];
this.emit(true);
}, 10_000);
}, 2500);
async init(viewport: Viewport) {
this.initialized = false;
@ -277,56 +304,73 @@ export class AssetStore {
return scrollTimeline ? delta : 0;
}
addAsset(asset: AssetResponseDto): void {
if (
this.assetToBucket[asset.id] ||
this.options.userId ||
this.options.personId ||
this.options.albumId ||
isMismatched(this.options.isArchived, asset.isArchived) ||
isMismatched(this.options.isFavorite, asset.isFavorite)
) {
// If asset is already in the bucket we don't need to recalculate
// asset store containers
this.updateAsset(asset);
return;
addAssets(assets: AssetResponseDto[]) {
const assetsToUpdate: AssetResponseDto[] = [];
const assetsToAdd: AssetResponseDto[] = [];
for (const asset of assets) {
if (
this.assetToBucket[asset.id] ||
this.options.userId ||
this.options.personId ||
this.options.albumId ||
isMismatched(this.options.isArchived, asset.isArchived) ||
isMismatched(this.options.isFavorite, asset.isFavorite)
) {
// If asset is already in the bucket we don't need to recalculate
// asset store containers
assetsToUpdate.push(asset);
} else {
assetsToAdd.push(asset);
}
}
this.addAssetToBucket(asset);
this.updateAssets(assetsToUpdate);
this.addAssetsToBuckets(assetsToAdd);
}
private addAssetToBucket(asset: AssetResponseDto) {
const timeBucket = DateTime.fromISO(asset.fileCreatedAt).toUTC().startOf('month').toString();
let bucket = this.getBucketByDate(timeBucket);
private addAssetsToBuckets(assets: AssetResponseDto[]) {
if (assets.length === 0) {
return;
}
const updatedBuckets = new Set<AssetBucket>();
if (!bucket) {
bucket = {
bucketDate: timeBucket,
bucketHeight: THUMBNAIL_HEIGHT,
bucketCount: 0,
assets: [],
cancelToken: null,
position: BucketPosition.Unknown,
};
for (const asset of assets) {
const timeBucket = DateTime.fromISO(asset.fileCreatedAt).toUTC().startOf('month').toString();
let bucket = this.getBucketByDate(timeBucket);
this.buckets.push(bucket);
this.buckets = this.buckets.sort((a, b) => {
const aDate = DateTime.fromISO(a.bucketDate).toUTC();
const bDate = DateTime.fromISO(b.bucketDate).toUTC();
if (!bucket) {
bucket = {
bucketDate: timeBucket,
bucketHeight: THUMBNAIL_HEIGHT,
bucketCount: 0,
assets: [],
cancelToken: null,
position: BucketPosition.Unknown,
};
this.buckets.push(bucket);
}
bucket.assets.push(asset);
this.assets.push(asset);
updatedBuckets.add(bucket);
}
this.buckets = this.buckets.sort((a, b) => {
const aDate = DateTime.fromISO(a.bucketDate).toUTC();
const bDate = DateTime.fromISO(b.bucketDate).toUTC();
return bDate.diff(aDate).milliseconds;
});
for (const bucket of updatedBuckets) {
bucket.assets.sort((a, b) => {
const aDate = DateTime.fromISO(a.fileCreatedAt).toUTC();
const bDate = DateTime.fromISO(b.fileCreatedAt).toUTC();
return bDate.diff(aDate).milliseconds;
});
}
bucket.assets.push(asset);
bucket.assets.sort((a, b) => {
const aDate = DateTime.fromISO(a.fileCreatedAt).toUTC();
const bDate = DateTime.fromISO(b.fileCreatedAt).toUTC();
return bDate.diff(aDate).milliseconds;
});
// If we added an asset to the store, we need to recalculate
// asset store containers
this.assets.push(asset);
this.emit(true);
}
@ -358,21 +402,29 @@ export class AssetStore {
return null;
}
updateAsset(_asset: AssetResponseDto) {
const asset = this.assets.find((asset) => asset.id === _asset.id);
if (!asset) {
updateAssets(assets: AssetResponseDto[]) {
if (assets.length === 0) {
return;
}
const assetsToReculculate: AssetResponseDto[] = [];
const recalculate = asset.fileCreatedAt !== _asset.fileCreatedAt;
if (recalculate) {
this.removeAssets([asset.id]);
this.addAssetToBucket(_asset);
return;
for (const _asset of assets) {
const asset = this.assets.find((asset) => asset.id === _asset.id);
if (!asset) {
continue;
}
const recalculate = asset.fileCreatedAt !== _asset.fileCreatedAt;
Object.assign(asset, _asset);
if (recalculate) {
assetsToReculculate.push(asset);
}
}
Object.assign(asset, _asset);
this.emit(recalculate);
this.removeAssets(assetsToReculculate.map((asset) => asset.id));
this.addAssetsToBuckets(assetsToReculculate);
this.emit(assetsToReculculate.length > 0);
}
removeAssets(ids: string[]) {