Merge pull request #21 from Terranom674/fix/09629-stability-rollback

0.9.6.29: Shadowtree stabilisieren
This commit is contained in:
Terranom674
2026-08-19 19:59:15 +02:00
committed by GitHub
5 changed files with 26 additions and 48 deletions

View File

@@ -62,18 +62,12 @@ SELECT id, dir, name
OR LOWER(name) = LOWER(\''.$name_sql.'\') OR LOWER(name) = LOWER(\''.$name_sql.'\')
) )
ORDER BY CASE WHEN dir = \''.$dir_sql.'\' THEN 0 ELSE 1 END, id ORDER BY CASE WHEN dir = \''.$dir_sql.'\' THEN 0 ELSE 1 END, id
LIMIT 1
;'; ;';
$result = pwg_query($query); $result = pwg_query($query);
$ids = array(); if (!pwg_db_num_rows($result)) return null;
while ($row = pwg_db_fetch_assoc($result)) $row = pwg_db_fetch_assoc($result);
{ return (int)$row['id'];
$ids[] = (int)$row['id'];
}
if (count($ids) > 1)
{
throw new RuntimeException('Albumzuordnung ist mehrdeutig fuer "'.$name.'".');
}
return count($ids) === 1 ? $ids[0] : null;
} }
function bratonien_tools_nc_ensure_album_path($relative_dir, $excluded_site_id, array &$cache, array &$created_ids) function bratonien_tools_nc_ensure_album_path($relative_dir, $excluded_site_id, array &$cache, array &$created_ids)
@@ -175,10 +169,6 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
try try
{ {
// Older WebDAV builds created a complete physical album tree for their own
// Piwigo site. Remove that technical duplicate first. The WebDAV files are
// placeholders and are rebuilt independently, therefore database deletion
// must not remove the actual Nextcloud originals.
$counts['removed_duplicate_categories'] = bratonien_tools_nc_remove_storage_categories($site_id); $counts['removed_duplicate_categories'] = bratonien_tools_nc_remove_storage_categories($site_id);
list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW()')); list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW()'));
@@ -286,7 +276,6 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
$counts['new_elements'] = count($new_ids); $counts['new_elements'] = count($new_ids);
} }
// Refresh the normal file attributes for connector-managed images only.
$updates = array(); $updates = array();
foreach ($all_ids as $id) foreach ($all_ids as $id)
{ {

View File

@@ -1,7 +1,7 @@
<?php <?php
/* /*
Plugin Name: Bratonien Tools Plugin Name: Bratonien Tools
Version: 0.9.6.28 Version: 0.9.6.29
Description: Erweiterbare Administrationswerkzeuge fuer die Bratonien-Piwigo-Installation. Description: Erweiterbare Administrationswerkzeuge fuer die Bratonien-Piwigo-Installation.
Plugin URI: https://github.com/Terranom674/Piwigo_Bratonien_Tools Plugin URI: https://github.com/Terranom674/Piwigo_Bratonien_Tools
Author: Bratonien Author: Bratonien

22
runtime/lib/build_webdav_placeholder_source.py Normal file → Executable file
View File

@@ -3,7 +3,7 @@
This creates only tiny placeholder files plus a metadata mapping; no Nextcloud This creates only tiny placeholder files plus a metadata mapping; no Nextcloud
original media is downloaded. The authenticated Nextcloud user is never used as original media is downloaded. The authenticated Nextcloud user is never used as
an album name. Selecting the user's WebDAV root mirrors its children directly. an album name.
""" """
from __future__ import annotations from __future__ import annotations
@@ -249,20 +249,32 @@ def main() -> int:
for remote_root_raw in args.root: for remote_root_raw in args.root:
remote_root = validate_relative(remote_root_raw) remote_root = validate_relative(remote_root_raw)
current, _ = client.list_collection(remote_root) current, root_children = client.list_collection(remote_root)
fileid = int(current["fileid"]) fileid = int(current["fileid"])
if fileid in used_fileids: if fileid in used_fileids:
fail(f"duplicate selected Nextcloud root fileid: {fileid}") fail(f"duplicate selected Nextcloud root fileid: {fileid}")
used_fileids.add(fileid) used_fileids.add(fileid)
# An explicitly selected folder keeps its own name. The authenticated
# user's WebDAV root is transparent and must never become an album.
display = "" if remote_root == "" else (str(current.get("display_name", "")).strip() or PurePosixPath(remote_root).name)
local_name = f"root-{fileid}" local_name = f"root-{fileid}"
local_root = staging / local_name local_root = staging / local_name
files, folders, skipped = build_root(client, remote_root, local_root, seed, mapping) files, folders, skipped = build_root(client, remote_root, local_root, seed, mapping)
total_files += files total_files += files
total_folders += folders total_folders += folders
total_skipped += skipped total_skipped += skipped
if remote_root == "":
for child in sorted(root_children, key=lambda item: str(item.get("display_name", "")).casefold()):
name = safe_local_name(str(child.get("display_name", "")))
child_fileid = int(child.get("fileid", 0))
if child_fileid < 1:
fail(f"Nextcloud returned no stable fileid for root child {name!r}")
child_path = source_dir / local_name / name
if bool(child.get("is_dir")):
manifest.append(f"webdav:{child_fileid}\tfolder\t{name}\t{child_path}")
elif Path(name).suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS:
manifest.append(f"webdav:{child_fileid}\tfile\t{name}\t{child_path}")
continue
display = str(current.get("display_name", "")).strip() or PurePosixPath(remote_root).name
manifest.append(f"webdav:{fileid}\tfolder\t{display}\t{source_dir / local_name}") manifest.append(f"webdav:{fileid}\tfolder\t{display}\t{source_dir / local_name}")
if previous.exists(): if previous.exists():

0
runtime/lib/piwigo-sync.php Normal file → Executable file
View File

31
runtime/lib/shadow_tree.py Normal file → Executable file
View File

@@ -79,23 +79,15 @@ def preferred_target(source_key: str, raw_name: str, parent_target: Path, old_ma
return safe_name(raw_name) return safe_name(raw_name)
def mirror_directory( def mirror_directory(source: Path, target: Path, source_key: str, target_key: Path, old_map: dict[str, str], new_map: dict[str, str]) -> None:
source: Path,
target: Path,
source_key: str,
target_key: Path,
old_map: dict[str, str],
new_map: dict[str, str],
used: set[str] | None = None,
) -> None:
target.mkdir(parents=True, exist_ok=True) target.mkdir(parents=True, exist_ok=True)
used_names = used if used is not None else set() used: set[str] = set()
for child in sorted(source.iterdir(), key=lambda item: (item.name.casefold(), item.name)): for child in sorted(source.iterdir(), key=lambda item: (item.name.casefold(), item.name)):
if child.is_symlink(): if child.is_symlink():
continue continue
child_source_key = f"{source_key}/{child.name}" child_source_key = f"{source_key}/{child.name}"
preferred = preferred_target(child_source_key, child.name, target_key, old_map) preferred = preferred_target(child_source_key, child.name, target_key, old_map)
child_name = unique_name(preferred, used_names, child.is_file()) child_name = unique_name(preferred, used, child.is_file())
child_target = target / child_name child_target = target / child_name
child_target_key = target_key / child_name child_target_key = target_key / child_name
new_map[child_source_key] = child_target_key.as_posix() new_map[child_source_key] = child_target_key.as_posix()
@@ -128,26 +120,11 @@ def build(manifest: Path, destination: Path, state_file: Path) -> None:
try: try:
used_roots: set[str] = set() used_roots: set[str] = set()
transparent_roots = 0
for entry in sorted(entries, key=lambda item: (item["display_name"].casefold(), item["share_id"])): for entry in sorted(entries, key=lambda item: (item["display_name"].casefold(), item["share_id"])):
source = Path(entry["source_path"]) source = Path(entry["source_path"])
source_key = f"share:{entry['share_id']}" source_key = f"share:{entry['share_id']}"
is_file_share = entry["item_type"] == "file"
# Empty display_name is intentional: it represents the authenticated
# user's WebDAV root. Its children belong directly at destination;
# the Nextcloud username must never become an album wrapper.
if not is_file_share and entry["display_name"] == "":
transparent_roots += 1
if transparent_roots > 1:
raise ValueError("only one transparent WebDAV root is allowed")
if not source.is_dir():
raise FileNotFoundError(f"source is not a readable directory: {source}")
new_map[source_key] = "."
mirror_directory(source, staging, source_key, Path("."), old_map, new_map, used_roots)
continue
preferred = preferred_target(source_key, entry["display_name"], Path("."), old_map) preferred = preferred_target(source_key, entry["display_name"], Path("."), old_map)
is_file_share = entry["item_type"] == "file"
root_name = unique_name(preferred, used_roots, is_file_share) root_name = unique_name(preferred, used_roots, is_file_share)
root_key = Path(root_name) root_key = Path(root_name)
new_map[source_key] = root_key.as_posix() new_map[source_key] = root_key.as_posix()