diff --git a/include/nc_productive_ws.inc.php b/include/nc_productive_ws.inc.php index 8c7a88f..29f9fd1 100644 --- a/include/nc_productive_ws.inc.php +++ b/include/nc_productive_ws.inc.php @@ -62,18 +62,12 @@ SELECT id, dir, name OR LOWER(name) = LOWER(\''.$name_sql.'\') ) ORDER BY CASE WHEN dir = \''.$dir_sql.'\' THEN 0 ELSE 1 END, id + LIMIT 1 ;'; $result = pwg_query($query); - $ids = array(); - while ($row = pwg_db_fetch_assoc($result)) - { - $ids[] = (int)$row['id']; - } - if (count($ids) > 1) - { - throw new RuntimeException('Albumzuordnung ist mehrdeutig fuer "'.$name.'".'); - } - return count($ids) === 1 ? $ids[0] : null; + if (!pwg_db_num_rows($result)) return null; + $row = pwg_db_fetch_assoc($result); + return (int)$row['id']; } 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 { - // 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); 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); } - // Refresh the normal file attributes for connector-managed images only. $updates = array(); foreach ($all_ids as $id) { diff --git a/main.inc.php b/main.inc.php index 27e26e3..3b19d9e 100644 --- a/main.inc.php +++ b/main.inc.php @@ -1,7 +1,7 @@ int: for remote_root_raw in args.root: 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"]) if fileid in used_fileids: fail(f"duplicate selected Nextcloud root fileid: {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_root = staging / local_name files, folders, skipped = build_root(client, remote_root, local_root, seed, mapping) total_files += files total_folders += folders 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}") if previous.exists(): diff --git a/runtime/lib/piwigo-sync.php b/runtime/lib/piwigo-sync.php old mode 100644 new mode 100755 diff --git a/runtime/lib/shadow_tree.py b/runtime/lib/shadow_tree.py old mode 100644 new mode 100755 index 7d19238..c1b1477 --- a/runtime/lib/shadow_tree.py +++ b/runtime/lib/shadow_tree.py @@ -79,23 +79,15 @@ def preferred_target(source_key: str, raw_name: str, parent_target: Path, old_ma return safe_name(raw_name) -def mirror_directory( - 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: +def mirror_directory(source: Path, target: Path, source_key: str, target_key: Path, old_map: dict[str, str], new_map: dict[str, str]) -> None: 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)): if child.is_symlink(): continue child_source_key = f"{source_key}/{child.name}" 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_key = target_key / child_name 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: used_roots: set[str] = set() - transparent_roots = 0 for entry in sorted(entries, key=lambda item: (item["display_name"].casefold(), item["share_id"])): source = Path(entry["source_path"]) 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) + is_file_share = entry["item_type"] == "file" root_name = unique_name(preferred, used_roots, is_file_share) root_key = Path(root_name) new_map[source_key] = root_key.as_posix()