Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update only a single album #511

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 11 additions & 1 deletion src/sigal/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -120,6 +129,7 @@ def build(
quiet,
force,
force_album,
only_album,
config,
theme,
title,
Expand Down Expand Up @@ -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
Expand Down
74 changes: 50 additions & 24 deletions src/sigal/gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"]
Expand All @@ -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:
Expand All @@ -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.")
Expand All @@ -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(
Expand All @@ -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())
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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(
Expand Down
60 changes: 8 additions & 52 deletions src/sigal/plugins/nomedia.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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):
Expand Down
49 changes: 49 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
15 changes: 15 additions & 0 deletions tests/test_gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading