Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0b70940
Optimize file manager move operations on same filesystem
mgutt Oct 19, 2025
42d6622
Add directory check for rsync rename optimization and suppress stderr…
mgutt Oct 19, 2025
d37af27
Use device IDs instead of mount points for more robust filesystem com…
mgutt Oct 19, 2025
2568736
Escape rsync filter meta-characters and handle symlinked directories …
mgutt Oct 19, 2025
fbcc368
file_manager: remove redundant PID validation and persist PID to surv…
mgutt Oct 24, 2025
6511029
file_manager: use separate PID file instead of INI serialization
mgutt Oct 24, 2025
77579fd
Merge branch 'unraid:master' into optimize-file-manager-move
mgutt Oct 24, 2025
69a21de
file_manager: persist move state and handle restarts
mgutt Oct 24, 2025
7c38306
file_manager: suppress outdated status messages
mgutt Oct 24, 2025
d26390c
file_manager: fixed syntax bug in nginx config to force using fileman…
mgutt Oct 24, 2025
37e59b6
Fix file_manager bugs and migrate to JSON status format
mgutt Oct 25, 2025
be210fc
Add minimum width guard to mb_strimhalf()
mgutt Oct 25, 2025
031b4ba
Fix JSON array index bug in parse_rsync_progress()
mgutt Oct 25, 2025
ef0be9c
Rename $move to $delete_empty_dirs and add commented parent-to-subfol…
mgutt Oct 25, 2025
f105f35
Enable parent-to-subdirectory check for rsync-rename moves
mgutt Oct 25, 2025
a33f8c4
Fix file_manager rsync issues: status parsing and directory permissions
mgutt Oct 25, 2025
58ec118
Optimize file_manager buffering and regex patterns
mgutt Oct 26, 2025
38c6d84
Test stdbuf -o0 for unbuffered rsync output
mgutt Oct 26, 2025
8103cba
Fix rsync output handling with process substitution
mgutt Oct 26, 2025
63328e9
Add explanatory comments and fix progress parsing
mgutt Oct 26, 2025
f693c49
Add comment explaining bash requirement for process substitution
mgutt Oct 26, 2025
da0bfca
Fix grep regex: Remove -C1 context flag to prevent progress line in f…
mgutt Oct 26, 2025
8c0ff09
Improve code readability: Reorder progress parsing logic and fix comm…
mgutt Oct 26, 2025
67c5626
Fix rsync progress parsing: Initialize text array and filter empty lines
mgutt Oct 26, 2025
617ce85
Optimize spinning icon animation in File Manager
mgutt Oct 26, 2025
07d4e06
Replace   with whitespace-nowrap utility class
mgutt Oct 26, 2025
46b3d5d
Remove unnecessary nowrap spans, simplify DOM manipulation
mgutt Oct 26, 2025
737a515
Add validation for empty jQuery objects in icon handling
mgutt Oct 26, 2025
4b90b9a
Remove unnecessary jQuery validation checks
mgutt Oct 26, 2025
ef01699
Add comment explaining --no-inc-recursive flag usage
mgutt Oct 26, 2025
d764b1e
Calculate ETA when rsync only shows elapsed time
mgutt Oct 26, 2025
6a4ec6e
Add ETA hysteresis to smooth fluctuations
mgutt Oct 26, 2025
d8e1034
Small rsync ETA fix and improved regex
mgutt Oct 27, 2025
dc180a8
Revert wrong test to extract file line of status file
mgutt Oct 28, 2025
bd271c7
Add -m1 to file_line grep for performance
mgutt Oct 28, 2025
e4339f5
Merge branch 'unraid:master' into optimize-file-manager-move
mgutt Oct 29, 2025
dada8c4
Fix subdirectory check for move operations
mgutt Oct 29, 2025
e2f53a6
Improve mb_strimhalf and calculate_eta functions
mgutt Oct 29, 2025
f0d0c28
Remove 'deleting' prefix from rsync rename status output
mgutt Oct 29, 2025
59c1615
Add --no-inc-recursive flag to rsync copy operations
mgutt Nov 1, 2025
3856369
improve comments in parse_rsync_progress function
mgutt Nov 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 0 additions & 36 deletions emhttp/plugins/dynamix/Browse.page
Original file line number Diff line number Diff line change
Expand Up @@ -498,15 +498,6 @@ function doAction(action, title, id) {
case 9: // move file
// disallow mixing of disk and user shares
var valid = ud ? /^\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).+$|^\/boot\/.+/);
// check if 'mv' can be used
if (path.length > 2) {
if (user) {
if (home(source,target) == 1) action++;
} else {
var mv = '/'+path[0]+'/'+path[1]+'/';
if (target.substr(0,mv.length) == mv) action++;
}
}
break;
case 11: // change owner
var valid = /.+/;
Expand Down Expand Up @@ -568,15 +559,6 @@ function doAction(action, title, id) {
case 9: // move file
// disallow mixing of disk and user shares
var valid = ud ? /^\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).+$|^\/boot\/.+/);
// check if 'mv' can be used
if (path.length > 2) {
if (user) {
if (home(source,target) == 1) action++;
} else {
var mv = '/'+path[0]+'/'+path[1]+'/';
if (target.substr(0,mv.length) == mv) action++;
}
}
break;
case 11: // change owner
var valid = /.+/;
Expand Down Expand Up @@ -782,15 +764,6 @@ function doActions(action, title) {
case 4: // move object
// disallow mixing of disk and user shares
var valid = ud ? /^\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).+$|^\/boot\/.+/);
// check if 'mv' can be used
if (path.length > 2) {
if (user) {
if (home(source.join('\n'),target) == 1) action++;
} else {
var mv = '/'+path[0]+'/'+path[1]+'/';
if (target.substr(0,mv.length) == mv) action++;
}
}
break;
case 11: // change owner
var valid = /.+/;
Expand Down Expand Up @@ -854,15 +827,6 @@ function doActions(action, title) {
case 4: // move object
// disallow mixing of disk and user shares
var valid = ud ? /^\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).+$|^\/boot\/.+/);
// check if 'mv' can be used
if (path.length > 2) {
if (user) {
if (home(source.join('\n'),target) == 1) action++;
} else {
var mv = '/'+path[0]+'/'+path[1]+'/';
if (target.substr(0,mv.length) == mv) action++;
}
}
break;
case 11: // change owner
var valid = /.+/;
Expand Down
121 changes: 114 additions & 7 deletions emhttp/plugins/dynamix/BrowseButton.page
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const dfm = {
};
var dfm_read = {};

