mirror of
https://github.com/immich-app/immich.git
synced 2025-01-06 03:46:47 +01:00
perf(web): batch asset store changes (#7974)
* perf(web): batch asset store changes * update external calls to assetstore
This commit is contained in:
parent
a3dfa27a53
commit
5a6b71dda3
2 changed files with 118 additions and 66 deletions
|
@ -169,12 +169,12 @@
|
||||||
case AssetAction.UNARCHIVE:
|
case AssetAction.UNARCHIVE:
|
||||||
case AssetAction.FAVORITE:
|
case AssetAction.FAVORITE:
|
||||||
case AssetAction.UNFAVORITE: {
|
case AssetAction.UNFAVORITE: {
|
||||||
assetStore.updateAsset(asset);
|
assetStore.updateAssets([asset]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case AssetAction.ADD: {
|
case AssetAction.ADD: {
|
||||||
assetStore.addAsset(asset);
|
assetStore.addAssets([asset]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,22 +46,22 @@ const THUMBNAIL_HEIGHT = 235;
|
||||||
|
|
||||||
interface AddAsset {
|
interface AddAsset {
|
||||||
type: 'add';
|
type: 'add';
|
||||||
value: AssetResponseDto;
|
values: AssetResponseDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateAsset {
|
interface UpdateAsset {
|
||||||
type: 'update';
|
type: 'update';
|
||||||
value: AssetResponseDto;
|
values: AssetResponseDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteAsset {
|
interface DeleteAsset {
|
||||||
type: 'delete';
|
type: 'delete';
|
||||||
value: string;
|
values: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TrashAssets {
|
interface TrashAssets {
|
||||||
type: 'trash';
|
type: 'trash';
|
||||||
value: string[];
|
values: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const photoViewer = writable<HTMLImageElement | null>(null);
|
export const photoViewer = writable<HTMLImageElement | null>(null);
|
||||||
|
@ -102,16 +102,16 @@ export class AssetStore {
|
||||||
connect() {
|
connect() {
|
||||||
this.unsubscribers.push(
|
this.unsubscribers.push(
|
||||||
websocketEvents.on('on_upload_success', (asset) => {
|
websocketEvents.on('on_upload_success', (asset) => {
|
||||||
this.addPendingChanges({ type: 'add', value: asset });
|
this.addPendingChanges({ type: 'add', values: [asset] });
|
||||||
}),
|
}),
|
||||||
websocketEvents.on('on_asset_trash', (ids) => {
|
websocketEvents.on('on_asset_trash', (ids) => {
|
||||||
this.addPendingChanges({ type: 'trash', value: ids });
|
this.addPendingChanges({ type: 'trash', values: ids });
|
||||||
}),
|
}),
|
||||||
websocketEvents.on('on_asset_update', (asset) => {
|
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) => {
|
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(() => {
|
processPendingChanges = throttle(() => {
|
||||||
for (const { type, value } of this.pendingChanges) {
|
for (const { type, values } of this.getPendingChangeBatches()) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'add': {
|
case 'add': {
|
||||||
this.addAsset(value);
|
this.addAssets(values);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'update': {
|
case 'update': {
|
||||||
this.updateAsset(value);
|
this.updateAssets(values);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'trash': {
|
case 'trash': {
|
||||||
if (!this.options.isTrashed) {
|
if (!this.options.isTrashed) {
|
||||||
this.removeAssets(value);
|
this.removeAssets(values);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'delete': {
|
case 'delete': {
|
||||||
this.removeAssets([value]);
|
this.removeAssets(values);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,7 +178,7 @@ export class AssetStore {
|
||||||
|
|
||||||
this.pendingChanges = [];
|
this.pendingChanges = [];
|
||||||
this.emit(true);
|
this.emit(true);
|
||||||
}, 10_000);
|
}, 2500);
|
||||||
|
|
||||||
async init(viewport: Viewport) {
|
async init(viewport: Viewport) {
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
|
@ -277,56 +304,73 @@ export class AssetStore {
|
||||||
return scrollTimeline ? delta : 0;
|
return scrollTimeline ? delta : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
addAsset(asset: AssetResponseDto): void {
|
addAssets(assets: AssetResponseDto[]) {
|
||||||
if (
|
const assetsToUpdate: AssetResponseDto[] = [];
|
||||||
this.assetToBucket[asset.id] ||
|
const assetsToAdd: AssetResponseDto[] = [];
|
||||||
this.options.userId ||
|
|
||||||
this.options.personId ||
|
for (const asset of assets) {
|
||||||
this.options.albumId ||
|
if (
|
||||||
isMismatched(this.options.isArchived, asset.isArchived) ||
|
this.assetToBucket[asset.id] ||
|
||||||
isMismatched(this.options.isFavorite, asset.isFavorite)
|
this.options.userId ||
|
||||||
) {
|
this.options.personId ||
|
||||||
// If asset is already in the bucket we don't need to recalculate
|
this.options.albumId ||
|
||||||
// asset store containers
|
isMismatched(this.options.isArchived, asset.isArchived) ||
|
||||||
this.updateAsset(asset);
|
isMismatched(this.options.isFavorite, asset.isFavorite)
|
||||||
return;
|
) {
|
||||||
|
// 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) {
|
private addAssetsToBuckets(assets: AssetResponseDto[]) {
|
||||||
const timeBucket = DateTime.fromISO(asset.fileCreatedAt).toUTC().startOf('month').toString();
|
if (assets.length === 0) {
|
||||||
let bucket = this.getBucketByDate(timeBucket);
|
return;
|
||||||
|
}
|
||||||
|
const updatedBuckets = new Set<AssetBucket>();
|
||||||
|
|
||||||
if (!bucket) {
|
for (const asset of assets) {
|
||||||
bucket = {
|
const timeBucket = DateTime.fromISO(asset.fileCreatedAt).toUTC().startOf('month').toString();
|
||||||
bucketDate: timeBucket,
|
let bucket = this.getBucketByDate(timeBucket);
|
||||||
bucketHeight: THUMBNAIL_HEIGHT,
|
|
||||||
bucketCount: 0,
|
|
||||||
assets: [],
|
|
||||||
cancelToken: null,
|
|
||||||
position: BucketPosition.Unknown,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.buckets.push(bucket);
|
if (!bucket) {
|
||||||
this.buckets = this.buckets.sort((a, b) => {
|
bucket = {
|
||||||
const aDate = DateTime.fromISO(a.bucketDate).toUTC();
|
bucketDate: timeBucket,
|
||||||
const bDate = DateTime.fromISO(b.bucketDate).toUTC();
|
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;
|
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);
|
this.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,21 +402,29 @@ export class AssetStore {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAsset(_asset: AssetResponseDto) {
|
updateAssets(assets: AssetResponseDto[]) {
|
||||||
const asset = this.assets.find((asset) => asset.id === _asset.id);
|
if (assets.length === 0) {
|
||||||
if (!asset) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const assetsToReculculate: AssetResponseDto[] = [];
|
||||||
|
|
||||||
const recalculate = asset.fileCreatedAt !== _asset.fileCreatedAt;
|
for (const _asset of assets) {
|
||||||
if (recalculate) {
|
const asset = this.assets.find((asset) => asset.id === _asset.id);
|
||||||
this.removeAssets([asset.id]);
|
if (!asset) {
|
||||||
this.addAssetToBucket(_asset);
|
continue;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
const recalculate = asset.fileCreatedAt !== _asset.fileCreatedAt;
|
||||||
|
Object.assign(asset, _asset);
|
||||||
|
|
||||||
|
if (recalculate) {
|
||||||
|
assetsToReculculate.push(asset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(asset, _asset);
|
this.removeAssets(assetsToReculculate.map((asset) => asset.id));
|
||||||
this.emit(recalculate);
|
this.addAssetsToBuckets(assetsToReculculate);
|
||||||
|
this.emit(assetsToReculculate.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAssets(ids: string[]) {
|
removeAssets(ids: string[]) {
|
||||||
|
|
Loading…
Reference in a new issue