1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

Download zip folder

This commit is contained in:
mcdamo 2021-04-28 21:37:37 +10:00
parent 6249d942c0
commit 6af288e77a
9 changed files with 295 additions and 5 deletions

193
package-lock.json generated
View File

@ -4167,6 +4167,74 @@
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"archiver": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz",
"integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==",
"requires": {
"archiver-utils": "^2.1.0",
"async": "^3.2.0",
"buffer-crc32": "^0.2.1",
"readable-stream": "^3.6.0",
"readdir-glob": "^1.0.0",
"tar-stream": "^2.2.0",
"zip-stream": "^4.1.0"
},
"dependencies": {
"async": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"archiver-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
"requires": {
"glob": "^7.1.4",
"graceful-fs": "^4.2.0",
"lazystream": "^1.0.0",
"lodash.defaults": "^4.2.0",
"lodash.difference": "^4.5.0",
"lodash.flatten": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.union": "^4.6.0",
"normalize-path": "^3.0.0",
"readable-stream": "^2.0.0"
},
"dependencies": {
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
}
}
},
"archy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
@ -5030,8 +5098,7 @@
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
"dev": true
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
},
"buffer-equal": {
"version": "0.0.1",
@ -5992,6 +6059,29 @@
"arity-n": "^1.0.4"
}
},
"compress-commons": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.0.tgz",
"integrity": "sha512-ofaaLqfraD1YRTkrRKPCrGJ1pFeDG/MVCkVVV2FNGeWquSlqw5wOrwOfPQ1xF2u+blpeWASie5EubHz+vsNIgA==",
"requires": {
"buffer-crc32": "^0.2.13",
"crc32-stream": "^4.0.1",
"normalize-path": "^3.0.0",
"readable-stream": "^3.6.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
@ -6498,6 +6588,36 @@
}
}
},
"crc-32": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz",
"integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"crc32-stream": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz",
"integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==",
"requires": {
"crc-32": "^1.2.0",
"readable-stream": "^3.4.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"create-ecdh": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
@ -8503,6 +8623,11 @@
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
"dev": true
},
"exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz",
"integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw=="
},
"expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@ -12071,7 +12196,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
"integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=",
"dev": true,
"requires": {
"readable-stream": "^2.0.5"
}
@ -12386,6 +12510,16 @@
"integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=",
"dev": true
},
"lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
},
"lodash.difference": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
"integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw="
},
"lodash.escape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz",
@ -12395,6 +12529,11 @@
"lodash._root": "^3.0.0"
}
},
"lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
},
"lodash.flattendeep": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
@ -12413,6 +12552,11 @@
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
"dev": true
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
},
"lodash.keys": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
@ -12469,6 +12613,11 @@
"lodash.escape": "^3.0.0"
}
},
"lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg="
},
"lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@ -14085,8 +14234,7 @@
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
},
"normalize-range": {
"version": "0.1.2",
@ -17932,6 +18080,11 @@
"integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=",
"dev": true
},
"printj": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
"integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ=="
},
"process": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz",
@ -18603,6 +18756,14 @@
"util-deprecate": "~1.0.1"
}
},
"readdir-glob": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz",
"integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==",
"requires": {
"minimatch": "^3.0.4"
}
},
"readdirp": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
@ -23987,6 +24148,28 @@
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true
},
"zip-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz",
"integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==",
"requires": {
"archiver-utils": "^2.1.0",
"compress-commons": "^4.1.0",
"readable-stream": "^3.6.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"zone.js": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.4.tgz",

View File

@ -33,6 +33,7 @@
"url": "https://github.com/bpatrik/PiGallery2/issues"
},
"dependencies": {
"archiver": "^5.3.0",
"bcrypt": "5.0.1",
"body-parser": "1.19.0",
"cookie-parser": "1.4.5",

View File

@ -16,6 +16,7 @@ import {QueryParams} from '../../common/QueryParams';
import {VideoProcessing} from '../model/fileprocessing/VideoProcessing';
import {SearchQueryDTO, SearchQueryTypes} from '../../common/entities/SearchQueryDTO';
import {LocationLookupException} from '../exceptions/LocationLookupException';
import {SupportedFormats} from '../../common/SupportedFormats';
export class GalleryMWs {
@ -56,6 +57,75 @@ export class GalleryMWs {
}
}
public static async zipDirectory(req: Request, res: Response, next: NextFunction): Promise<any> {
if (Config.Client.Other.enableDownloadZip === false) {
return next();
}
const directoryName = req.params.directory || '/';
const absoluteDirectoryName = path.join(ProjectPath.ImageFolder, directoryName);
try {
if ((await fsp.stat(absoluteDirectoryName)).isDirectory() === false) {
return next();
}
} catch (e) {
return next();
}
try {
const directory = await ObjectManagers.getInstance()
.GalleryManager.listDirectory(directoryName,
parseInt(req.query[QueryParams.gallery.knownLastModified] as string, 10),
parseInt(req.query[QueryParams.gallery.knownLastScanned] as string, 10));
if (directory == null) {
req.resultPipe = new ContentWrapper(null, null, true);
return next();
}
if (req.session.user.permissions &&
req.session.user.permissions.length > 0 &&
req.session.user.permissions[0] !== '/*') {
(directory as DirectoryDTO).directories = (directory as DirectoryDTO).directories.filter((d): boolean =>
UserDTOUtils.isDirectoryAvailable(d, req.session.user.permissions));
}
res.set('Content-Type', 'application/zip');
res.set('Content-Disposition', 'attachment; filename=Gallery.zip');
const fs = require('fs');
const archiver = require('archiver');
const archive = archiver('zip');
res.on('close', function() {
console.log('zip ' + archive.pointer() + ' bytes');
});
archive.on('error', function(err: any) {
throw err;
});
archive.pipe(res);
// append photos in selected directory
for (const ext of SupportedFormats.WithDots.Photos) {
archive.glob(`*${ext}`, {cwd:absoluteDirectoryName, nocase:true});
}
// append videos in selected directory
for (const ext of SupportedFormats.WithDots.Videos) {
archive.glob(`*${ext}`, {cwd:absoluteDirectoryName, nocase:true});
}
await archive.finalize(function(err: any) {
if (err) {
throw err;
}
req.resultPipe = true;
});
return next();
} catch (err) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error creating zip', err));
}
}
public static cleanUpGalleryResults(req: Request, res: Response, next: NextFunction): any {
if (!req.resultPipe) {

View File

@ -71,6 +71,13 @@ export class RenderingMWs {
return res.sendFile(req.resultPipe, {maxAge: 31536000, dotfiles: 'allow'});
}
public static renderZipStream(req: Request, res: Response, next: NextFunction): any {
if (!req.resultPipe) {
return next();
}
return res;
}
public static renderOK(req: Request, res: Response, next: NextFunction): void {
const message = new Message<string>(null, 'ok');
res.json(message);

View File

@ -23,6 +23,7 @@ export class GalleryRouter {
this.addGetMetaFile(app);
this.addRandom(app);
this.addDirectoryList(app);
this.addDirectoryZip(app);
this.addSearch(app);
this.addAutoComplete(app);
@ -44,6 +45,18 @@ export class GalleryRouter {
);
}
protected static addDirectoryZip(app: Express): void {
app.get(['/api/gallery/zip/:directory(*)'],
// common part
AuthenticationMWs.authenticate,
AuthenticationMWs.normalizePathParam('directory'),
AuthenticationMWs.authorisePath('directory', true),
// specific part
GalleryMWs.zipDirectory,
RenderingMWs.renderZipStream
);
}
protected static addGetImage(app: Express): void {
app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))'],

View File

@ -105,6 +105,8 @@ export class ClientOtherConfig {
NavBar: NavBarConfig = new NavBarConfig();
@ConfigProperty()
captionFirstNaming: boolean = false; // shows the caption instead of the filename in the photo grid
@ConfigProperty()
enableDownloadZip: boolean = false;
}
@SubConfigClass()

View File

@ -15,6 +15,9 @@
</ol>
<div class="right-side">
<a *ngIf="config.Client.Other.enableDownloadZip && directory && ItemCount > 0" [href]="'/api/gallery/zip/' + getDirectoryPath()"
[queryParams]="queryService.getParams()">Download</a>
<div class="divider" *ngIf="directory && ItemCount > 0">&nbsp;</div>
<div class="photos-count" *ngIf="ItemCount > 0 && config.Client.Other.NavBar.showItemCount">
{{ItemCount}} <span i18n>items</span>
</div>

View File

@ -93,6 +93,10 @@ export class GalleryNavigatorComponent implements OnChanges {
this.galleryService.setSorting(sorting);
}
getDirectoryPath(): string {
return Utils.concatUrls(this.directory.path, this.directory.name);
}
}
interface NavigatorPath {

View File

@ -60,6 +60,13 @@
[ngModel]="states.Client.captionFirstNaming">
</app-settings-entry>
<app-settings-entry
name="Download Zip"
description="Enable download zip of a directory contents"
i18n-description i18n-name
[ngModel]="states.Client.enableDownloadZip">
</app-settings-entry>
<hr/>