diff --git a/Plugin/unraid/pages/scheduler.php b/Plugin/unraid/pages/scheduler.php index fb52f0a..027401f 100644 --- a/Plugin/unraid/pages/scheduler.php +++ b/Plugin/unraid/pages/scheduler.php @@ -4661,21 +4661,42 @@ async function vvSaveArrange() { if (!arrayMap.has(primaryArray)) arrayMap.set(primaryArray, []); }); - let failed = false; + const fail = () => { + btn.textContent = 'Error!'; + btn.style.color = '#f44336'; + setTimeout(() => { btn.textContent = 'Save Arrangement'; btn.disabled = false; btn.style.color = ''; }, 2500); + }; + + // Phase 1 — relocations, one atomic call each. + // + // A cross-array move changes two arrays. Expressing it as two reorderarray.php calls means + // a failure between them leaves master.conf half-written: the script removed from its old + // orchestrator and never added to the new one, or present in both and running twice. There + // is no rollback, and the page still shows the intended arrangement, so the operator re-drags + // from a view that no longer matches the file. + // + // movescript.php does the whole relocation inside one guarded write, so it either happens or + // it does not. Phase 2 can then only get the ordering wrong, never the membership. + const moves = new Map(); // script → final array ('' = out of every orchestrator) + for (const p of vvArrangePending) { + if (p.fromArray === p.toArray) continue; // pure reorder — phase 2 owns it + moves.set(p.script, p.toArray || ''); + } + for (const [script, toArray] of moves) { + const r = await vvPost('/plugins/varaverk/api/movescript.php', { script, to_array: toArray }) + .catch(() => ({ ok: false })); + if (!r.ok) return fail(); + } + + // Phase 2 — order and enabled state, per array. Membership is already correct. for (const [arrayName, scripts] of arrayMap) { const r = await vvPost('/plugins/varaverk/api/reorderarray.php', { array_name: arrayName, scripts: JSON.stringify(scripts) - }).then(r => r.json()).catch(() => ({ ok: false })); - if (!r.ok) { failed = true; break; } + }).catch(() => ({ ok: false })); + if (!r.ok) return fail(); } - if (failed) { - btn.textContent = 'Error!'; - btn.style.color = '#f44336'; - setTimeout(() => { btn.textContent = 'Save Arrangement'; btn.disabled = false; btn.style.color = ''; }, 2500); - return; - } location.reload(); } @@ -4905,7 +4926,7 @@ async function _vvFolderDrop(e) { const folders = vvGetCurrentFolders(); const r = await vvPost('/plugins/varaverk/api/savefolders.php', { folders: JSON.stringify(folders) - }).then(r => r.json()).catch(() => ({ ok: false })); + }).catch(() => ({ ok: false })); if (!r.ok) { oldParent.appendChild(srcEl); @@ -5062,7 +5083,7 @@ async function _vvDoCreateFolder(name, wrap) { folders[name] = []; const r = await vvPost('/plugins/varaverk/api/savefolders.php', { folders: JSON.stringify(folders) - }).then(r => r.json()).catch(() => ({ ok: false })); + }).catch(() => ({ ok: false })); if (!r.ok) { vvAlert('Failed to create folder.'); return; } wrap.remove(); // Add folder group to DOM