diff --git a/app/common/service/stack/StackModeService.php b/app/common/service/stack/StackModeService.php index e7918a1..e6d2582 100644 --- a/app/common/service/stack/StackModeService.php +++ b/app/common/service/stack/StackModeService.php @@ -3,7 +3,114 @@ namespace app\common\service\stack; use base\common\service\stack\StackModeServiceBase; +use RuntimeException; class StackModeService extends StackModeServiceBase { + public function getModePlan(string $mode): array + { + $mode = trim($mode); + if ($mode === '') { + throw new RuntimeException('模式不能为空'); + } + + $config = $this->loadConfig(); + $modes = $config['modes']; + if (!isset($modes[$mode])) { + throw new RuntimeException("模式不存在:{$mode}"); + } + + $defaultMode = $config['default_mode']; + $managedFiles = $config['managed_files']; + + $plan = []; + $skippedFiles = []; + foreach ($managedFiles as $relativePath) { + $modeSource = $this->resolveModeFile($mode, $relativePath); + if ($modeSource !== null) { + $plan[] = [ + 'file' => $relativePath, + 'source_mode' => $mode, + 'source_path' => $modeSource, + ]; + continue; + } + + $defaultSource = $this->resolveModeFile($defaultMode, $relativePath); + if ($defaultSource !== null) { + $plan[] = [ + 'file' => $relativePath, + 'source_mode' => $defaultMode, + 'source_path' => $defaultSource, + ]; + continue; + } + + // File not found in mode OR default - skip it (don't throw) + $skippedFiles[] = $relativePath; + } + + return [ + 'mode' => $mode, + 'default_mode' => $defaultMode, + 'managed_files' => $managedFiles, + 'plan' => $plan, + 'skipped_files' => $skippedFiles, + ]; + } + + public function applyMode(string $mode, string $operator = 'system'): array + { + // Use our overridden getModePlan (skips missing files instead of throwing) + $result = parent::applyMode($mode, $operator); + + $appliedFiles = $result['applied_files'] ?? []; + $backupId = $result['backup_id'] ?? ''; + + $config = $this->loadConfig(); + $managedFiles = $config['managed_files']; + + $deletedFiles = []; + + foreach ($managedFiles as $relativePath) { + // Skip files already handled by parent + if (in_array($relativePath, $appliedFiles)) { + continue; + } + + $targetPath = $this->toRootPath($relativePath); + if (!is_file($targetPath)) { + continue; + } + + // This file was skipped by getModePlan (not in mode or default) + // Back it up before deleting + if ($backupId !== '') { + $backupDir = $this->joinPath($this->backupRoot, $backupId); + $backupFilesDir = $this->joinPath($backupDir, 'files'); + $backupFilePath = $this->joinPath($backupFilesDir, $this->toSystemPath($relativePath)); + + $this->ensureDir(dirname($backupFilePath)); + copy($targetPath, $backupFilePath); + + // Update backup meta + $metaPath = $this->joinPath($backupDir, 'meta.json'); + if (is_file($metaPath)) { + $meta = $this->readJsonFile($metaPath); + $meta['files'][] = [ + 'file' => $relativePath, + 'existed' => true, + ]; + $this->writeJsonFile($metaPath, $meta); + } + + // Only delete if we successfully backed up + @unlink($targetPath); + $deletedFiles[] = $relativePath; + } + } + + $result['deleted_files'] = $deletedFiles; + return $result; + } }