Skip to content

Commit 523e659

Browse files
authored
Merge pull request #2516 from mgutt/fix-issue-2510
Fix issue #2510 and preserve empty directories during move
2 parents fa37cf3 + d4ae499 commit 523e659

File tree

1 file changed

+34
-7
lines changed

1 file changed

+34
-7
lines changed

emhttp/plugins/dynamix/nchan/file_manager

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -540,12 +540,14 @@ while (true) {
540540
}
541541
}
542542

543-
// target must not be a subdirectory of any source (backup-dir should be outside source tree)
544-
$source_dirname = is_dir($valid_source_path) ? $valid_source_path : dirname($valid_source_path);
545-
if (strpos(rtrim($target,'/') . '/', rtrim($source_dirname,'/') . '/') === 0) {
546-
$reply['error'] = _('Cannot move directory into its own subdirectory');
547-
$use_rsync_rename = false;
548-
break 2; // break out of both: foreach and case
543+
// target must not be a subdirectory of any source directory (backup-dir should be outside source tree)
544+
// This check is only relevant when moving directories, not files
545+
if (is_dir($valid_source_path)) {
546+
if (strpos(rtrim($target,'/') . '/', rtrim($valid_source_path,'/') . '/') === 0) {
547+
$reply['error'] = _('Cannot move directory into its own subdirectory');
548+
$use_rsync_rename = false;
549+
break 2; // break out of both: foreach and case
550+
}
549551
}
550552

551553
}
@@ -557,9 +559,34 @@ while (true) {
557559
// - existing files are overwritten in --backup-dir (like not using --ignore-existing)
558560
// - missing directories are created in --backup-dir (like using --mkpath)
559561
// - rsync prefixes the moved files with "deleting " in the output, which we strip with sed, to not confuse the user
562+
// - rsync --backup deletes empty directories instead of moving them to --backup-dir (https://github.com/RsyncProject/rsync/issues/842), so we copy empty directories first
560563
if ($use_rsync_rename) {
561564
$parent_dir = dirname(validname($source[0]));
562-
$cmd = "rsync -r --out-format=%f --info=flist0,misc0,stats0,name1,progress2 --delete --backup --backup-dir=".escapeshellarg($target)." ".quoted_rsync_include($source)." --exclude='*' ".escapeshellarg($empty_dir)." ".escapeshellarg($parent_dir)." > >(stdbuf -o0 tr '\\r' '\\n' | sed 's/^deleting //' >$status) 2>$error & echo \$!";
565+
$parent_dir_escaped = escapeshellarg($parent_dir);
566+
$target_escaped = escapeshellarg($target);
567+
$empty_dir_escaped = escapeshellarg($empty_dir);
568+
$rsync_includes = quoted_rsync_include($source);
569+
570+
// Build relative paths for find (e.g., ./dir instead of /mnt/disk1/sharename/dir)
571+
$source_relative = [];
572+
foreach ($source as $s) {
573+
$valid = validname($s);
574+
if ($valid) {
575+
$source_relative[] = escapeshellarg('./' . basename($valid));
576+
}
577+
}
578+
$source_relative_joined = implode(' ', $source_relative);
579+
580+
// Execute both rsync commands in a single bash block so they share the same PID and output stream
581+
// First: copy only empty directories to target, then: move everything with rsync rename trick
582+
$cmd = <<<BASH
583+
{
584+
cd $parent_dir_escaped &&
585+
find $source_relative_joined -type d -empty -print0 | rsync -aX --files-from=- --from0 --out-format=%f --info=flist0,misc0,stats0,name1,progress2 -d . $target_escaped &&
586+
rsync -r --out-format=%f --info=flist0,misc0,stats0,name1,progress2 --delete --backup --backup-dir=$target_escaped $rsync_includes --exclude='*' $empty_dir_escaped $parent_dir_escaped
587+
} > >(stdbuf -o0 tr '\\r' '\\n' | sed 's/^deleting //' >$status) 2>$error & echo \$!
588+
BASH;
589+
563590
exec($cmd, $pid);
564591

565592
// use rsync copy-delete

0 commit comments

Comments
 (0)