mirror of
https://github.com/Terranom674/Piwigo_Bratonien_Tools.git
synced 2026-09-19 19:54:31 +00:00
Compare commits
7 Commits
fix/09627-
...
fix/09629-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b915c310a | ||
|
|
6df469ab7a | ||
|
|
55294643b4 | ||
|
|
0f3c78f02c | ||
|
|
d80163a704 | ||
|
|
9e57c4ea3d | ||
|
|
30f1c0af18 |
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
Plugin Name: Bratonien Tools
|
Plugin Name: Bratonien Tools
|
||||||
Version: 0.9.6.27
|
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
22
runtime/lib/build_webdav_placeholder_source.py
Normal file → Executable 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():
|
||||||
|
|||||||
29
runtime/lib/piwigo-sync.php
Normal file → Executable file
29
runtime/lib/piwigo-sync.php
Normal file → Executable file
@@ -252,8 +252,6 @@ try
|
|||||||
);
|
);
|
||||||
decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'bratonien.nc.syncProductive', 'site_id'=>$site_id), $headers));
|
decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'bratonien.nc.syncProductive', 'site_id'=>$site_id), $headers));
|
||||||
$orphan = decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'bratonien.nc.syncOrphans', 'site_id'=>$site_id, 'simulate'=>0), $headers));
|
$orphan = decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'bratonien.nc.syncOrphans', 'site_id'=>$site_id, 'simulate'=>0), $headers));
|
||||||
// Entfernt alte technische bratonien-webdav-N Wrapper aus Site 1,
|
|
||||||
// nachdem deren generierte Verzeichnisse beim Reconcile entfernt wurden.
|
|
||||||
if ($site_id !== 1)
|
if ($site_id !== 1)
|
||||||
{
|
{
|
||||||
decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'bratonien.nc.syncOrphans', 'site_id'=>1, 'simulate'=>0), $headers));
|
decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'bratonien.nc.syncOrphans', 'site_id'=>1, 'simulate'=>0), $headers));
|
||||||
@@ -284,16 +282,31 @@ try
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'pwg.session.login', 'username'=>$fallback_user, 'password'=>$fallback_password), array(), $cookie_file));
|
decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'pwg.session.login', 'username'=>$fallback_user, 'password'=>$fallback_password), array(), $cookie_file));
|
||||||
http_request(
|
|
||||||
$base_url.'/admin.php?page=site_update&site='.$site_id,
|
// Der Fallback darf keinen zweiten Strukturpfad benutzen. Auch mit
|
||||||
array('sync'=>'files','display_info'=>1,'privacy_level'=>0,'sync_meta'=>1,'simulate'=>0,'subcats-included'=>1,'bratonien_connector'=>1,'submit'=>1),
|
// Benutzer/Passwort wird exakt derselbe Bratonien-Sync wie mit API-Key
|
||||||
|
// ausgefuehrt. Dadurch werden alte technische WebDAV-Kategorien entfernt
|
||||||
|
// und vorhandene Piwigo-Alben wiederverwendet.
|
||||||
|
decode_ws(http_request(
|
||||||
|
$base_url.'/ws.php?format=json',
|
||||||
|
array('method'=>'bratonien.nc.syncProductive', 'site_id'=>$site_id),
|
||||||
array(),
|
array(),
|
||||||
$cookie_file
|
$cookie_file
|
||||||
);
|
));
|
||||||
$orphan = decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'bratonien.nc.syncOrphans', 'site_id'=>$site_id, 'simulate'=>0), array(), $cookie_file));
|
$orphan = decode_ws(http_request(
|
||||||
|
$base_url.'/ws.php?format=json',
|
||||||
|
array('method'=>'bratonien.nc.syncOrphans', 'site_id'=>$site_id, 'simulate'=>0),
|
||||||
|
array(),
|
||||||
|
$cookie_file
|
||||||
|
));
|
||||||
if ($site_id !== 1)
|
if ($site_id !== 1)
|
||||||
{
|
{
|
||||||
decode_ws(http_request($base_url.'/ws.php?format=json', array('method'=>'bratonien.nc.syncOrphans', 'site_id'=>1, 'simulate'=>0), array(), $cookie_file));
|
decode_ws(http_request(
|
||||||
|
$base_url.'/ws.php?format=json',
|
||||||
|
array('method'=>'bratonien.nc.syncOrphans', 'site_id'=>1, 'simulate'=>0),
|
||||||
|
array(),
|
||||||
|
$cookie_file
|
||||||
|
));
|
||||||
}
|
}
|
||||||
$added = (int)($orphan['added_orphans'] ?? 0);
|
$added = (int)($orphan['added_orphans'] ?? 0);
|
||||||
$deleted = (int)($orphan['deleted_orphans'] ?? 0);
|
$deleted = (int)($orphan['deleted_orphans'] ?? 0);
|
||||||
|
|||||||
31
runtime/lib/shadow_tree.py
Normal file → Executable file
31
runtime/lib/shadow_tree.py
Normal file → Executable 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()
|
||||||
|
|||||||
Reference in New Issue
Block a user