refactor: download service (#16600)
This commit is contained in:
@@ -4,53 +4,72 @@ import { StorageCore } from 'src/cores/storage.core';
|
||||
import { AssetIdsDto } from 'src/dtos/asset.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { Permission } from 'src/enum';
|
||||
import { ImmichReadStream } from 'src/repositories/storage.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { HumanReadableSize } from 'src/utils/bytes';
|
||||
import { usePagination } from 'src/utils/pagination';
|
||||
import { getPreferences } from 'src/utils/preferences';
|
||||
|
||||
@Injectable()
|
||||
export class DownloadService extends BaseService {
|
||||
async getDownloadInfo(auth: AuthDto, dto: DownloadInfoDto): Promise<DownloadResponseDto> {
|
||||
let assets;
|
||||
|
||||
if (dto.assetIds) {
|
||||
const assetIds = dto.assetIds;
|
||||
await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: assetIds });
|
||||
assets = this.downloadRepository.downloadAssetIds(assetIds);
|
||||
} else if (dto.albumId) {
|
||||
const albumId = dto.albumId;
|
||||
await this.requireAccess({ auth, permission: Permission.ALBUM_DOWNLOAD, ids: [albumId] });
|
||||
assets = this.downloadRepository.downloadAlbumId(albumId);
|
||||
} else if (dto.userId) {
|
||||
const userId = dto.userId;
|
||||
await this.requireAccess({ auth, permission: Permission.TIMELINE_DOWNLOAD, ids: [userId] });
|
||||
assets = this.downloadRepository.downloadUserId(userId);
|
||||
} else {
|
||||
throw new BadRequestException('assetIds, albumId, or userId is required');
|
||||
}
|
||||
|
||||
const targetSize = dto.archiveSize || HumanReadableSize.GiB * 4;
|
||||
const metadata = await this.userRepository.getMetadata(auth.user.id);
|
||||
const preferences = getPreferences(auth.user.email, metadata);
|
||||
const motionIds = new Set<string>();
|
||||
const archives: DownloadArchiveInfo[] = [];
|
||||
let archive: DownloadArchiveInfo = { size: 0, assetIds: [] };
|
||||
|
||||
const metadata = await this.userRepository.getMetadata(auth.user.id);
|
||||
const preferences = getPreferences(auth.user.email, metadata);
|
||||
const addToArchive = ({ id, size }: { id: string; size: number | null }) => {
|
||||
archive.assetIds.push(id);
|
||||
archive.size += Number(size || 0);
|
||||
|
||||
const assetPagination = await this.getDownloadAssets(auth, dto);
|
||||
for await (const assets of assetPagination) {
|
||||
// motion part of live photos
|
||||
const motionIds = assets.map((asset) => asset.livePhotoVideoId).filter((id): id is string => !!id);
|
||||
if (motionIds.length > 0) {
|
||||
const motionAssets = await this.assetRepository.getByIds(motionIds, { exifInfo: true });
|
||||
for (const motionAsset of motionAssets) {
|
||||
if (
|
||||
!StorageCore.isAndroidMotionPath(motionAsset.originalPath) ||
|
||||
preferences.download.includeEmbeddedVideos
|
||||
) {
|
||||
assets.push(motionAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const asset of assets) {
|
||||
archive.size += Number(asset.exifInfo?.fileSizeInByte || 0);
|
||||
archive.assetIds.push(asset.id);
|
||||
|
||||
if (archive.size > targetSize) {
|
||||
archives.push(archive);
|
||||
archive = { size: 0, assetIds: [] };
|
||||
}
|
||||
}
|
||||
|
||||
if (archive.assetIds.length > 0) {
|
||||
if (archive.size > targetSize) {
|
||||
archives.push(archive);
|
||||
archive = { size: 0, assetIds: [] };
|
||||
}
|
||||
};
|
||||
|
||||
for await (const asset of assets) {
|
||||
// motion part of live photos
|
||||
if (asset.livePhotoVideoId) {
|
||||
motionIds.add(asset.livePhotoVideoId);
|
||||
}
|
||||
|
||||
addToArchive(asset);
|
||||
}
|
||||
|
||||
if (motionIds.size > 0) {
|
||||
const motionAssets = this.downloadRepository.downloadMotionAssetIds([...motionIds]);
|
||||
for await (const motionAsset of motionAssets) {
|
||||
if (StorageCore.isAndroidMotionPath(motionAsset.originalPath) && !preferences.download.includeEmbeddedVideos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addToArchive(motionAsset);
|
||||
}
|
||||
}
|
||||
|
||||
if (archive.assetIds.length > 0) {
|
||||
archives.push(archive);
|
||||
}
|
||||
|
||||
let totalSize = 0;
|
||||
@@ -99,31 +118,4 @@ export class DownloadService extends BaseService {
|
||||
|
||||
return { stream: zip.stream };
|
||||
}
|
||||
|
||||
private async getDownloadAssets(auth: AuthDto, dto: DownloadInfoDto): Promise<AsyncGenerator<AssetEntity[]>> {
|
||||
const PAGINATION_SIZE = 2500;
|
||||
|
||||
if (dto.assetIds) {
|
||||
const assetIds = dto.assetIds;
|
||||
await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: assetIds });
|
||||
const assets = await this.assetRepository.getByIds(assetIds, { exifInfo: true });
|
||||
return usePagination(PAGINATION_SIZE, () => ({ hasNextPage: false, items: assets }));
|
||||
}
|
||||
|
||||
if (dto.albumId) {
|
||||
const albumId = dto.albumId;
|
||||
await this.requireAccess({ auth, permission: Permission.ALBUM_DOWNLOAD, ids: [albumId] });
|
||||
return usePagination(PAGINATION_SIZE, (pagination) => this.assetRepository.getByAlbumId(pagination, albumId));
|
||||
}
|
||||
|
||||
if (dto.userId) {
|
||||
const userId = dto.userId;
|
||||
await this.requireAccess({ auth, permission: Permission.TIMELINE_DOWNLOAD, ids: [userId] });
|
||||
return usePagination(PAGINATION_SIZE, (pagination) =>
|
||||
this.assetRepository.getByUserId(pagination, userId, { isVisible: true }),
|
||||
);
|
||||
}
|
||||
|
||||
throw new BadRequestException('assetIds, albumId, or userId is required');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user