hunk.code_window:     restart: always
     database:
       container_name: immich_postgres
       image: postgres:14
       env_file:
         - .env
       environment:
         POSTGRES_PASSWORD: ${DB_PASSWORD}
         POSTGRES_USER: ${DB_USERNAME}
  
hunk.labels: keep keep keep keep replace keep keep keep keep keep
hunk.after_edit:     image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
  
hunk.file_path: docker/docker-compose.yml
hunk.type: replace
file: import { api, AddAssetsResponseDto, AssetResponseDto } from '@api';
  import {
  	notificationController,
  	NotificationType
  } from '$lib/components/shared-components/notification/notification';
  import { downloadAssets } from '$lib/stores/download';
  
  export const addAssetsToAlbum = async (
  	albumId: string,
  	assetIds: Array<string>,
  	key: string | undefined = undefined
  ): Promise<AddAssetsResponseDto> =>
  	api.albumApi
  		.addAssetsToAlbum({ id: albumId, addAssetsDto: { assetIds }, key })
  		.then(({ data: dto }) => {
  			if (dto.successfullyAdded > 0) {
  				// This might be 0 if the user tries to add an asset that is already in the album
  				notificationController.show({
  					message: `Added ${dto.successfullyAdded} to ${dto.album?.albumName}`,
  					type: NotificationType.Info
  				});
  			}
  
  			return dto;
  		});
  
  export async function bulkDownload(
  	fileName: string,
  	assets: AssetResponseDto[],
  	onDone?: () => void,
  	key?: string
  ) {
  	const assetIds = assets.map((asset) => asset.id);
  
  	try {
  		// let skip = 0;
  		let count = 0;
  		let done = false;
  
  		while (!done) {
  			count++;
  
  			const downloadFileName = fileName + `${count === 1 ? '' : count}.zip`;
  			downloadAssets.set({ [downloadFileName]: 0 });
  
  			let total = 0;
  
  			const { data, status, headers } = await api.assetApi.downloadFiles(
  				{ downloadFilesDto: { assetIds }, key },
  				{
  					responseType: 'blob',
  					onDownloadProgress: function (progressEvent) {
  						const request = this as XMLHttpRequest;
  						if (!total) {
  							total = Number(request.getResponseHeader('X-Immich-Content-Length-Hint')) || 0;
  						}
  
  						if (total) {
  							const current = progressEvent.loaded;
  							downloadAssets.set({ [downloadFileName]: Math.floor((current / total) * 100) });
  						}
  					}
  				}
  			);
  
  			const isNotComplete = headers['x-immich-archive-complete'] === 'false';
  			const fileCount = Number(headers['x-immich-archive-file-count']) || 0;
  			if (isNotComplete && fileCount > 0) {
  				// skip += fileCount;
  			} else {
  				onDone?.();
  				done = true;
  			}
  
  			if (!(data instanceof Blob)) {
  				return;
  			}
  
  			if (status === 201) {
  				const fileUrl = URL.createObjectURL(data);
  				const anchor = document.createElement('a');
  				anchor.href = fileUrl;
  				anchor.download = downloadFileName;
  
  				document.body.appendChild(anchor);
  				anchor.click();
  				document.body.removeChild(anchor);
  
  				URL.revokeObjectURL(fileUrl);
  
  				// Remove item from download list
  				setTimeout(() => {
  					downloadAssets.set({});
  				}, 2000);
  			}
  		}
  	} catch (e) {
  		console.error('Error downloading file ', e);
  		notificationController.show({
  			type: NotificationType.Error,
  			message: 'Error downloading file, check console for more details.'
  		});
  	}
  }
  
  /**
   * Returns the lowercase filename extension without a dot (.) and
   * an empty string when not found.
   */
  export function getFilenameExtension(filename: string): string {
  	const lastIndex = Math.max(0, filename.lastIndexOf('.'));
  	const startIndex = (lastIndex || Infinity) + 1;
  	return filename.slice(startIndex).toLowerCase();
  }
  
  /**
   * Returns the filename of an asset including file extension
   */
  export function getAssetFilename(asset: AssetResponseDto): string {
  	const fileExtension = getFilenameExtension(asset.originalPath);
  	return `${asset.originalFileName}.${fileExtension}`;
  }
  
  /**
   * Returns the MIME type of the file and an empty string when not found.
   */
  export function getFileMimeType(file: File): string {
  	const mimeTypes: Record<string, string> = {
  		'3gp': 'video/3gpp',
  		arw: 'image/x-sony-arw',
  		dng: 'image/dng',
  		heic: 'image/heic',
  		heif: 'image/heif',
  		insp: 'image/jpeg',
  		insv: 'video/mp4',
  		nef: 'image/x-nikon-nef',
  		raf: 'image/x-fuji-raf',
  		srw: 'image/x-samsung-srw'
  	};
  	// Return the MIME type determined by the browser or the MIME type based on the file extension.
  	return file.type || (mimeTypes[getFilenameExtension(file.name)] ?? '');
  }
  
  /**
   * Returns aspect ratio for the asset
   */
  export function getAssetRatio(asset: AssetResponseDto) {
  	let height = asset.exifInfo?.exifImageHeight || 235;
  	let width = asset.exifInfo?.exifImageWidth || 235;
  	const orientation = Number(asset.exifInfo?.orientation);
  	if (orientation) {
  		if (orientation == 6 || orientation == -90) {
  			[width, height] = [height, width];
  		}
  	}
  	return { width, height };
  }
  