Added album downloading through front-end

This commit is contained in:
Pitu 2017-10-04 02:05:38 -03:00
parent 8d8dbc7078
commit 992b632d1a
9 changed files with 93 additions and 6 deletions

View File

@ -62,7 +62,14 @@ module.exports = {
to install a separate binary called graphicsmagick (http://www.graphicsmagick.org) to install a separate binary called graphicsmagick (http://www.graphicsmagick.org)
for images and ffmpeg (https://ffmpeg.org/) for video files for images and ffmpeg (https://ffmpeg.org/) for video files
*/ */
generateThumbnails: false generateThumbnails: false,
/*
Allows users to download a .zip file of all files in an album.
The file is generated when the user clicks the download button in the view
and is re-used if the album has not changed between download requests
*/
generateZips: true
}, },
// Folder where to store logs // Folder where to store logs

View File

@ -3,7 +3,8 @@ const db = require('knex')(config.database);
const randomstring = require('randomstring'); const randomstring = require('randomstring');
const utils = require('./utilsController.js'); const utils = require('./utilsController.js');
const path = require('path'); const path = require('path');
const fs = require('fs');
const Zip = require('jszip');
const albumsController = {}; const albumsController = {};
albumsController.list = async (req, res, next) => { albumsController.list = async (req, res, next) => {
@ -129,4 +130,44 @@ albumsController.get = async (req, res, next) => {
}); });
}; };
albumsController.generateZip = async (req, res, next) => {
const identifier = req.params.identifier;
if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' });
if (!config.uploads.generateZips) return res.status(401).json({ success: false, description: 'Zip generation disabled' });
const album = await db.table('albums').where({ identifier, enabled: 1 }).first();
if (!album) return res.json({ success: false, description: 'Album not found' });
if (album.zipGeneratedAt > album.editedAt) {
const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`);
const fileName = `${album.name}.zip`;
return res.download(filePath, fileName);
} else {
console.log(`Generating zip for album identifier: ${identifier}`);
const files = await db.table('files').select('name').where('albumid', album.id);
if (files.length === 0) return res.json({ success: false, description: 'There are no files in the album' });
const zipPath = path.join(__dirname, '..', config.uploads.folder, 'zips', `${album.identifier}.zip`);
let archive = new Zip();
for (let file of files) {
archive.file(file.name, fs.readFileSync(path.join(__dirname, '..', config.uploads.folder, file.name)));
}
archive
.generateNodeStream({ type: 'nodebuffer', streamFiles: true })
.pipe(fs.createWriteStream(zipPath))
.on('finish', async () => {
await db.table('albums')
.where('id', album.id)
.update({ zipGeneratedAt: Math.floor(Date.now() / 1000) });
const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`);
const fileName = `${album.name}.zip`;
return res.download(filePath, fileName);
});
}
};
module.exports = albumsController; module.exports = albumsController;

View File