function dfm_footer(action, text) {
function dfm_footer(action, text, $icon) {
switch (action) {
case 'show':
$('#user-notice').show();
Expand All @@ -61,8 +61,41 @@ function dfm_footer(action, text) {
$('#user-notice').hide();
break;
case 'write':
let icon = '<a class="hand" onclick="dfm_openDialog(true)" title="_(File Manager)_"><i class="icon-u-duplicate dfm"></i></a>';
$('#user-notice').html(icon + text);
let fileManagerIcon = '<a class="hand" onclick="dfm_openDialog(true)" title="_(File Manager)_"><i class="icon-u-duplicate dfm"></i></a>';
let $notice = $('#user-notice');

// Ensure text is a string
text = text || '';

// Add icon and text
if ($icon && text) {

// Extract selector from icon classes
let iconSelector = '.' + $icon.attr('class').split(' ').join('.');
Comment thread
mgutt marked this conversation as resolved.

// Check if icon already exists in DOM to preserve animation
let $existingIcon = $notice.find(iconSelector);

// Reuse existing icon element, update only text
if ($existingIcon.length > 0) {
// Preserve icon in DOM, only update text nodes
$notice.contents().filter(function() {
return this.nodeType === 3; // Text nodes only
}).remove();

// Update text after icon (which stays in place)
$existingIcon.after(document.createTextNode(text));

// First time - insert icon + text
} else {
$notice.empty().append(fileManagerIcon).append($icon.clone()).append(document.createTextNode(text));
}

// No icon or no text - insert as-is
} else {
$notice.html(fileManagerIcon + text);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

break;
case 'clear':
$('#user-notice').html('');
Expand Down Expand Up @@ -113,6 +146,82 @@ function dfm_createSource(source) {

function dfm_showProgress(data) {
if (!data) return 0;

// Try to parse as JSON first
try {
let parsed = JSON.parse(data);

// Universal JSON format: {action: int, text: [text0, text1]}
// text[0] = file/main text, text[1] = progress info (optional)
if (parsed.action !== undefined && Array.isArray(parsed.text)) {

// Define spinning icon as jQuery object
let $icon = $('<i/>').addClass('fa fa-circle-o-notch fa-spin dfm');

// Extract selector from icon classes
let iconSelector = '.' + $icon.attr('class').split(' ').join('.');

// Build dialog text - preserve existing icon if present to avoid animation restart
let $dialogContainer = dfm.window.find('.dfm_text');
let dialogText = parsed.text[0] || '';
if (dialogText) {

// Find existing icon elements (there might be two: one for file, one for progress)
let $existingIcons = $dialogContainer.find(iconSelector);
if ($existingIcons.length > 0) {

// Preserve icons in DOM, only update text nodes
$dialogContainer.contents().filter(function() {
return this.nodeType === 3; // Text nodes only
}).remove();

// Remove <br> elements
$dialogContainer.find('br').remove();

// Insert updated text after first icon (which stays in place)
$existingIcons.first().after(document.createTextNode(dialogText));

// Add progress line with its own icon (reuse or create second icon)
if (parsed.text[1]) {
let $secondIcon = $existingIcons.eq(1);
if ($secondIcon.length === 0) {
// Need to add second icon
$dialogContainer.append('<br>').append($icon.clone()).append(document.createTextNode(parsed.text[1]));
} else {
// Second icon exists, just update text
$secondIcon.before('<br>');
$secondIcon.after(document.createTextNode(parsed.text[1]));
}
}

// First time - insert icon + text
} else {
$dialogContainer.empty().append($icon.clone()).append(document.createTextNode(dialogText));

// Optionally add second line of icon + text
if (parsed.text[1]) {
$dialogContainer.append('<br>').append($icon.clone()).append(document.createTextNode(parsed.text[1]));
}

}

// No text - clear everything including icon
} else {
$dialogContainer.empty();
}

// Build footer text (progress info only)
let footerText = parsed.text[1] || parsed.text[0] || '';
dfm_footer('write', footerText, $icon);
dfm.previous = footerText;
return 0;

}
} catch(e) {
// Not JSON, fallback to old string parsing
}

// Legacy string parsing for non-migrated operations
let file = null;
let text = data.split('\n');
let line = text[0].split('... ');
Expand Down Expand Up @@ -206,8 +315,7 @@ function dfm_makeDialog(open) {
dfm.window.find('#dfm_exist').prop('checked',dfm_read.exist ? false : true);
dfm.height = 630;
break;
case 4: // move folder/object (rsync)
case 5: // move folder/object (mv)
case 4: // move folder/object
dfm.window.html($('#dfm_templateMoveFolder').html());
dfm.window.find('#dfm_target').val(dfm_read.target).prop('disabled',true);
dfm.window.find('#dfm_sparse').prop('checked',dfm_read.sparse ? true : false);
Expand All @@ -225,8 +333,7 @@ function dfm_makeDialog(open) {
dfm.window.find('#dfm_exist').prop('checked',dfm_read.exist ? false : true);
dfm.height = 630;
break;
case 9: // move file (rsync)
case 10: // move file (mv)
case 9: // move file
dfm.window.html($('#dfm_templateMoveFile').html());
dfm.window.find('#dfm_target').val(dfm_read.target).prop('disabled',true);
dfm.window.find('#dfm_sparse').prop('checked',dfm_read.sparse ? true :false);
Expand Down
Loading
Loading