From 34bb74a3303e1f484fe3537ed97934a905416cae Mon Sep 17 00:00:00 2001 From: David Schultz Date: Mon, 19 Feb 2024 15:29:36 -0600 Subject: [PATCH] update only a single album --- docs/getting_started.rst | 11 ++++++ src/sigal/__main__.py | 12 +++++- src/sigal/gallery.py | 74 ++++++++++++++++++++++++------------ src/sigal/plugins/nomedia.py | 60 ++++------------------------- tests/test_cli.py | 49 ++++++++++++++++++++++++ tests/test_gallery.py | 15 ++++++++ 6 files changed, 144 insertions(+), 77 deletions(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 35063e0b..565faad3 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -72,6 +72,17 @@ Optional arguments: My Pictures/Landscapes => Force My Other Pictures/Landscapes => Force +``--only-album`` + Only write a specific album path. Other albums will be ignored as if you + had set the ``ignore_directories`` setting to all of their names. + +:: + + --only-album 'My Pictures/Pics' + My Pictures/Pics => Write + My Pictures/Pics/* => Ignore + * => Ignore + ``-v, --verbose`` Show all messages diff --git a/src/sigal/__main__.py b/src/sigal/__main__.py index 30534514..7d1456ae 100644 --- a/src/sigal/__main__.py +++ b/src/sigal/__main__.py @@ -85,6 +85,15 @@ def init(path): "the album name. (-a 'My Pictures/* Pics' -a 'Festival')" ), ) +@option( + "--only-album", + default=None, + help=( + "Only write a specific album path. Other albums will be ignored " + "as if you had set the `ignore_directories` setting to all of " + "their names. (--only-album 'My Pictures/Pics')" + ), +) @option("-v", "--verbose", is_flag=True, help="Show all messages") @option( "-d", @@ -120,6 +129,7 @@ def build( quiet, force, force_album, + only_album, config, theme, title, @@ -186,7 +196,7 @@ def build( locale.setlocale(locale.LC_ALL, settings["locale"]) init_plugins(settings) - gal = Gallery(settings, ncpu=ncpu, show_progress=show_progress) + gal = Gallery(settings, ncpu=ncpu, show_progress=show_progress, only_album=only_album) gal.build(force=force_album if len(force_album) else force) # copy extra files diff --git a/src/sigal/gallery.py b/src/sigal/gallery.py index f9eefa06..ca9271f4 100644 --- a/src/sigal/gallery.py +++ b/src/sigal/gallery.py @@ -699,7 +699,7 @@ def zip(self): class Gallery: - def __init__(self, settings, ncpu=None, show_progress=False): + def __init__(self, settings, ncpu=None, show_progress=False, only_album=None): self.settings = settings self.logger = logging.getLogger(__name__) self.stats = defaultdict(int) @@ -711,6 +711,7 @@ def __init__(self, settings, ncpu=None, show_progress=False): # Build the list of directories with images albums = self.albums = {} + self.only_album = only_album src_path = self.settings["source"] ignore_dirs = settings["ignore_directories"] @@ -724,17 +725,26 @@ def __init__(self, settings, ncpu=None, show_progress=False): self.progressbar_target = None if show_progress and isatty else Devnull() - for path, dirs, files in os.walk(src_path, followlinks=True, topdown=False): + for path, dirs, files in os.walk(src_path, followlinks=True, topdown=True): if show_progress: print("\rCollecting albums " + next(progressChars), end="") relpath = os.path.relpath(path, src_path) - # Test if the directory match the ignore_dirs settings - if ignore_dirs and any( - fnmatch.fnmatch(relpath, ignore) for ignore in ignore_dirs - ): - self.logger.info("Ignoring %s", relpath) - continue + for d in dirs[:]: + dir_relpath = join(relpath, d) if relpath != "." else d + + # Test if the directory matches the only_album settings + if only_album and dir_relpath not in only_album and relpath != only_album: + self.logger.info("Skipping %s", dir_relpath) + dirs.remove(d) + continue + + # Test if the directory matches the ignore_dirs settings + if ignore_dirs and any( + fnmatch.fnmatch(dir_relpath, ignore) for ignore in ignore_dirs + ): + self.logger.info("Ignoring %s", dir_relpath) + dirs.remove(d) # Remove files that match the ignore_files settings if ignore_files: @@ -746,21 +756,29 @@ def __init__(self, settings, ncpu=None, show_progress=False): files = [os.path.split(f)[1] for f in files_path] self.logger.debug("Files after filtering: %r", files) - # Remove sub-directories that have been ignored in a previous - # iteration (as topdown=False, sub-directories are processed before - # their parent - for d in dirs[:]: - path = join(relpath, d) if relpath != "." else d - if path not in albums.keys(): - dirs.remove(d) - album = Album(relpath, settings, dirs, files, self) - if not album.medias and not album.albums: - self.logger.info("Skip empty album: %r", album) - else: - album.create_output_directories() - albums[relpath] = album + self.logger.debug("Processing subdirs: %r", dirs) + + self.stats["album"] += 1 + albums[relpath] = album + + # Remove empty albums + for relpath in sorted(albums.keys(), key=lambda x: len(x), reverse=True): + album = albums[relpath] + keep_only_album_children = only_album and os.path.relpath(os.path.dirname(album.src_path), src_path) == only_album + if not album.medias and not album.subdirs and not keep_only_album_children: + self.logger.info("Skip empty album: %r", album.path) + del albums[relpath] + if relpath != '.': + super_album = os.path.relpath(os.path.dirname(album.src_path), src_path) + self.logger.debug("Deleting album from super album %s", super_album) + albums[super_album].subdirs.remove(os.path.basename(album.path)) + self.stats["album_skipped"] += 1 + self.stats["album"] -= self.stats["album_skipped"] + + for album in albums.values(): + album.create_output_directories() if show_progress: print("\rCollecting albums, done.") @@ -771,6 +789,8 @@ def __init__(self, settings, ncpu=None, show_progress=False): file=self.progressbar_target, ) as progress_albums: for album in progress_albums: + if only_album and album.path != only_album: + continue album.sort_subdirs(settings["albums_sort_attr"]) with progressbar( @@ -779,6 +799,8 @@ def __init__(self, settings, ncpu=None, show_progress=False): file=self.progressbar_target, ) as progress_albums: for album in progress_albums: + if only_album and album.path != only_album: + continue album.sort_medias(settings["medias_sort_attr"]) self.logger.debug("Albums:\n%r", albums.values()) @@ -846,9 +868,11 @@ def log_func(x): show_eta=False, file=self.progressbar_target, ) as albums: - media_list = [ - f for album in albums for f in self.process_dir(album, force=force) - ] + media_list = [] + for album in albums: + if self.only_album and album.path != self.only_album: + continue + media_list.extend(self.process_dir(album, force=force)) except KeyboardInterrupt: sys.exit("Interrupted") @@ -902,6 +926,8 @@ def log_func(x): file=self.progressbar_target, ) as albums: for album in albums: + if self.only_album and album.path != self.only_album: + continue if album.albums: if album.medias: self.logger.warning( diff --git a/src/sigal/plugins/nomedia.py b/src/sigal/plugins/nomedia.py index 93fb4039..844bb79b 100644 --- a/src/sigal/plugins/nomedia.py +++ b/src/sigal/plugins/nomedia.py @@ -54,31 +54,6 @@ logger = logging.getLogger(__name__) -def _remove_albums_with_subdirs(albums, keystoremove, prefix=""): - for keytoremove in keystoremove: - for key in list(albums.keys()): - if key.startswith(prefix + keytoremove): - # subdirs' target directories have already been created, - # remove them first - try: - album = albums[key] - settings = album.settings - if album.medias: - os.rmdir(os.path.join(album.dst_path, settings["thumb_dir"])) - - if album.medias and settings["keep_orig"]: - os.rmdir(os.path.join(album.dst_path, settings["orig_dir"])) - - os.rmdir(album.dst_path) - except OSError: - # directory was created and populated with images in a - # previous run => keep it - pass - - # now remove the album from the surrounding album/gallery - del albums[key] - - def filter_nomedia(album, settings=None): """Removes all filtered Media and subdirs from an Album""" nomediapath = os.path.join(album.src_path, ".nomedia") @@ -90,39 +65,20 @@ def filter_nomedia(album, settings=None): logger.info( "Ignoring album '%s' because of present 0-byte .nomedia file", album.name ) - - # subdirs have been added to the gallery already, remove them - # there, too - _remove_albums_with_subdirs(album.gallery.albums, [album.path]) - try: - os.rmdir(album.dst_path) - except OSError: - # directory was created and populated with images in a - # previous run => keep it - pass - - # cannot set albums => empty subdirs so that no albums are - # generated - album.subdirs = [] - album.medias = [] + album.subdirs.clear() + album.medias.clear() else: with open(nomediapath) as nomediaFile: logger.info("Found a .nomedia file in %s, ignoring its entries", album.name) ignored = nomediaFile.read().split("\n") - album.medias = [ - media for media in album.medias if media.src_filename not in ignored - ] - album.subdirs = [ - dirname for dirname in album.subdirs if dirname not in ignored - ] - - # subdirs have been added to the gallery already, remove - # them there, too - _remove_albums_with_subdirs( - album.gallery.albums, ignored, album.path + os.path.sep - ) + for media in album.medias[:]: + if media.src_filename in ignored: + album.medias.remove(media) + for dirname in album.subdirs[:]: + if dirname in ignored: + album.subdirs.remove(dirname) def register(settings): diff --git a/tests/test_cli.py b/tests/test_cli.py index c6f13f37..5c497d32 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -105,6 +105,55 @@ def test_build(tmpdir, disconnect_signals): logger.setLevel(logging.INFO) +def test_build_only_album(tmpdir, disconnect_signals): + runner = CliRunner() + config_file = str(tmpdir.join("sigal.conf.py")) + tmpdir.mkdir("pictures") + tmpdir = str(tmpdir) + cwd = os.getcwd() + + try: + result = runner.invoke(init, [config_file]) + assert result.exit_code == 0 + os.symlink( + join(TESTGAL, "pictures", "dir2"), + join(tmpdir, "pictures", "dir1"), + ) + + os.chdir(tmpdir) + + with open(config_file) as f: + text = f.read() + + text += """ +theme = 'colorbox' +plugins = ['sigal.plugins.media_page', 'sigal.plugins.nomedia', + 'sigal.plugins.extended_caching'] +""" + + with open(config_file, "w") as f: + f.write(text) + + result = runner.invoke( + build, + ["pictures", "build", "--title", "Testing build", "-n", 1, "--debug", "--only-album", "dir1"], + catch_exceptions=False, + ) + assert result.exit_code == 0 + assert os.path.isfile( + join(tmpdir, "build", "dir1", "index.html") + ) + assert not os.path.isfile( + join(tmpdir, "build", "dir2", "index.html") + ) + finally: + os.chdir(cwd) + # Reset logger + logger = logging.getLogger("sigal") + logger.handlers[:] = [] + logger.setLevel(logging.INFO) + + def test_serve(tmpdir): config_file = str(tmpdir.join("sigal.conf.py")) runner = CliRunner() diff --git a/tests/test_gallery.py b/tests/test_gallery.py index e33e0177..8edfbc0e 100644 --- a/tests/test_gallery.py +++ b/tests/test_gallery.py @@ -326,6 +326,21 @@ def test_gallery(settings, tmp_path, caplog): logger.setLevel(logging.INFO) +def test_gallery_only_album(settings, tmp_path, caplog): + "Test the Gallery class updating a single album." + + caplog.set_level("ERROR") + settings["destination"] = str(tmp_path) + gal = Gallery(settings, ncpu=1, only_album="dir1") + gal.build() + + out_html = os.path.join(settings["destination"], "dir1", "index.html") + assert os.path.isfile(out_html) + + out_html2 = os.path.join(settings["destination"], "dir2", "index.html") + assert not os.path.isfile(out_html2) + + def test_custom_theme(settings, tmp_path, caplog): theme_path = tmp_path / "mytheme" tpl_path = theme_path / "templates"