diff --git a/build-plugin.sh b/build-plugin.sh index 5b519fa..4c2c627 100755 --- a/build-plugin.sh +++ b/build-plugin.sh @@ -2,7 +2,7 @@ set -eu PLUGIN="u-navigator" -VERSION="2026.06.23.r013" +VERSION="2026.06.23.r014" PKG_DIR="packages" WORK_DIR=".plugin-build" PKG_NAME="${PLUGIN}-${VERSION}.tgz" diff --git a/packages/u-navigator-2026.06.23.r013.tgz b/packages/u-navigator-2026.06.23.r013.tgz deleted file mode 100644 index 070a3cb..0000000 Binary files a/packages/u-navigator-2026.06.23.r013.tgz and /dev/null differ diff --git a/packages/u-navigator-2026.06.23.r014.tgz b/packages/u-navigator-2026.06.23.r014.tgz new file mode 100644 index 0000000..3bebf93 Binary files /dev/null and b/packages/u-navigator-2026.06.23.r014.tgz differ diff --git a/plugin-root/usr/local/emhttp/plugins/u-navigator/api/job.php b/plugin-root/usr/local/emhttp/plugins/u-navigator/api/job.php index cd750af..062989b 100644 --- a/plugin-root/usr/local/emhttp/plugins/u-navigator/api/job.php +++ b/plugin-root/usr/local/emhttp/plugins/u-navigator/api/job.php @@ -5,17 +5,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { unav_json(unav_job_read($_GET['id'] ?? '')); } -unav_assert_writable(); - $action = $_GET['action'] ?? ''; -if (!in_array($action, ['move', 'copy'], true)) { +if (!in_array($action, ['move', 'copy', 'size'], true)) { unav_error(404, 'Unknown job action'); } $data = unav_request_data(); $source = unav_existing_path($data['source'] ?? null); -$destination = unav_destination_path($data['destination'] ?? null); -unav_guard_destination($source['path'], $destination['path']); +$destination = null; +if (in_array($action, ['move', 'copy'], true)) { + unav_assert_writable(); + $destination = unav_destination_path($data['destination'] ?? null); + unav_guard_destination($source['path'], $destination['path']); +} $id = str_replace('.', '', uniqid($action . '-', true)); $now = date(DATE_ATOM); @@ -27,7 +29,7 @@ $job = [ 'message' => 'Queued', 'error' => null, 'source' => $source['path'], - 'destination' => $destination['path'], + 'destination' => $destination['path'] ?? null, 'createdAt' => $now, 'updatedAt' => $now, ]; diff --git a/plugin-root/usr/local/emhttp/plugins/u-navigator/api/job_worker.php b/plugin-root/usr/local/emhttp/plugins/u-navigator/api/job_worker.php index cdda58f..0396fe5 100644 --- a/plugin-root/usr/local/emhttp/plugins/u-navigator/api/job_worker.php +++ b/plugin-root/usr/local/emhttp/plugins/u-navigator/api/job_worker.php @@ -9,12 +9,16 @@ function unav_run_job(string $id): void { $job = unav_job_read($id); try { unav_worker_update($job, 'running', 5, 'Preparing'); - unav_worker_guard($job['source'], $job['destination']); if ($job['type'] === 'move') { + unav_worker_guard($job['source'], $job['destination']); unav_worker_move($job['source'], $job['destination'], $job); } elseif ($job['type'] === 'copy') { + unav_worker_guard($job['source'], $job['destination']); unav_worker_copy($job['source'], $job['destination'], $job); + } elseif ($job['type'] === 'size') { + $result = unav_worker_size($job['source'], $job); + $job['result'] = $result; } else { throw new RuntimeException('Unknown job action'); } @@ -55,6 +59,15 @@ function unav_worker_guard(string $source, string $destination): void { } } +function unav_worker_guard_source(string $source): string { + $sourceReal = realpath($source); + if ($sourceReal === false) { + throw new RuntimeException('Source not found'); + } + unav_worker_assert_inside_root($sourceReal); + return $sourceReal; +} + function unav_worker_assert_inside_root(string $path): void { foreach (unav_roots() as $root) { if (unav_is_inside($path, $root['path'])) { @@ -73,6 +86,38 @@ function unav_worker_move(string $source, string $destination, array &$job): voi unav_worker_delete($source); } +function unav_worker_size(string $source, array &$job): array { + $sourceReal = unav_worker_guard_source($source); + unav_worker_update($job, 'running', 10, 'Calculating size'); + $result = unav_worker_size_recursive($sourceReal); + unav_worker_update($job, 'running', 95, 'Size calculated'); + return $result; +} + +function unav_worker_size_recursive(string $path): array { + if (is_link($path)) { + return ['size' => 0, 'files' => 0, 'directories' => 0]; + } + if (is_file($path)) { + return ['size' => filesize($path) ?: 0, 'files' => 1, 'directories' => 0]; + } + if (!is_dir($path)) { + return ['size' => 0, 'files' => 0, 'directories' => 0]; + } + + $total = ['size' => 0, 'files' => 0, 'directories' => 1]; + foreach (scandir($path) ?: [] as $entry) { + if ($entry === '.' || $entry === '..') { + continue; + } + $child = unav_worker_size_recursive($path . '/' . $entry); + $total['size'] += $child['size']; + $total['files'] += $child['files']; + $total['directories'] += $child['directories']; + } + return $total; +} + function unav_worker_copy(string $source, string $destination, array &$job): void { if (is_link($source)) { throw new RuntimeException('Copying symlinks is not allowed'); diff --git a/server/public/app.js b/server/public/app.js index 6b8edad..6174e80 100644 --- a/server/public/app.js +++ b/server/public/app.js @@ -15,6 +15,8 @@ const state = { nextDebugId: 1, jobs: new Map(), jobRefresh: new Map(), + sizeJobs: new Map(), + sizeResults: new Map(), nextWindowId: 1, zIndex: 20 }; @@ -339,19 +341,23 @@ function renderProperties(body) { return; } + const sizeLabel = entry.type === 'directory' ? directorySizeLabel(entry) : formatBytes(entry.size); body.classList.add('properties'); body.innerHTML = `