feat(albums): optionally generate a ZIP file for each album

Fixes #125
pull/163/head
Romain 5 years ago
parent f10544a6a9
commit 9c581bbd0b

@ -194,6 +194,12 @@ const OPTIONS = {
choices: ['asc', 'desc'],
'default': 'asc'
},
'album-zip-files': {
group: 'Album options:',
description: 'Create a ZIP file per album',
type: 'boolean',
'default': false
},
// ------------------------------------
// Website options
@ -398,6 +404,7 @@ exports.get = (args) => {
sortAlbumsDirection: opts['sort-albums-direction'],
sortMediaBy: opts['sort-media-by'],
sortMediaDirection: opts['sort-media-direction'],
albumZipFiles: opts['album-zip-files'],
theme: opts['theme'],
themePath: opts['theme-path'],
themeStyle: opts['theme-style'],

@ -32,6 +32,14 @@ exports.build = function (opts, done) {
}
}
},
{
title: 'Updating ZIP files',
enabled: (ctx) => opts.albumZipFiles,
skip: () => opts.dryRun,
task: (ctx) => {
return steps.zipAlbums(ctx.album, opts.output)
}
},
{
title: 'Cleaning up',
enabled: (ctx) => opts.cleanup,

@ -61,6 +61,10 @@ Album.prototype.finalize = function (options, parent) {
this.url = url.resolve(albumsOutputFolder + '/', this.basename + '.html')
this.depth = parent.depth + 1
}
// path to the optional ZIP file
if (options.albumZipFiles && this.files.length > 0) {
this.zip = this.path.replace(/\.[^\\/.]+$/, '.zip')
}
// then finalize all nested albums (which uses the parent basename)
for (var i = 0; i < this.albums.length; ++i) {
this.albums[i].finalize(options, this)

@ -1,3 +1,4 @@
exports.index = require('./step-index').run
exports.process = require('./step-process').run
exports.cleanup = require('./step-cleanup').run
exports.zipAlbums = require('./step-album-zip').run

@ -0,0 +1,56 @@
const async = require('async')
const childProcess = require('child_process')
const Observable = require('zen-observable')
const path = require('path')
const trace = require('debug')('thumbsup:trace')
const debug = require('debug')('thumbsup:debug')
const error = require('debug')('thumbsup:error')
exports.run = function (rootAlbum, outputFolder) {
return new Observable(observer => {
const albums = flattenAlbums([rootAlbum])
const zippers = albums.filter(a => a.zip).map(album => {
return (done) => {
debug(`Zipping album ${album.zip} (${album.files.length} files)`)
const zipPath = path.join(outputFolder, album.zip)
const filenames = album.files.map(f => f.output.download.path)
createZip(zipPath, outputFolder, filenames, done)
}
})
async.series(zippers, err => {
if (err) {
observer.error(err)
} else {
observer.complete(err)
}
})
})
}
function flattenAlbums (albums, result) {
return albums.reduce((acc, album) => {
acc.push(album)
return flattenAlbums(album.albums, acc)
}, result || [])
}
// This function uses the Unix ZIP command, which supports "updating" a ZIP file
// In the future it could also delegate to 7zip on Windows
function createZip (targetZipPath, currentFolder, filesToInclude, done) {
const args = ['-FS', targetZipPath].concat(filesToInclude)
const startTime = Date.now()
trace(`Calling: zip ${args.join(' ')}`)
const child = childProcess.spawn('zip', args, {
cwd: currentFolder,
stdio: [ 'ignore', 'ignore', 'ignore' ]
})
child.on('error', (err) => {
error(`Error: please verify that <zip> is installed on your system`)
error(err.toString())
})
child.on('close', (code, signal) => {
const elapsed = Math.floor(Date.now() - startTime)
debug(`Zip exited with code ${code} in ${elapsed}ms`)
done(code === 0 ? null : new Error(`Error creating ZIP file ${targetZipPath}`))
})
}

@ -244,6 +244,47 @@ describe('Album', function () {
should(nested.files[1].path).eql('b')
})
})
describe('zip', function () {
it('is undefined if the option is not set', function () {
const a = new Album('Holidays')
should(a.zip).eql(undefined)
})
it('is undefined if the album has no direct files', function () {
const a = new Album('Holidays')
a.finalize({ albumZipFiles: true })
should(a.zip).eql(undefined)
})
it('points to a valid path if the album has direct files', function () {
const a = new Album({
title: 'Holidays',
files: [
fixtures.photo({ path: 'a' }),
fixtures.photo({ path: 'b' })
]
})
a.finalize({ albumZipFiles: true })
should(a.zip).eql('index.zip')
})
it('is set for sub-albums as well', function () {
const london = new Album({
title: 'London',
files: [
fixtures.photo({ path: 'a' }),
fixtures.photo({ path: 'b' })
]
})
const root = new Album({
title: 'Holidays',
albums: [london]
})
root.finalize({ albumZipFiles: true })
should(london.zip).eql('London.zip')
})
})
})
function albumWithFileDates (dates) {

Loading…
Cancel
Save