mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05: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
					
				@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user