Offline-tolerant music-video merge
The merge no longer refuses when NAS is unreachable. DB changes always run; per-side file operations are best-effort. When the NAS rename can't happen it's recorded in a new pending_nas_moves table, and nas:auto-sync drains the queue (rename + best-effort parent prune) once NAS is back, retaining attempts/last_error on failure. Also plugs missing isEnabled() guards on renameNasPath and deleteFolder so the merge cleanup path can't hang on smbclient timeouts. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
fcb752dd76
commit
847020fd02
4 files changed
+101
-12
No files matched your search
@@ -59,12 +59,6 @@ class VideoMergeService
|
||||
if ($source->type !== 'music' || $target->type !== 'music') {
|
||||
throw new \InvalidArgumentException('Only music-type videos can be merged.');
|
||||
}
|
||||
if (!$this->nas->isEnabled()) {
|
||||
throw new \RuntimeException(
|
||||
'NAS is currently unreachable — merge is disabled until it comes back online.'
|
||||
);
|
||||
}
|
||||
|
||||
$source->loadMissing(['audioTracks']);
|
||||
|
||||
$sourceDir = $this->nas->resolveVideoDir($source);
|
||||
@@ -74,14 +68,22 @@ class VideoMergeService
|
||||
throw new \RuntimeException('Source and target resolve to the same folder — refusing to merge.');
|
||||
}
|
||||
|
||||
// Merge is DB-first: the schema changes always run, and file moves
|
||||
// are best-effort on both sides. When NAS is unreachable, per-track
|
||||
// NAS renames get queued in `pending_nas_moves` and drained by
|
||||
// `nas:auto-sync` once NAS returns.
|
||||
$nasReachable = $this->nas->isEnabled();
|
||||
|
||||
Log::info('Merge: begin', [
|
||||
'source_id' => $source->id,
|
||||
'target_id' => $target->id,
|
||||
'source_dir' => $sourceDir,
|
||||
'target_dir' => $targetDir,
|
||||
'source_id' => $source->id,
|
||||
'target_id' => $target->id,
|
||||
'source_dir' => $sourceDir,
|
||||
'target_dir' => $targetDir,
|
||||
'nas_reachable' => $nasReachable,
|
||||
]);
|
||||
|
||||
// Ensure target has a tracks/ subfolder on both NAS and local
|
||||
// Ensure target has a tracks/ subfolder — always locally; on NAS
|
||||
// only when it's reachable (mkdirp itself no-ops otherwise).
|
||||
$this->nas->mkdirp("{$targetDir}/tracks");
|
||||
@mkdir(storage_path("app/{$targetDir}/tracks"), 0755, true);
|
||||
|
||||
@@ -202,7 +204,8 @@ class VideoMergeService
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a folder on both NAS and local disk. Logs the outcome.
|
||||
* Move a folder on both NAS and local disk. NAS ops are queued in
|
||||
* pending_nas_moves when NAS is unreachable; nas:auto-sync drains them.
|
||||
*/
|
||||
private function moveFolder(string $oldRel, string $newRel): void
|
||||
{
|
||||
@@ -212,6 +215,16 @@ class VideoMergeService
|
||||
if ($this->nas->isEnabled()) {
|
||||
$this->nas->renameNasPath($oldRel, $newRel);
|
||||
$nasMoved = true;
|
||||
} else {
|
||||
// Queue for later — nas:auto-sync will do the rename when NAS is back.
|
||||
DB::table('pending_nas_moves')->insert([
|
||||
'from_path' => $oldRel,
|
||||
'to_path' => $newRel,
|
||||
'kind' => 'folder',
|
||||
'origin' => 'merge',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$oldLocal = storage_path('app/' . $oldRel);
|
||||
@@ -224,6 +237,7 @@ class VideoMergeService
|
||||
Log::info('Merge: moveFolder', [
|
||||
'old' => $oldRel, 'new' => $newRel,
|
||||
'nas' => $nasMoved, 'local' => $localMoved,
|
||||
'queued' => !$nasMoved,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -233,6 +247,12 @@ class VideoMergeService
|
||||
$this->nas->deleteFile("{$rel}/meta.json");
|
||||
$this->nas->deleteFolder($rel);
|
||||
}
|
||||
// If NAS was unreachable we DON'T queue a delete: after auto-sync
|
||||
// drains the pending moves, the leftover source folder will be
|
||||
// empty and can be reaped by a subsequent housekeeping pass (or by
|
||||
// the videos:repair-merged command). Deleting a folder we haven't
|
||||
// yet emptied would blow away data.
|
||||
|
||||
$local = storage_path('app/' . $rel);
|
||||
if (is_dir($local)) {
|
||||
@unlink($local . '/meta.json');
|
||||
|
||||
Reference in new issue
Block a user