Merge feature-download-zip
This commit is contained in:
@@ -87,6 +87,7 @@ DEBUG=psitransfer:* npm start
|
||||
## Side notes
|
||||
|
||||
* There is no (end-to-end) payload encryption (yet).
|
||||
* `Download all as ZIP` does not support resuming the download.
|
||||
|
||||
:star2: Contribution is highly welcome :metal:
|
||||
|
||||
|
||||
@@ -19,7 +19,16 @@
|
||||
i.fa.fa-key
|
||||
| decrypt
|
||||
.panel.panel-primary(v-if='!needsPassword')
|
||||
.panel-heading Files
|
||||
.panel-heading
|
||||
strong Files
|
||||
div.pull-right(style="margin-top:-5px;")
|
||||
span.btn-group(v-if="downloadsAvailable")
|
||||
a.btn.btn-sm.btn-default(@click="downloadAll('zip')", title="Archive download is not resumeable!")
|
||||
i.fa.fa-fw.fa-fw.fa-download
|
||||
| zip
|
||||
a.btn.btn-sm.btn-default(@click="downloadAll('tar.gz')", title="Archive download is not resumeable!")
|
||||
i.fa.fa-fw.fa-fw.fa-download
|
||||
| tar.gz
|
||||
.panel-body
|
||||
table.table.table-hover.table-striped(style='margin-bottom: 0')
|
||||
tbody
|
||||
@@ -32,10 +41,8 @@
|
||||
a
|
||||
i.fa.fa-fw.fa-copy
|
||||
i.fa.fa-check.text-success.pull-right(v-show='file.downloaded')
|
||||
|
|
||||
strong {{ file.metadata.name }}
|
||||
|
|
||||
small ({{ humanFileSize(file.size) }})
|
||||
strong {{ file.metadata.name }}
|
||||
small(v-if="Number.isFinite(file.size)") ({{ humanFileSize(file.size) }})
|
||||
p {{ file.metadata.comment }}
|
||||
</template>
|
||||
|
||||
@@ -44,6 +51,7 @@
|
||||
"use strict";
|
||||
import AES from 'crypto-js/aes';
|
||||
import encUtf8 from 'crypto-js/enc-utf8';
|
||||
import MD5 from 'crypto-js/md5';
|
||||
|
||||
import FileIcon from './common/FileIcon.vue';
|
||||
import Clipboard from './common/Clipboard.vue';
|
||||
@@ -51,6 +59,7 @@
|
||||
export default {
|
||||
name: 'app',
|
||||
components: { FileIcon, Clipboard },
|
||||
|
||||
data () {
|
||||
return {
|
||||
files: [],
|
||||
@@ -63,6 +72,13 @@
|
||||
host: document.location.protocol + '//' + document.location.host
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
downloadsAvailable: function() {
|
||||
return this.files.filter(f => !f.downloaded || f.metadata.retention !== 'one-time').length > 0
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
download(file) {
|
||||
if(file.downloaded && file.metadata.retention === 'one-time') {
|
||||
@@ -73,6 +89,20 @@
|
||||
file.downloaded = true;
|
||||
},
|
||||
|
||||
downloadAll(format) {
|
||||
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() + '.' + format;
|
||||
|
||||
this.files.forEach(f => {
|
||||
f.downloaded = true;
|
||||
});
|
||||
},
|
||||
|
||||
copied(file, $event) {
|
||||
file.downloaded = $event === 'copied';
|
||||
},
|
||||
|
||||
@@ -11,7 +11,10 @@ const fs = require("fs");
|
||||
const tusMeta = require('tus-metadata');
|
||||
const assert = require('assert');
|
||||
const AES = require("crypto-js/aes");
|
||||
const MD5 = require("crypto-js/md5");
|
||||
const debug = require('debug')('psitransfer:main');
|
||||
const archiver = require('archiver');
|
||||
const zlib = require('zlib');
|
||||
|
||||
const errorPage = fs.readFileSync(path.join(__dirname, '../public/html/error.html')).toString();
|
||||
const store = new Store(config.uploadDir);
|
||||
@@ -72,11 +75,64 @@ app.get('/:sid', (req, res, next) => {
|
||||
});
|
||||
|
||||
|
||||
// Download single file
|
||||
// Download files
|
||||
app.get('/files/:fid', async(req, res, next) => {
|
||||
// let tusboy handle HEAD with Tus Header
|
||||
// let tusboy handle HEAD requests with Tus Header
|
||||
if(req.method === 'HEAD' && req.get('Tus-Resumable')) return next();
|
||||
|
||||
// Download all files
|
||||
if(req.params.fid.match(/^[a-z0-9+]+\.(tar\.gz|zip)$/)) {
|
||||
const sid = req.params.fid.split('++')[0];
|
||||
const format = req.params.fid.endsWith('.zip') ? 'zip' : 'tar.gz';
|
||||
const bucket = db.get(sid);
|
||||
|
||||
if(!bucket) return res.status(404).send(errorPage.replace('%%ERROR%%', 'Download bucket not found.'));
|
||||
|
||||
if(req.params.fid !== sid + '++' + MD5(bucket.map(f => f.key).join()).toString() + '.' + format) {
|
||||
res.status(404).send(errorPage.replace('%%ERROR%%', 'Invalid link'));
|
||||
return;
|
||||
}
|
||||
debug(`Download Bucket ${sid}`);
|
||||
|
||||
if(format === 'zip') res.header('ContentType', 'application/zip');
|
||||
if(format === 'tar.gz') res.header('ContentType', 'application/x-gtar');
|
||||
res.header('Content-Disposition', `attachment; filename="${sid}.${format}"`);
|
||||
|
||||
const archive = archiver(format === 'zip' ? 'zip' : 'tar');
|
||||
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}
|
||||
);
|
||||
});
|
||||
|
||||
if(format === 'tar.gz') {
|
||||
archive.pipe(zlib.createGzip()).pipe(res);
|
||||
} else {
|
||||
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}`);
|
||||
try {
|
||||
const info = await store.info(req.params.fid); // throws on 404
|
||||
|
||||
@@ -97,7 +97,7 @@ class Store {
|
||||
if(!end) end = info.size - 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});
|
||||
}
|
||||
|
||||
12
package.json
12
package.json
@@ -2,11 +2,18 @@
|
||||
"name": "psitransfer",
|
||||
"version": "1.0.0",
|
||||
"description": "Open self-hoste file-sharing solution",
|
||||
"keywords": ["share","upload","transfer","files","wetransfer"],
|
||||
"keywords": [
|
||||
"share",
|
||||
"upload",
|
||||
"transfer",
|
||||
"files",
|
||||
"wetransfer"
|
||||
],
|
||||
"repository": "psi-4ward/psitransfer",
|
||||
"bugs": "https://github.com/psi-4ward/psitransfer/issues",
|
||||
"main": "app.js",
|
||||
"dependencies": {
|
||||
"archiver": "^1.3.0",
|
||||
"compression": "^1.6.2",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"debug": "^2.6.0",
|
||||
@@ -18,8 +25,7 @@
|
||||
"tusboy": "^1.1.1",
|
||||
"uuid": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production node app.js",
|
||||
"dev": "NODE_ENV=dev DEBUG=psitransfer:* nodemon -i app -i dist -i data app.js",
|
||||
|
||||
Reference in New Issue
Block a user