mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
refactor(server): download assets (#3032)
* refactor: download assets * chore: open api * chore: finish tests, make size configurable * chore: defualt to 4GiB * chore: open api * fix: optional archive size * fix: bugs * chore: cleanup
This commit is contained in:
parent
df9c05bef3
commit
ad343b7b32
53 changed files with 1455 additions and 1403 deletions
551
web/src/api/open-api/api.ts
generated
551
web/src/api/open-api/api.ts
generated
|
|
@ -1111,16 +1111,41 @@ export type DeleteAssetStatus = typeof DeleteAssetStatus[keyof typeof DeleteAsse
|
|||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface DownloadFilesDto
|
||||
* @interface DownloadArchiveInfo
|
||||
*/
|
||||
export interface DownloadFilesDto {
|
||||
export interface DownloadArchiveInfo {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof DownloadArchiveInfo
|
||||
*/
|
||||
'size': number;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof DownloadFilesDto
|
||||
* @memberof DownloadArchiveInfo
|
||||
*/
|
||||
'assetIds': Array<string>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface DownloadResponseDto
|
||||
*/
|
||||
export interface DownloadResponseDto {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof DownloadResponseDto
|
||||
*/
|
||||
'totalSize': number;
|
||||
/**
|
||||
*
|
||||
* @type {Array<DownloadArchiveInfo>}
|
||||
* @memberof DownloadResponseDto
|
||||
*/
|
||||
'archives': Array<DownloadArchiveInfo>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
|
|
@ -3645,63 +3670,6 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration
|
|||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadArchive: async (id: string, name?: string, skip?: number, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('downloadArchive', 'id', id)
|
||||
const localVarPath = `/album/{id}/download`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication cookie required
|
||||
|
||||
// authentication api_key required
|
||||
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (name !== undefined) {
|
||||
localVarQueryParameter['name'] = name;
|
||||
}
|
||||
|
||||
if (skip !== undefined) {
|
||||
localVarQueryParameter['skip'] = skip;
|
||||
}
|
||||
|
||||
if (key !== undefined) {
|
||||
localVarQueryParameter['key'] = key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
|
@ -4039,19 +4007,6 @@ export const AlbumApiFp = function(configuration?: Configuration) {
|
|||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAlbum(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async downloadArchive(id: string, name?: string, skip?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<File>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadArchive(id, name, skip, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
|
|
@ -4165,18 +4120,6 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath
|
|||
deleteAlbum(id: string, options?: any): AxiosPromise<void> {
|
||||
return localVarFp.deleteAlbum(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadArchive(id: string, name?: string, skip?: number, key?: string, options?: any): AxiosPromise<File> {
|
||||
return localVarFp.downloadArchive(id, name, skip, key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
|
|
@ -4315,41 +4258,6 @@ export interface AlbumApiDeleteAlbumRequest {
|
|||
readonly id: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for downloadArchive operation in AlbumApi.
|
||||
* @export
|
||||
* @interface AlbumApiDownloadArchiveRequest
|
||||
*/
|
||||
export interface AlbumApiDownloadArchiveRequest {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AlbumApiDownloadArchive
|
||||
*/
|
||||
readonly id: string
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AlbumApiDownloadArchive
|
||||
*/
|
||||
readonly name?: string
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AlbumApiDownloadArchive
|
||||
*/
|
||||
readonly skip?: number
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AlbumApiDownloadArchive
|
||||
*/
|
||||
readonly key?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for getAlbumInfo operation in AlbumApi.
|
||||
* @export
|
||||
|
|
@ -4506,17 +4414,6 @@ export class AlbumApi extends BaseAPI {
|
|||
return AlbumApiFp(this.configuration).deleteAlbum(requestParameters.id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {AlbumApiDownloadArchiveRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AlbumApi
|
||||
*/
|
||||
public downloadArchive(requestParameters: AlbumApiDownloadArchiveRequest, options?: AxiosRequestConfig) {
|
||||
return AlbumApiFp(this.configuration).downloadArchive(requestParameters.id, requestParameters.name, requestParameters.skip, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
|
|
@ -4773,62 +4670,15 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {AssetIdsDto} assetIdsDto
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadFile: async (id: string, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('downloadFile', 'id', id)
|
||||
const localVarPath = `/asset/download/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication cookie required
|
||||
|
||||
// authentication api_key required
|
||||
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (key !== undefined) {
|
||||
localVarQueryParameter['key'] = key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {DownloadFilesDto} downloadFilesDto
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadFiles: async (downloadFilesDto: DownloadFilesDto, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'downloadFilesDto' is not null or undefined
|
||||
assertParamExists('downloadFiles', 'downloadFilesDto', downloadFilesDto)
|
||||
const localVarPath = `/asset/download-files`;
|
||||
downloadArchive: async (assetIdsDto: AssetIdsDto, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'assetIdsDto' is not null or undefined
|
||||
assertParamExists('downloadArchive', 'assetIdsDto', assetIdsDto)
|
||||
const localVarPath = `/asset/download`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
|
|
@ -4860,7 +4710,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(downloadFilesDto, localVarRequestOptions, configuration)
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(assetIdsDto, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
|
|
@ -4868,15 +4718,17 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
};
|
||||
},
|
||||
/**
|
||||
* Current this is not used in any UI element
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadLibrary: async (name?: string, skip?: number, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/asset/download-library`;
|
||||
downloadFile: async (id: string, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('downloadFile', 'id', id)
|
||||
const localVarPath = `/asset/download/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
|
|
@ -4884,7 +4736,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
|
@ -4897,14 +4749,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (name !== undefined) {
|
||||
localVarQueryParameter['name'] = name;
|
||||
}
|
||||
|
||||
if (skip !== undefined) {
|
||||
localVarQueryParameter['skip'] = skip;
|
||||
}
|
||||
|
||||
if (key !== undefined) {
|
||||
localVarQueryParameter['key'] = key;
|
||||
}
|
||||
|
|
@ -5356,6 +5200,69 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {Array<string>} [assetIds]
|
||||
* @param {string} [albumId]
|
||||
* @param {string} [userId]
|
||||
* @param {number} [archiveSize]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getDownloadInfo: async (assetIds?: Array<string>, albumId?: string, userId?: string, archiveSize?: number, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/asset/download`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication cookie required
|
||||
|
||||
// authentication api_key required
|
||||
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (assetIds) {
|
||||
localVarQueryParameter['assetIds'] = assetIds;
|
||||
}
|
||||
|
||||
if (albumId !== undefined) {
|
||||
localVarQueryParameter['albumId'] = albumId;
|
||||
}
|
||||
|
||||
if (userId !== undefined) {
|
||||
localVarQueryParameter['userId'] = userId;
|
||||
}
|
||||
|
||||
if (archiveSize !== undefined) {
|
||||
localVarQueryParameter['archiveSize'] = archiveSize;
|
||||
}
|
||||
|
||||
if (key !== undefined) {
|
||||
localVarQueryParameter['key'] = key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
|
@ -5888,6 +5795,17 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAsset(deleteAssetDto, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {AssetIdsDto} assetIdsDto
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async downloadArchive(assetIdsDto: AssetIdsDto, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<File>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadArchive(assetIdsDto, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
|
|
@ -5899,29 +5817,6 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(id, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {DownloadFilesDto} downloadFilesDto
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async downloadFiles(downloadFilesDto: DownloadFilesDto, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<File>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFiles(downloadFilesDto, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* Current this is not used in any UI element
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async downloadLibrary(name?: string, skip?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<File>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadLibrary(name, skip, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* Get all AssetEntity belong to the user
|
||||
* @param {string} [userId]
|
||||
|
|
@ -6025,6 +5920,20 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
const localVarAxiosArgs = await localVarAxiosParamCreator.getCuratedObjects(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {Array<string>} [assetIds]
|
||||
* @param {string} [albumId]
|
||||
* @param {string} [userId]
|
||||
* @param {number} [archiveSize]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getDownloadInfo(assetIds?: Array<string>, albumId?: string, userId?: string, archiveSize?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DownloadResponseDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getDownloadInfo(assetIds, albumId, userId, archiveSize, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {boolean} [isFavorite]
|
||||
|
|
@ -6172,6 +6081,16 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
deleteAsset(deleteAssetDto: DeleteAssetDto, options?: any): AxiosPromise<Array<DeleteAssetResponseDto>> {
|
||||
return localVarFp.deleteAsset(deleteAssetDto, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {AssetIdsDto} assetIdsDto
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadArchive(assetIdsDto: AssetIdsDto, key?: string, options?: any): AxiosPromise<File> {
|
||||
return localVarFp.downloadArchive(assetIdsDto, key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
|
|
@ -6182,27 +6101,6 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
downloadFile(id: string, key?: string, options?: any): AxiosPromise<File> {
|
||||
return localVarFp.downloadFile(id, key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {DownloadFilesDto} downloadFilesDto
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadFiles(downloadFilesDto: DownloadFilesDto, key?: string, options?: any): AxiosPromise<File> {
|
||||
return localVarFp.downloadFiles(downloadFilesDto, key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Current this is not used in any UI element
|
||||
* @param {string} [name]
|
||||
* @param {number} [skip]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
downloadLibrary(name?: string, skip?: number, key?: string, options?: any): AxiosPromise<File> {
|
||||
return localVarFp.downloadLibrary(name, skip, key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Get all AssetEntity belong to the user
|
||||
* @param {string} [userId]
|
||||
|
|
@ -6296,6 +6194,19 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
getCuratedObjects(options?: any): AxiosPromise<Array<CuratedObjectsResponseDto>> {
|
||||
return localVarFp.getCuratedObjects(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {Array<string>} [assetIds]
|
||||
* @param {string} [albumId]
|
||||
* @param {string} [userId]
|
||||
* @param {number} [archiveSize]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getDownloadInfo(assetIds?: Array<string>, albumId?: string, userId?: string, archiveSize?: number, key?: string, options?: any): AxiosPromise<DownloadResponseDto> {
|
||||
return localVarFp.getDownloadInfo(assetIds, albumId, userId, archiveSize, key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {boolean} [isFavorite]
|
||||
|
|
@ -6454,6 +6365,27 @@ export interface AssetApiDeleteAssetRequest {
|
|||
readonly deleteAssetDto: DeleteAssetDto
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for downloadArchive operation in AssetApi.
|
||||
* @export
|
||||
* @interface AssetApiDownloadArchiveRequest
|
||||
*/
|
||||
export interface AssetApiDownloadArchiveRequest {
|
||||
/**
|
||||
*
|
||||
* @type {AssetIdsDto}
|
||||
* @memberof AssetApiDownloadArchive
|
||||
*/
|
||||
readonly assetIdsDto: AssetIdsDto
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetApiDownloadArchive
|
||||
*/
|
||||
readonly key?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for downloadFile operation in AssetApi.
|
||||
* @export
|
||||
|
|
@ -6475,55 +6407,6 @@ export interface AssetApiDownloadFileRequest {
|
|||
readonly key?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for downloadFiles operation in AssetApi.
|
||||
* @export
|
||||
* @interface AssetApiDownloadFilesRequest
|
||||
*/
|
||||
export interface AssetApiDownloadFilesRequest {
|
||||
/**
|
||||
*
|
||||
* @type {DownloadFilesDto}
|
||||
* @memberof AssetApiDownloadFiles
|
||||
*/
|
||||
readonly downloadFilesDto: DownloadFilesDto
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetApiDownloadFiles
|
||||
*/
|
||||
readonly key?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for downloadLibrary operation in AssetApi.
|
||||
* @export
|
||||
* @interface AssetApiDownloadLibraryRequest
|
||||
*/
|
||||
export interface AssetApiDownloadLibraryRequest {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetApiDownloadLibrary
|
||||
*/
|
||||
readonly name?: string
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetApiDownloadLibrary
|
||||
*/
|
||||
readonly skip?: number
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetApiDownloadLibrary
|
||||
*/
|
||||
readonly key?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for getAllAssets operation in AssetApi.
|
||||
* @export
|
||||
|
|
@ -6650,6 +6533,48 @@ export interface AssetApiGetAssetThumbnailRequest {
|
|||
readonly key?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for getDownloadInfo operation in AssetApi.
|
||||
* @export
|
||||
* @interface AssetApiGetDownloadInfoRequest
|
||||
*/
|
||||
export interface AssetApiGetDownloadInfoRequest {
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof AssetApiGetDownloadInfo
|
||||
*/
|
||||
readonly assetIds?: Array<string>
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetApiGetDownloadInfo
|
||||
*/
|
||||
readonly albumId?: string
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetApiGetDownloadInfo
|
||||
*/
|
||||
readonly userId?: string
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetApiGetDownloadInfo
|
||||
*/
|
||||
readonly archiveSize?: number
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetApiGetDownloadInfo
|
||||
*/
|
||||
readonly key?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for getMapMarkers operation in AssetApi.
|
||||
* @export
|
||||
|
|
@ -6953,6 +6878,17 @@ export class AssetApi extends BaseAPI {
|
|||
return AssetApiFp(this.configuration).deleteAsset(requestParameters.deleteAssetDto, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiDownloadArchiveRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public downloadArchive(requestParameters: AssetApiDownloadArchiveRequest, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).downloadArchive(requestParameters.assetIdsDto, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiDownloadFileRequest} requestParameters Request parameters.
|
||||
|
|
@ -6964,28 +6900,6 @@ export class AssetApi extends BaseAPI {
|
|||
return AssetApiFp(this.configuration).downloadFile(requestParameters.id, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiDownloadFilesRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public downloadFiles(requestParameters: AssetApiDownloadFilesRequest, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).downloadFiles(requestParameters.downloadFilesDto, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Current this is not used in any UI element
|
||||
* @param {AssetApiDownloadLibraryRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public downloadLibrary(requestParameters: AssetApiDownloadLibraryRequest = {}, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).downloadLibrary(requestParameters.name, requestParameters.skip, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all AssetEntity belong to the user
|
||||
* @param {AssetApiGetAllAssetsRequest} requestParameters Request parameters.
|
||||
|
|
@ -7091,6 +7005,17 @@ export class AssetApi extends BaseAPI {
|
|||
return AssetApiFp(this.configuration).getCuratedObjects(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiGetDownloadInfoRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public getDownloadInfo(requestParameters: AssetApiGetDownloadInfoRequest = {}, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).getDownloadInfo(requestParameters.assetIds, requestParameters.albumId, requestParameters.userId, requestParameters.archiveSize, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiGetMapMarkersRequest} requestParameters Request parameters.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
import { afterNavigate, goto } from '$app/navigation';
|
||||
import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
|
||||
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
||||
import { downloadAssets } from '$lib/stores/download';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||
import {
|
||||
|
|
@ -45,6 +44,7 @@
|
|||
import ThumbnailSelection from './thumbnail-selection.svelte';
|
||||
import UserSelectionModal from './user-selection-modal.svelte';
|
||||
import { handleError } from '../../utils/handle-error';
|
||||
import { downloadArchive } from '../../utils/asset-utils';
|
||||
|
||||
export let album: AlbumResponseDto;
|
||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||
|
|
@ -242,78 +242,12 @@
|
|||
};
|
||||
|
||||
const downloadAlbum = async () => {
|
||||
try {
|
||||
let skip = 0;
|
||||
let count = 0;
|
||||
let done = false;
|
||||
|
||||
while (!done) {
|
||||
count++;
|
||||
|
||||
const fileName = album.albumName + `${count === 1 ? '' : count}.zip`;
|
||||
|
||||
$downloadAssets[fileName] = 0;
|
||||
|
||||
let total = 0;
|
||||
|
||||
const { data, status, headers } = await api.albumApi.downloadArchive(
|
||||
{ id: album.id, skip: skip || undefined, key: sharedLink?.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[fileName] = 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 {
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (!(data instanceof Blob)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 200) {
|
||||
const fileUrl = URL.createObjectURL(data);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = fileUrl;
|
||||
anchor.download = fileName;
|
||||
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
|
||||
URL.revokeObjectURL(fileUrl);
|
||||
|
||||
// Remove item from download list
|
||||
setTimeout(() => {
|
||||
const copy = $downloadAssets;
|
||||
delete copy[fileName];
|
||||
$downloadAssets = copy;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
$downloadAssets = {};
|
||||
console.error('Error downloading file ', e);
|
||||
notificationController.show({
|
||||
type: NotificationType.Error,
|
||||
message: 'Error downloading file, check console for more details.'
|
||||
});
|
||||
}
|
||||
await downloadArchive(
|
||||
`${album.albumName}.zip`,
|
||||
{ albumId: album.id },
|
||||
undefined,
|
||||
sharedLink?.key
|
||||
);
|
||||
};
|
||||
|
||||
const showAlbumOptionsMenu = ({ x, y }: MouseEvent) => {
|
||||
|
|
@ -360,7 +294,7 @@
|
|||
>
|
||||
<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
|
||||
{#if sharedLink?.allowDownload || !isPublicShared}
|
||||
<DownloadAction filename={album.albumName} sharedLinkKey={sharedLink?.key} />
|
||||
<DownloadAction filename="{album.albumName}.zip" sharedLinkKey={sharedLink?.key} />
|
||||
{/if}
|
||||
{#if isOwned}
|
||||
<RemoveFromAlbum bind:album />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { downloadAssets } from '$lib/stores/download';
|
||||
import {
|
||||
AlbumResponseDto,
|
||||
api,
|
||||
|
|
@ -25,7 +24,7 @@
|
|||
|
||||
import { assetStore } from '$lib/stores/assets.store';
|
||||
import { isShowDetail } from '$lib/stores/preferences.store';
|
||||
import { addAssetsToAlbum, getFilenameExtension } from '$lib/utils/asset-utils';
|
||||
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let asset: AssetResponseDto;
|
||||
|
|
@ -115,75 +114,6 @@
|
|||
$isShowDetail = !$isShowDetail;
|
||||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
if (asset.livePhotoVideoId) {
|
||||
downloadFile(asset.livePhotoVideoId, true, publicSharedKey);
|
||||
downloadFile(asset.id, false, publicSharedKey);
|
||||
return;
|
||||
}
|
||||
|
||||
downloadFile(asset.id, false, publicSharedKey);
|
||||
};
|
||||
|
||||
const downloadFile = async (assetId: string, isLivePhoto: boolean, key: string) => {
|
||||
try {
|
||||
const imageExtension = isLivePhoto ? 'mov' : getFilenameExtension(asset.originalPath);
|
||||
const imageFileName = asset.originalFileName + '.' + imageExtension;
|
||||
|
||||
// If assets is already download -> return;
|
||||
if ($downloadAssets[imageFileName]) {
|
||||
return;
|
||||
}
|
||||
|
||||
$downloadAssets[imageFileName] = 0;
|
||||
|
||||
const { data, status } = await api.assetApi.downloadFile(
|
||||
{ id: assetId, key },
|
||||
{
|
||||
responseType: 'blob',
|
||||
onDownloadProgress: (progressEvent) => {
|
||||
if (progressEvent.lengthComputable) {
|
||||
const total = progressEvent.total;
|
||||
const current = progressEvent.loaded;
|
||||
$downloadAssets[imageFileName] = Math.floor((current / total) * 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!(data instanceof Blob)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 200) {
|
||||
const fileUrl = URL.createObjectURL(data);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = fileUrl;
|
||||
anchor.download = imageFileName;
|
||||
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
|
||||
URL.revokeObjectURL(fileUrl);
|
||||
|
||||
// Remove item from download list
|
||||
setTimeout(() => {
|
||||
const copy = $downloadAssets;
|
||||
delete copy[imageFileName];
|
||||
$downloadAssets = copy;
|
||||
}, 2000);
|
||||
}
|
||||
} catch (e) {
|
||||
$downloadAssets = {};
|
||||
console.error('Error downloading file ', e);
|
||||
notificationController.show({
|
||||
type: NotificationType.Error,
|
||||
message: 'Error downloading file, check console for more details.'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAsset = async () => {
|
||||
try {
|
||||
if (
|
||||
|
|
@ -313,7 +243,7 @@
|
|||
showDownloadButton={shouldShowDownloadButton}
|
||||
on:goBack={closeViewer}
|
||||
on:showDetail={showDetailInfoHandler}
|
||||
on:download={handleDownload}
|
||||
on:download={() => downloadFile(asset, publicSharedKey)}
|
||||
on:delete={deleteAsset}
|
||||
on:favorite={toggleFavorite}
|
||||
on:addToAlbum={() => openAlbumPicker(false)}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,30 @@
|
|||
<script lang="ts">
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import { bulkDownload } from '$lib/utils/asset-utils';
|
||||
import { downloadArchive, downloadFile } from '$lib/utils/asset-utils';
|
||||
import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
|
||||
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||
|
||||
export let filename = 'immich';
|
||||
export let filename = 'immich.zip';
|
||||
export let sharedLinkKey: string | undefined = undefined;
|
||||
export let menuItem = false;
|
||||
|
||||
const { getAssets, clearSelect } = getAssetControlContext();
|
||||
|
||||
const handleDownloadFiles = async () => {
|
||||
await bulkDownload(filename, Array.from(getAssets()), clearSelect, sharedLinkKey);
|
||||
const assets = Array.from(getAssets());
|
||||
if (assets.length === 1) {
|
||||
await downloadFile(assets[0], sharedLinkKey);
|
||||
clearSelect();
|
||||
return;
|
||||
}
|
||||
|
||||
await downloadArchive(
|
||||
filename,
|
||||
{ assetIds: assets.map((asset) => asset.id) },
|
||||
clearSelect,
|
||||
sharedLinkKey
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { bulkDownload } from '$lib/utils/asset-utils';
|
||||
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||
import { downloadArchive } from '$lib/utils/asset-utils';
|
||||
import { api, AssetResponseDto, SharedLinkResponseDto } from '@api';
|
||||
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
||||
import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
|
||||
|
|
@ -38,7 +38,12 @@
|
|||
});
|
||||
|
||||
const downloadAssets = async () => {
|
||||
await bulkDownload('immich-shared', assets, undefined, sharedLink.key);
|
||||
await downloadArchive(
|
||||
`immich-shared.zip`,
|
||||
{ assetIds: assets.map((asset) => asset.id) },
|
||||
undefined,
|
||||
sharedLink.key
|
||||
);
|
||||
};
|
||||
|
||||
const handleUploadAssets = async (files: File[] = []) => {
|
||||
|
|
@ -78,7 +83,7 @@
|
|||
<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
|
||||
<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
|
||||
{#if sharedLink?.allowDownload}
|
||||
<DownloadAction filename="immich-shared" sharedLinkKey={sharedLink.key} />
|
||||
<DownloadAction filename="immich-shared.zip" sharedLinkKey={sharedLink.key} />
|
||||
{/if}
|
||||
{#if isOwned}
|
||||
<RemoveFromSharedLink bind:sharedLink />
|
||||
|
|
|
|||
|
|
@ -9,3 +9,18 @@ export const isDownloading = derived(downloadAssets, ($downloadAssets) => {
|
|||
|
||||
return true;
|
||||
});
|
||||
|
||||
const update = (key: string, value: number | null) => {
|
||||
downloadAssets.update((state) => {
|
||||
const newState = { ...state };
|
||||
if (value === null) {
|
||||
delete newState[key];
|
||||
} else {
|
||||
newState[key] = value;
|
||||
}
|
||||
return newState;
|
||||
});
|
||||
};
|
||||
|
||||
export const clearDownload = (key: string) => update(key, null);
|
||||
export const updateDownload = (key: string, value: number) => update(key, value);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
import { api, AddAssetsResponseDto, AssetResponseDto } from '@api';
|
||||
import {
|
||||
notificationController,
|
||||
NotificationType
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { downloadAssets } from '$lib/stores/download';
|
||||
import { clearDownload, updateDownload } from '$lib/stores/download';
|
||||
import {
|
||||
AddAssetsResponseDto,
|
||||
api,
|
||||
AssetApiGetDownloadInfoRequest,
|
||||
AssetResponseDto,
|
||||
DownloadResponseDto
|
||||
} from '@api';
|
||||
import { handleError } from './handle-error';
|
||||
|
||||
export const addAssetsToAlbum = async (
|
||||
albumId: string,
|
||||
|
|
@ -24,84 +31,104 @@ export const addAssetsToAlbum = async (
|
|||
return dto;
|
||||
});
|
||||
|
||||
export async function bulkDownload(
|
||||
const downloadBlob = (data: Blob, filename: string) => {
|
||||
const url = URL.createObjectURL(data);
|
||||
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = filename;
|
||||
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export const downloadArchive = async (
|
||||
fileName: string,
|
||||
assets: AssetResponseDto[],
|
||||
options: Omit<AssetApiGetDownloadInfoRequest, 'key'>,
|
||||
onDone?: () => void,
|
||||
key?: string
|
||||
) {
|
||||
const assetIds = assets.map((asset) => asset.id);
|
||||
) => {
|
||||
let downloadInfo: DownloadResponseDto | null = null;
|
||||
|
||||
try {
|
||||
// let skip = 0;
|
||||
let count = 0;
|
||||
let done = false;
|
||||
const { data } = await api.assetApi.getDownloadInfo({ ...options, key });
|
||||
downloadInfo = data;
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to download files');
|
||||
return;
|
||||
}
|
||||
|
||||
while (!done) {
|
||||
count++;
|
||||
// TODO: prompt for big download
|
||||
// const total = downloadInfo.totalSize;
|
||||
|
||||
const downloadFileName = fileName + `${count === 1 ? '' : count}.zip`;
|
||||
downloadAssets.set({ [downloadFileName]: 0 });
|
||||
for (let i = 0; i < downloadInfo.archives.length; i++) {
|
||||
const archive = downloadInfo.archives[i];
|
||||
const suffix = downloadInfo.archives.length === 1 ? '' : `+${i + 1}`;
|
||||
const archiveName = fileName.replace('.zip', `${suffix}.zip`);
|
||||
|
||||
let total = 0;
|
||||
let downloadKey = `${archiveName}`;
|
||||
if (downloadInfo.archives.length > 1) {
|
||||
downloadKey = `${archiveName} (${i + 1}/${downloadInfo.archives.length})`;
|
||||
}
|
||||
|
||||
const { data, status, headers } = await api.assetApi.downloadFiles(
|
||||
{ downloadFilesDto: { assetIds }, key },
|
||||
updateDownload(downloadKey, 0);
|
||||
|
||||
try {
|
||||
const { data } = await api.assetApi.downloadArchive(
|
||||
{ assetIdsDto: { assetIds: archive.assetIds }, key },
|
||||
{
|
||||
responseType: 'blob',
|
||||
onDownloadProgress: function (progressEvent) {
|
||||
const request = this as XMLHttpRequest;
|
||||
if (!total) {
|
||||
total = Number(request.getResponseHeader('X-Immich-Content-Length-Hint')) || 0;
|
||||
}
|
||||
onDownloadProgress: (event) =>
|
||||
updateDownload(downloadKey, Math.floor((event.loaded / archive.size) * 100))
|
||||
}
|
||||
);
|
||||
|
||||
if (total) {
|
||||
const current = progressEvent.loaded;
|
||||
downloadAssets.set({ [downloadFileName]: Math.floor((current / total) * 100) });
|
||||
downloadBlob(data, archiveName);
|
||||
} catch (e) {
|
||||
handleError(e, 'Unable to download files');
|
||||
clearDownload(downloadKey);
|
||||
return;
|
||||
} finally {
|
||||
setTimeout(() => clearDownload(downloadKey), 3_000);
|
||||
}
|
||||
}
|
||||
|
||||
onDone?.();
|
||||
};
|
||||
|
||||
export const downloadFile = async (asset: AssetResponseDto, key?: string) => {
|
||||
const filenames = [`${asset.originalFileName}.${getFilenameExtension(asset.originalPath)}`];
|
||||
if (asset.livePhotoVideoId) {
|
||||
filenames.push(`${asset.originalFileName}.mov`);
|
||||
}
|
||||
|
||||
for (const filename of filenames) {
|
||||
try {
|
||||
updateDownload(filename, 0);
|
||||
|
||||
const { data } = await api.assetApi.downloadFile(
|
||||
{ id: asset.id, key },
|
||||
{
|
||||
responseType: 'blob',
|
||||
onDownloadProgress: (event: ProgressEvent) => {
|
||||
if (event.lengthComputable) {
|
||||
updateDownload(filename, Math.floor((event.loaded / event.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);
|
||||
}
|
||||
downloadBlob(data, filename);
|
||||
} catch (e) {
|
||||
handleError(e, `Error downloading ${filename}`);
|
||||
} finally {
|
||||
setTimeout(() => clearDownload(filename), 3_000);
|
||||
}
|
||||
} 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
|
||||
|
|
|
|||
|
|
@ -4,10 +4,20 @@ import {
|
|||
NotificationType
|
||||
} from '../components/shared-components/notification/notification';
|
||||
|
||||
export function handleError(error: unknown, message: string) {
|
||||
export async function handleError(error: unknown, message: string) {
|
||||
console.error(`[handleError]: ${message}`, error);
|
||||
|
||||
let serverMessage = (error as ApiError)?.response?.data?.message;
|
||||
let data = (error as ApiError)?.response?.data;
|
||||
if (data instanceof Blob) {
|
||||
const response = await data.text();
|
||||
try {
|
||||
data = JSON.parse(response);
|
||||
} catch {
|
||||
data = { message: response };
|
||||
}
|
||||
}
|
||||
|
||||
let serverMessage = data?.message;
|
||||
if (serverMessage) {
|
||||
serverMessage = `${String(serverMessage).slice(0, 75)}\n(Immich Server Error)`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
</AssetSelectContextMenu>
|
||||
<DeleteAssets {onAssetDelete} />
|
||||
<AssetSelectContextMenu icon={DotsVertical} title="Add">
|
||||
<DownloadAction menuItem />
|
||||
<DownloadAction menuItem filename="{data.person.name || 'immich'}.zip" />
|
||||
<FavoriteAction menuItem removeFavorite={isAllFavorite} />
|
||||
<ArchiveAction
|
||||
menuItem
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue