diff --git a/docs/plugins.rst b/docs/plugins.rst index 4074e1c..4da49dc 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -2,6 +2,8 @@ Plugins ========= +.. contents:: + How to use plugins ------------------ @@ -128,3 +130,8 @@ Watermark plugin ================ .. automodule:: sigal.plugins.watermark + +ZIP Gallery plugin +================== + +.. automodule:: sigal.plugins.zip_gallery diff --git a/sigal/gallery.py b/sigal/gallery.py index 1695852..3943b14 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -31,7 +31,6 @@ import os import pickle import random import sys -import zipfile from click import progressbar, get_terminal_size from collections import defaultdict @@ -521,37 +520,10 @@ class Album: @cached_property def zip(self): - """Make a ZIP archive with all media files and return its path. - - If the ``zip_gallery`` setting is set,it contains the location of a zip - archive with all original images of the corresponding directory. - + """Placeholder ZIP method. + The ZIP logic is controlled by the zip_gallery plugin """ - zip_gallery = self.settings['zip_gallery'] - - if zip_gallery and len(self) > 0: - zip_gallery = zip_gallery.format(album=self) - archive_path = join(self.dst_path, zip_gallery) - if (self.settings.get('zip_skip_if_exists', False) and - isfile(archive_path)): - self.logger.debug("Archive %s already created, passing", - archive_path) - return zip_gallery - - archive = zipfile.ZipFile(archive_path, 'w', allowZip64=True) - attr = ('src_path' if self.settings['zip_media_format'] == 'orig' - else 'dst_path') - - for p in self: - path = getattr(p, attr) - try: - archive.write(path, os.path.split(path)[1]) - except OSError as e: - self.logger.warn('Failed to add %s to the ZIP: %s', p, e) - - archive.close() - self.logger.debug('Created ZIP archive %s', archive_path) - return zip_gallery + return None class Gallery(object): diff --git a/sigal/plugins/zip_gallery.py b/sigal/plugins/zip_gallery.py new file mode 100644 index 0000000..c5808cc --- /dev/null +++ b/sigal/plugins/zip_gallery.py @@ -0,0 +1,103 @@ +# Copyright 2019 - Remi Ferrand + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +""" This plugin controls the generation of a ZIP archive for a gallery + +If the ``zip_gallery`` setting is set, it contains the location of a zip +archive with all original images of the corresponding directory. + +To ignore a ZIP gallery generation for a particular album, put +a ``.nozip_gallery`` file next to it in its parent folder. Only the existence +of this ``.nozip_gallery`` file is tested. If no ``.nozip_gallery`` file is +present, then make a ZIP archive with all media files. +""" + +import logging +import os +from os.path import isfile, join, splitext +from sigal import signals +from sigal.gallery import Album +from sigal.utils import cached_property +import zipfile + +logger = logging.getLogger(__name__) + +def _should_generate_album_zip(album): + """Checks whether a `.nozip_gallery` file exists in the album folder""" + nozipgallerypath = os.path.join(album.src_path, ".nozip_gallery") + return not os.path.isfile(nozipgallerypath) + +def _generate_album_zip(album): + """Make a ZIP archive with all media files and return its path. + + If the ``zip_gallery`` setting is set,it contains the location of a zip + archive with all original images of the corresponding directory. + """ + + zip_gallery = album.settings['zip_gallery'] + + if zip_gallery and len(album) > 0: + zip_gallery = zip_gallery.format(album=album) + archive_path = join(album.dst_path, zip_gallery) + if (album.settings.get('zip_skip_if_exists', False) and + isfile(archive_path)): + logger.debug("Archive %s already created, passing", archive_path) + return zip_gallery + + archive = zipfile.ZipFile(archive_path, 'w', allowZip64=True) + attr = ('src_path' if album.settings['zip_media_format'] == 'orig' + else 'dst_path') + + for p in album: + path = getattr(p, attr) + try: + archive.write(path, os.path.split(path)[1]) + except OSError as e: + logger.warn('Failed to add %s to the ZIP: %s', p, e) + + archive.close() + logger.debug('Created ZIP archive %s', archive_path) + return zip_gallery + + return False + +def generate_album_zip(album): + """Checks for .nozip_gallery file in album folder. + If this file exists, no ZIP archive is generated. + If the file is absent, make a ZIP archive with all media files and return its path. + + If the ``zip_gallery`` setting is set,it contains the location of a zip + archive with all original images of the corresponding directory. + """ + + # check if ZIP file generation as been disabled by .nozip_gallery file + if not _should_generate_album_zip(album): + logger.info("Ignoring ZIP gallery generation for album '%s' because of present " + ".nozip_gallery file", album.name) + return False + + return _generate_album_zip(album) + +def nozip_gallery_file(album, settings=None): + """Filesystem based switch to disable ZIP generation for an Album""" + Album.zip = cached_property(generate_album_zip) + +def register(settings): + signals.album_initialized.connect(nozip_gallery_file) diff --git a/tests/sample/pictures/dir2/.nozip_gallery b/tests/sample/pictures/dir2/.nozip_gallery new file mode 100644 index 0000000..e69de29 diff --git a/tests/sample/sigal.conf.py b/tests/sample/sigal.conf.py index cbf4ac1..34e5d24 100644 --- a/tests/sample/sigal.conf.py +++ b/tests/sample/sigal.conf.py @@ -9,9 +9,15 @@ links = [('Example link', 'http://example.org'), files_to_copy = (('../watermark.png', 'watermark.png'),) -plugins = ['sigal.plugins.adjust', 'sigal.plugins.copyright', - 'sigal.plugins.watermark', 'sigal.plugins.feeds', - 'sigal.plugins.nomedia', 'sigal.plugins.extended_caching'] +plugins = [ + 'sigal.plugins.adjust', + 'sigal.plugins.copyright', + 'sigal.plugins.extended_caching' + 'sigal.plugins.feeds', + 'sigal.plugins.nomedia', + 'sigal.plugins.watermark', + 'sigal.plugins.zip_gallery', +] copyright = '© An example copyright message' adjust_options = {'color': 0.9, 'brightness': 1.0, 'contrast': 1.0, 'sharpness': 0.0} diff --git a/tests/test_zip.py b/tests/test_zip.py index 0065133..57e6522 100644 --- a/tests/test_zip.py +++ b/tests/test_zip.py @@ -2,33 +2,33 @@ import os import glob import zipfile +from sigal import init_plugins from sigal.gallery import Gallery from sigal.settings import read_settings CURRENT_DIR = os.path.dirname(__file__) SAMPLE_DIR = os.path.join(CURRENT_DIR, 'sample') -SAMPLE_SOURCE = os.path.join(SAMPLE_DIR, 'pictures', 'dir1') +SAMPLE_SOURCE = os.path.join(SAMPLE_DIR, 'pictures') -def make_gallery(**kwargs): +def make_gallery(source_dir='dir1', **kwargs): default_conf = os.path.join(SAMPLE_DIR, 'sigal.conf.py') settings = read_settings(default_conf) - settings['source'] = SAMPLE_SOURCE + settings['source'] = os.path.join(SAMPLE_SOURCE, source_dir) settings.update(kwargs) + init_plugins(settings) return Gallery(settings, ncpu=1) def test_zipped_correctly(tmpdir): outpath = str(tmpdir) - gallery = make_gallery(destination=outpath, - zip_gallery='archive.zip') + gallery = make_gallery(destination=outpath, zip_gallery='archive.zip') gallery.build() - zipped1 = glob.glob(os.path.join(outpath, 'test1', '*.zip')) - assert len(zipped1) == 1 - assert os.path.basename(zipped1[0]) == 'archive.zip' + zipf = os.path.join(outpath, 'test1', 'archive.zip') + assert os.path.isfile(zipf) - zip_file = zipfile.ZipFile(zipped1[0], 'r') + zip_file = zipfile.ZipFile(zipf, 'r') expected = ('11.jpg', 'CMB_Timeline300_no_WMAP.jpg', 'flickr_jerquiaga_2394751088_cc-by-nc.jpg', 'example.gif') @@ -38,16 +38,23 @@ def test_zipped_correctly(tmpdir): zip_file.close() - zipped2 = glob.glob(os.path.join(outpath, 'test2', '*.zip')) - assert len(zipped2) == 1 - assert os.path.basename(zipped2[0]) == 'archive.zip' + assert os.path.isfile(os.path.join(outpath, 'test2', 'archive.zip')) + + +def test_not_zipped(tmpdir): + # test that the zip file is not created when the .nozip_gallery file + # is present + outpath = str(tmpdir) + gallery = make_gallery(destination=outpath, zip_gallery='archive.zip', + source_dir='dir2') + gallery.build() + assert not os.path.isfile(os.path.join(outpath, 'archive.zip')) def test_no_archive(tmpdir): outpath = str(tmpdir) - gallery = make_gallery(destination=outpath, - zip_gallery=False) + gallery = make_gallery(destination=outpath, zip_gallery=False) gallery.build() - assert not glob.glob(os.path.join(outpath, 'test1', '*.zip')) - assert not glob.glob(os.path.join(outpath, 'test2', '*.zip')) + assert not os.path.isfile(os.path.join(outpath, 'test1', 'archive.zip')) + assert not os.path.isfile(os.path.join(outpath, 'test2', 'archive.zip'))