Add download bucket as zip archive
This commit is contained in:
@@ -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';
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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});
|
||||||
}
|
}
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user