feat(server): multi archive downloads (#956)

This commit is contained in:
Jason Rasmussen
2022-11-15 10:51:56 -05:00
committed by GitHub
parent b5d75e2016
commit f2f255e6e6
26 changed files with 538 additions and 151 deletions

View File

@@ -294,6 +294,12 @@ export interface AssetCountByTimeBucketResponseDto {
* @interface AssetCountByUserIdResponseDto
*/
export interface AssetCountByUserIdResponseDto {
/**
*
* @type {number}
* @memberof AssetCountByUserIdResponseDto
*/
'audio': number;
/**
*
* @type {number}
@@ -306,6 +312,18 @@ export interface AssetCountByUserIdResponseDto {
* @memberof AssetCountByUserIdResponseDto
*/
'videos': number;
/**
*
* @type {number}
* @memberof AssetCountByUserIdResponseDto
*/
'other': number;
/**
*
* @type {number}
* @memberof AssetCountByUserIdResponseDto
*/
'total': number;
}
/**
*
@@ -1898,10 +1916,11 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration
/**
*
* @param {string} albumId
* @param {number} [skip]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
downloadArchive: async (albumId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
downloadArchive: async (albumId: string, skip?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'albumId' is not null or undefined
assertParamExists('downloadArchive', 'albumId', albumId)
const localVarPath = `/album/{albumId}/download`
@@ -1921,6 +1940,10 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (skip !== undefined) {
localVarQueryParameter['skip'] = skip;
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
@@ -2227,11 +2250,12 @@ export const AlbumApiFp = function(configuration?: Configuration) {
/**
*
* @param {string} albumId
* @param {number} [skip]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async downloadArchive(albumId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadArchive(albumId, options);
async downloadArchive(albumId: string, skip?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadArchive(albumId, skip, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
@@ -2348,11 +2372,12 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath
/**
*
* @param {string} albumId
* @param {number} [skip]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
downloadArchive(albumId: string, options?: any): AxiosPromise<object> {
return localVarFp.downloadArchive(albumId, options).then((request) => request(axios, basePath));
downloadArchive(albumId: string, skip?: number, options?: any): AxiosPromise<object> {
return localVarFp.downloadArchive(albumId, skip, options).then((request) => request(axios, basePath));
},
/**
*
@@ -2470,12 +2495,13 @@ export class AlbumApi extends BaseAPI {
/**
*
* @param {string} albumId
* @param {number} [skip]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AlbumApi
*/
public downloadArchive(albumId: string, options?: AxiosRequestConfig) {
return AlbumApiFp(this.configuration).downloadArchive(albumId, options).then((request) => request(this.axios, this.basePath));
public downloadArchive(albumId: string, skip?: number, options?: AxiosRequestConfig) {
return AlbumApiFp(this.configuration).downloadArchive(albumId, skip, options).then((request) => request(this.axios, this.basePath));
}
/**
@@ -2722,6 +2748,44 @@ 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 {number} [skip]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
downloadLibrary: async (skip?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/asset/download-library`;
// 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 bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (skip !== undefined) {
localVarQueryParameter['skip'] = skip;
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@@ -3332,6 +3396,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(aid, did, isThumb, isWeb, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {number} [skip]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async downloadLibrary(skip?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadLibrary(skip, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
* Get all AssetEntity belong to the user
* @summary
@@ -3527,6 +3601,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
downloadFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: any): AxiosPromise<object> {
return localVarFp.downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(axios, basePath));
},
/**
*
* @param {number} [skip]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
downloadLibrary(skip?: number, options?: any): AxiosPromise<object> {
return localVarFp.downloadLibrary(skip, options).then((request) => request(axios, basePath));
},
/**
* Get all AssetEntity belong to the user
* @summary
@@ -3716,6 +3799,17 @@ export class AssetApi extends BaseAPI {
return AssetApiFp(this.configuration).downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {number} [skip]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AssetApi
*/
public downloadLibrary(skip?: number, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).downloadLibrary(skip, options).then((request) => request(this.axios, this.basePath));
}
/**
* Get all AssetEntity belong to the user
* @summary

View File

@@ -313,53 +313,69 @@
const downloadAlbum = async () => {
try {
const fileName = album.albumName + '.zip';
let skip = 0;
let count = 0;
let done = false;
// If assets is already download -> return;
if ($downloadAssets[fileName]) {
return;
}
while (!done) {
count++;
$downloadAssets[fileName] = 0;
const fileName = album.albumName + `${count === 1 ? '' : count}.zip`;
let total = 0;
const { data, status } = await api.albumApi.downloadArchive(album.id, {
responseType: 'blob',
onDownloadProgress: function (progressEvent) {
const request = this as XMLHttpRequest;
if (!total) {
total = Number(request.getResponseHeader('X-Immich-Content-Length-Hint')) || 0;
$downloadAssets[fileName] = 0;
let total = 0;
const { data, status, headers } = await api.albumApi.downloadArchive(
album.id,
skip || undefined,
{
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);
}
}
}
);
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 (!(data instanceof Blob)) {
return;
}
if (status === 200) {
const fileUrl = URL.createObjectURL(data);
const anchor = document.createElement('a');
anchor.href = fileUrl;
anchor.download = fileName;
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);
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
URL.revokeObjectURL(fileUrl);
URL.revokeObjectURL(fileUrl);
// Remove item from download list
setTimeout(() => {
const copy = $downloadAssets;
delete copy[fileName];
$downloadAssets = copy;
}, 2000);
// Remove item from download list
setTimeout(() => {
const copy = $downloadAssets;
delete copy[fileName];
$downloadAssets = copy;
}, 2000);
}
}
} catch (e) {
console.error('Error downloading file ', e);