Files
NibasaViewer/viewer/directory.py

244 lines
8.2 KiB
Python

"""
Directory-related rendering helpers for the gallery view.
"""
# Standard library imports.
from pathlib import Path
# Django imports.
from django.http import HttpResponseNotFound
from django.shortcuts import render
from django.conf import settings
# Project imports.
from .common import (
normalize_sort,
normalize_theme,
build_query,
gallery_url,
append_query,
sort_images,
build_sort_options,
build_breadcrumbs,
get_first_image_thumbnail_url,
is_image_file,
make_thumbnail,
)
from .specials import get_special_paths, special_name
from .models import Image as ImModel
def do_recursive_search(start_path, query):
"""
Gets all images and sub-directories inside the start_path whose name matches the given query,
and then joins the results recursively by iterating in each sub-directory.
"""
try:
entries = [entry for entry in start_path.iterdir() if not entry.is_symlink()]
except OSError:
return [], []
all_subdirs = sorted(
[entry for entry in entries if entry.is_dir()],
key=lambda item: (item.name.lower(), str(item).lower()),
)
subdirs = [entry for entry in all_subdirs if query.lower() in entry.name.lower()]
images = sorted(
[
entry
for entry in entries
if entry.is_file()
and is_image_file(entry)
and query.lower() in entry.name.lower()
],
key=lambda item: (item.name.lower(), str(item).lower()),
)
for subdir in all_subdirs:
rec_subdirs, rec_images = do_recursive_search(subdir, query.lower())
subdirs.extend(rec_subdirs)
images.extend(rec_images)
return subdirs, images
def render_directory(request, path_text, full_path, special=None):
"""
Renders the gallery view related to directories, be it the contents of an actual directory
in the file system, or logical gallery directories like search result pages.
"""
# Search remains a GET parameter. For sort and theme prefer explicit
# GET parameters when present (so query-preserving links behave as
# callers expect), otherwise fall back to middleware-provided settings.
search_text = request.GET.get("search", "").strip()
sort_key = normalize_sort(
request.GET.get("sort") or getattr(request, "sort", None) or "abc"
)
theme = normalize_theme(
request.GET.get("theme") or getattr(request, "theme", None) or "dark"
)
query_state = build_query(search_text)
# If rendering a logical special gallery, obtain the image list from the
# Image model and skip filesystem subdir enumeration.
if special is not None:
# build images using the helper that returns Path objects
images = get_special_paths(request.user, special)
# Respect the client's sort_key only if the special is favorites
# otherwise the ordering comes from the DB query (most-visited/recent).
if special == "favorites":
images = sort_images(images, sort_key)
current_subdirs = []
else:
try:
current_entries = [
entry for entry in full_path.iterdir() if not entry.is_symlink()
]
except OSError:
return HttpResponseNotFound("Not found")
current_subdirs = sorted(
[entry for entry in current_entries if entry.is_dir()],
key=lambda item: (
item.name.lower(),
str(item.relative_to(settings.GALLERY_ROOT)).lower(),
),
)
if search_text == "":
images = [
entry
for entry in current_entries
if entry.is_file() and is_image_file(entry)
]
else:
_, images = do_recursive_search(full_path, search_text)
images = sort_images(images, sort_key)
image_data = []
for image in images:
rel_path = image.relative_to(settings.GALLERY_ROOT)
image_url = gallery_url(rel_path, False, query_state)
thumbnail = None
try:
# use shared make_thumbnail so tests can patch viewer.common.make_thumbnail
make_thumbnail(image)
thumb_path = settings.THUMBNAILS_ROOT.joinpath(rel_path)
if thumb_path.exists():
thumbnail = "/thumbs/" + str(rel_path).replace("\\", "/")
except Exception:
pass
image_data.append(
{"path": image_url, "name": image.name, "thumbnail": thumbnail}
)
subdir_data = []
for subdir in current_subdirs:
rel_path = subdir.relative_to(settings.GALLERY_ROOT)
subdir_data.append(
{
"path": gallery_url(rel_path, True, query_state),
"name": subdir.name,
"thumbnail": get_first_image_thumbnail_url(subdir),
}
)
next_theme = "light" if theme == "dark" else "dark"
# The theme toggle button now calls the persistent settings endpoint.
# Include `next` (for redirect back) and also surface `sort` and `search`
# as top-level query parameters so templates/tests can inspect them easily.
theme_toggle_query = {
"next": request.get_full_path(),
"theme": next_theme,
"sort": sort_key,
}
if search_text != "":
theme_toggle_query["search"] = search_text
search_action_query = {"sort": sort_key, "theme": theme}
# Back (directory) and Home (root) links and thumbnails
dir_rel = None
try:
# derive directory path text relative to gallery root
dir_rel = full_path.parent.relative_to(settings.GALLERY_ROOT)
dir_text = str(dir_rel).replace("\\", "/")
except Exception:
dir_text = ""
# For special galleries hide the Back control but still present Home
if special is not None:
back_url = None
back_thumb = None
else:
back_url = gallery_url(
Path(dir_text) if dir_text != "" else None, True, query_state
)
back_thumb = get_first_image_thumbnail_url(full_path.parent)
home_url = gallery_url(None, True, query_state)
home_thumb = get_first_image_thumbnail_url(settings.GALLERY_ROOT)
context = {
"path": path_text,
"search_text": search_text,
"theme": theme,
"sort_key": sort_key,
"sort_label": "",
"sort_options": build_sort_options(request, search_text, sort_key, theme),
"breadcrumbs": build_breadcrumbs(path_text, query_state),
"images": image_data,
"subdirs": subdir_data,
"back_url": back_url,
"back_thumb": back_thumb,
"home_url": home_url,
"home_thumb": home_thumb,
"theme_toggle_url": append_query(
"/gallery/toggle-settings/", theme_toggle_query
),
"search_action_url": gallery_url(
Path(path_text) if path_text != "" else None, True, search_action_query
),
"clear_search_url": gallery_url(
Path(path_text) if path_text != "" else None, True, None
),
}
# When rendering a special gallery adjust breadcrumbs and hide search
# and back controls. Use `is_special` and `special_name` in the template.
if special is not None:
context["is_special"] = True
context["special_name"] = special_name(special)
# expose which special is active so templates can highlight it
context["active_special"] = special
# Override breadcrumbs for special galleries to be a single label
# and make it link to the special gallery root so templates can show
# it as an active, clickable breadcrumb.
from .specials import special_root_url
context["breadcrumbs"] = [
{
"label": context["special_name"],
"path": special_root_url(special, query_state),
}
]
# Hide the search box (templates use `is_special` to decide)
context["search_text"] = ""
context["search_action_url"] = ""
context["clear_search_url"] = ""
# sort_label depends on SORT_LABELS in common; import lazily to avoid circulars
from .common import SORT_LABELS
context["sort_label"] = SORT_LABELS.get(sort_key, "")
return render(request, "gallery_view.html", context)