Add download bucket as zip archive

This commit is contained in:
Christoph Wiechert
2017-04-25 18:22:24 +02:00
parent 85a88a80c6
commit c7b354386d
4 changed files with 86 additions and 9 deletions

View File

@@ -19,7 +19,11 @@
i.fa.fa-key i.fa.fa-key
| decrypt | decrypt
.panel.panel-primary(v-if='!needsPassword') .panel.panel-primary(v-if='!needsPassword')
.panel-heading Files .panel-heading
a.pull-right(style="color:#fff", @click="downloadAll", v-if="downloadsAvailable")
i.fa.fa-fw.fa-download
| Download ZIP
| Files
.panel-body .panel-body
table.table.table-hover.table-striped(style='margin-bottom: 0') table.table.table-hover.table-striped(style='margin-bottom: 0')
tbody tbody
@@ -32,10 +36,8 @@
a a
i.fa.fa-fw.fa-copy i.fa.fa-fw.fa-copy
i.fa.fa-check.text-success.pull-right(v-show='file.downloaded') i.fa.fa-check.text-success.pull-right(v-show='file.downloaded')
| strong {{ file.metadata.name }}
strong {{ file.metadata.name }} small(v-if="Number.isFinite(file.size)") ({{ humanFileSize(file.size) }})
|
small ({{ humanFileSize(file.size) }})
p {{ file.metadata.comment }} p {{ file.metadata.comment }}
</template> </template>
@@ -44,6 +46,7 @@
"use strict"; "use strict";
import AES from 'crypto-js/aes'; import AES from 'crypto-js/aes';
import encUtf8 from 'crypto-js/enc-utf8'; import encUtf8 from 'crypto-js/enc-utf8';
import MD5 from 'crypto-js/md5';
import FileIcon from './common/FileIcon.vue'; import FileIcon from './common/FileIcon.vue';
import Clipboard from './common/Clipboard.vue'; import Clipboard from './common/Clipboard.vue';
@@ -51,6 +54,7 @@
export default { export default {
name: 'app', name: 'app',
components: { FileIcon, Clipboard }, components: { FileIcon, Clipboard },
data () { data () {
return { return {
files: [], files: [],
@@ -63,6 +67,13 @@
host: document.location.protocol + '//' + document.location.host host: document.location.protocol + '//' + document.location.host
} }
}, },
computed: {
downloadsAvailable: function() {
return this.files.some(f => !f.downloaded || f.metadata.retention !== 'one-time')
}
},
methods: { methods: {
download(file) { download(file) {
if(file.downloaded && file.metadata.retention === 'one-time') { if(file.downloaded && file.metadata.retention === 'one-time') {
@@ -73,6 +84,20 @@
file.downloaded = true; file.downloaded = true;
}, },
downloadAll() {
document.location.href = document.location.protocol + '//' + document.location.host
+ '/files/' + this.sid + '++'
+ MD5(
this.files
.filter(f => !f.downloaded || f.metadata.retention !== 'one-time')
.map(f => f.key).join()
).toString() + '.zip';
this.files.forEach(f => {
f.downloaded = true;
});
},
copied(file, $event) { copied(file, $event) {
file.downloaded = $event === 'copied'; file.downloaded = $event === 'copied';
}, },

View File

@@ -11,7 +11,9 @@ const fs = require("fs");
const tusMeta = require('tus-metadata'); const tusMeta = require('tus-metadata');
const assert = require('assert'); const assert = require('assert');
const AES = require("crypto-js/aes"); const AES = require("crypto-js/aes");
const MD5 = require("crypto-js/md5");
const debug = require('debug')('psitransfer:main'); const debug = require('debug')('psitransfer:main');
const archiver = require('archiver');
const errorPage = fs.readFileSync(path.join(__dirname, '../public/html/error.html')).toString(); const errorPage = fs.readFileSync(path.join(__dirname, '../public/html/error.html')).toString();
const store = new Store(config.uploadDir); const store = new Store(config.uploadDir);
@@ -73,6 +75,50 @@ app.get('/files/:fid', async(req, res, next) => {
// let tusboy handle HEAD with Tus Header // let tusboy handle HEAD with Tus Header
if(req.method === 'HEAD' && req.get('Tus-Resumable')) return next(); if(req.method === 'HEAD' && req.get('Tus-Resumable')) return next();
// Download all files
if(req.params.fid.endsWith('.zip')) {
const sid = req.params.fid.split('++')[0];
const bucket = db.get(sid);
if(req.params.fid !== sid + '++' + MD5(bucket.map(f => f.key).join()).toString() + '.zip') {
res.status(404).send(errorPage.replace('%%ERROR%%', 'Invalid link'));
return;
}
debug(`Download Bucket ${sid}`);
res.header('ContentType', 'application/zip');
res.header('Content-Disposition', 'attachment; filename="' + sid + '.zip"');
const archive = archiver('zip');
archive.on('error', function(err) {
console.error(err);
});
bucket.forEach(info => {
archive.append(
fs.createReadStream(store.getFilename(info.metadata.sid + '++' + info.key)),
{name: info.metadata.name}
);
});
archive.pipe(res);
archive.finalize();
try {
res.on('finish', async () => {
bucket.forEach(async info => {
if(info.metadata.retention === 'one-time') {
await db.remove(info.metadata.sid, info.metadata.key);
}
});
});
} catch(e) {
console.error(e);
}
return;
}
// Download single file
debug(`Download ${req.params.fid}`); debug(`Download ${req.params.fid}`);
try { try {
const info = await store.info(req.params.fid); // throws on 404 const info = await store.info(req.params.fid); // throws on 404

View File

@@ -97,7 +97,7 @@ class Store {
if(!end) end = info.size - 1; if(!end) end = info.size - 1;
contentLength = end - start + 1 contentLength = end - start + 1
} }
cb({ contentLength, metadata: info.metadata, info }); if(cb) cb({ contentLength, metadata: info.metadata, info });
}); });
return fsp.createReadStream(this.getFilename(fid), {start, end}); return fsp.createReadStream(this.getFilename(fid), {start, end});
} }

View File

@@ -2,11 +2,18 @@
"name": "psitransfer", "name": "psitransfer",
"version": "1.0.0", "version": "1.0.0",
"description": "Open self-hoste file-sharing solution", "description": "Open self-hoste file-sharing solution",
"keywords": ["share","upload","transfer","files","wetransfer"], "keywords": [
"share",
"upload",
"transfer",
"files",
"wetransfer"
],
"repository": "psi-4ward/psitransfer", "repository": "psi-4ward/psitransfer",
"bugs": "https://github.com/psi-4ward/psitransfer/issues", "bugs": "https://github.com/psi-4ward/psitransfer/issues",
"main": "app.js", "main": "app.js",
"dependencies": { "dependencies": {
"archiver": "^1.3.0",
"compression": "^1.6.2", "compression": "^1.6.2",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"debug": "^2.6.0", "debug": "^2.6.0",
@@ -18,8 +25,7 @@
"tusboy": "^1.1.1", "tusboy": "^1.1.1",
"uuid": "^3.0.1" "uuid": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {},
},
"scripts": { "scripts": {
"start": "NODE_ENV=production node app.js", "start": "NODE_ENV=production node app.js",
"dev": "NODE_ENV=dev DEBUG=psitransfer:* nodemon -i app -i dist -i data app.js", "dev": "NODE_ENV=dev DEBUG=psitransfer:* nodemon -i app -i dist -i data app.js",