@ -42,7 +42,7 @@ uploadsController.upload = async (req, res, next) => {
const albumid = req.headers.albumid || req.params.albumid; const albumid = req.headers.albumid || req.params.albumid;
if (albumid && user) { if (albumid && user) {
const album = await db.table('albums').where({ id: album, userid: user.id }).first(); const album = await db.table('albums').where({ id: albumid, userid: user.id }).first();
if (!album) { if (!album) {
return res.json({ return res.json({
success: false, success: false,
@ -150,6 +150,11 @@ uploadsController.processFilesForDisplay = async (req, res, files, existingFiles
file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`; file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`;
utils.generateThumbs(file); utils.generateThumbs(file);
} }
if (file.albumid) {
db.table('albums').where('id', file.albumid).update('editedAt', file.timestamp).then(() => {})
.catch(error => { console.log(error); res.json({ success: false, description: 'Error updating album' }); });
}
} }
}; };
@ -172,6 +177,9 @@ uploadsController.delete = async (req, res) => {
try { try {
await uploadsController.deleteFile(file.name); await uploadsController.deleteFile(file.name);
await db.table('files').where('id', id).del(); await db.table('files').where('id', id).del();
if (file.albumid) {
await db.table('albums').where('id', file.albumid).update('editedAt', Math.floor(Date.now() / 1000));
}
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }

13
database/migration.js Normal file
View File

@ -0,0 +1,13 @@
const config = require('../config.js');
const db = require('knex')(config.database);
const migration = {};
migration.start = async () => {
await db.schema.table('albums', table => {
table.dateTime('editedAt');
table.dateTime('zipGeneratedAt');
});
console.log('Migration finished! Now start lolisafe normally');
};
migration.start();

View File

@ -16,6 +16,7 @@ fs.existsSync('./pages/custom' ) || fs.mkdirSync('./pages/custom');
fs.existsSync('./' + config.logsFolder) || fs.mkdirSync('./' + config.logsFolder); fs.existsSync('./' + config.logsFolder) || fs.mkdirSync('./' + config.logsFolder);
fs.existsSync('./' + config.uploads.folder) || fs.mkdirSync('./' + config.uploads.folder); fs.existsSync('./' + config.uploads.folder) || fs.mkdirSync('./' + config.uploads.folder);
fs.existsSync('./' + config.uploads.folder + '/thumbs') || fs.mkdirSync('./' + config.uploads.folder + '/thumbs'); fs.existsSync('./' + config.uploads.folder + '/thumbs') || fs.mkdirSync('./' + config.uploads.folder + '/thumbs');
fs.existsSync('./' + config.uploads.folder + '/zips') || fs.mkdirSync('./' + config.uploads.folder + '/zips')
safe.use(helmet()); safe.use(helmet());
safe.set('trust proxy', 1); safe.set('trust proxy', 1);

View File

@ -23,6 +23,7 @@
"fluent-ffmpeg": "^2.1.0", "fluent-ffmpeg": "^2.1.0",
"gm": "^1.23.0", "gm": "^1.23.0",
"helmet": "^3.5.0", "helmet": "^3.5.0",
"jszip": "^3.1.4",
"knex": "^0.12.6", "knex": "^0.12.6",
"multer": "^1.2.1", "multer": "^1.2.1",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",

View File

@ -38,12 +38,18 @@ routes.get('/a/:identifier', async (req, res, next) => {
} }
} }
let enableDownload = false;
if (config.uploads.generateZips) enableDownload = true;
return res.render('album', { return res.render('album', {
layout: false, layout: false,
title: album.name, title: album.name,
count: files.length, count: files.length,
thumb, thumb,
files files,
identifier,
enableDownload
}); });
}); });

View File

@ -21,6 +21,7 @@ routes.post('/upload', (req, res, next) => uploadController.upload(req, res, nex
routes.post('/upload/delete', (req, res, next) => uploadController.delete(req, res, next)); routes.post('/upload/delete', (req, res, next) => uploadController.delete(req, res, next));
routes.post('/upload/:albumid', (req, res, next) => uploadController.upload(req, res, next)); routes.post('/upload/:albumid', (req, res, next) => uploadController.upload(req, res, next));
routes.get('/album/get/:identifier', (req, res, next) => albumsController.get(req, res, next)); routes.get('/album/get/:identifier', (req, res, next) => albumsController.get(req, res, next));
routes.get('/album/zip/:identifier', (req, res, next) => albumsController.generateZip(req, res, next));
routes.get('/album/:id', (req, res, next) => uploadController.list(req, res, next)); routes.get('/album/:id', (req, res, next) => uploadController.list(req, res, next));
routes.get('/album/:id/:page', (req, res, next) => uploadController.list(req, res, next)); routes.get('/album/:id/:page', (req, res, next) => uploadController.list(req, res, next));
routes.get('/albums', (req, res, next) => albumsController.list(req, res, next)); routes.get('/albums', (req, res, next) => albumsController.list(req, res, next));

View File

@ -43,8 +43,17 @@
<section class="hero is-fullheight"> <section class="hero is-fullheight">
<div class="hero-head"> <div class="hero-head">
<div class="container"> <div class="container">
<h1 class="title" id='title' style='margin-top: 1.5rem;'>{{ title }}</h1> <div class="columns">
<h1 class="subtitle" id='count'> {{ count }} files</h1> <div class="column is-9">
<h1 class="title" id='title' style='margin-top: 1.5rem;'>{{ title }}</h1>
<h1 class="subtitle" id='count'>{{ count }} files</h1>
</div>
<div class="column is-3" style="text-align: center; padding-top: 45px;">
{{#if enableDownload}}
<a class="button is-primary is-outlined" title="Download album" href="/api/album/zip/{{ identifier }}">Download Album</a>
{{/if}}
</div>
</div>
<hr> <hr>
</div> </div>
</div> </div>