Browse Source

Added album downloading through front-end

Pitu 6 years ago
parent
commit
992b632d1a

+ 8 - 1
config.sample.js

@@ -62,7 +62,14 @@ module.exports = {
 			to install a separate binary called graphicsmagick (http://www.graphicsmagick.org)
 			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

+ 42 - 1
controllers/albumsController.js

@@ -3,7 +3,8 @@ const db = require('knex')(config.database);
 const randomstring = require('randomstring');
 const utils = require('./utilsController.js');
 const path = require('path');
-
+const fs = require('fs');
+const Zip = require('jszip');
 const albumsController = {};
 
 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;

+ 9 - 1
controllers/uploadController.js

@@ -42,7 +42,7 @@ uploadsController.upload = async (req, res, next) => {
 	const albumid = req.headers.albumid || req.params.albumid;
 
 	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) {
 			return res.json({
 				success: false,
@@ -150,6 +150,11 @@ uploadsController.processFilesForDisplay = async (req, res, files, existingFiles
 			file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`;
 			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 {
 		await uploadsController.deleteFile(file.name);
 		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) {
 		console.log(err);
 	}

+ 13 - 0
database/migration.js

@@ -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();

+ 1 - 0
lolisafe.js

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

+ 1 - 0
package.json

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

+ 7 - 1
routes/album.js

@@ -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', {
 		layout: false,
 		title: album.name,
 		count: files.length,
 		thumb,
-		files
+		files,
+		identifier,
+		enableDownload
 	});
 });
 

+ 1 - 0
routes/api.js

@@ -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/: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/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/:page', (req, res, next) => uploadController.list(req, res, next));
 routes.get('/albums', (req, res, next) => albumsController.list(req, res, next));

+ 11 - 2
views/album.handlebars

@@ -43,8 +43,17 @@
 		<section class="hero is-fullheight">
 			<div class="hero-head">
 				<div class="container">
-					<h1 class="title" id='title' style='margin-top: 1.5rem;'>{{ title }}</h1>
-					<h1 class="subtitle" id='count'> {{ count }} files</h1>
+					<div class="columns">
+						<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>
 				</div>
 			</div>