diff --git a/app/Services/SyncService.php b/app/Services/SyncService.php index 4f69df0..27b3f4a 100644 --- a/app/Services/SyncService.php +++ b/app/Services/SyncService.php @@ -84,6 +84,11 @@ public function sync(): array $report = []; $baseUrl = "http://{$this->peerIp}:{$this->peerPort}"; + $pruned = $this->pruneOrphanedPending(); + if ($pruned > 0) { + $report[] = "{$pruned} مسیر فایل یتیم از صف retry حذف شد."; + } + $healed = $this->healMissingFiles(); if ($healed > 0) { $report[] = "{$healed} فایل گمشده شناسایی شد و در صف دریافت قرار گرفت."; @@ -534,6 +539,10 @@ private function applyChange(array $change, string $prefix, array &$report): voi try { if ($action === 'deleted') { + if ($table === 'patients') { + $this->deletePatientFilesFromDisk((int) $recordId); + $this->removePatientFilesFromPending((int) $recordId); + } DB::table($table)->where('id', $recordId)->delete(); $report[] = "{$label} حذف ✓"; } elseif ($action === 'created') { @@ -546,6 +555,9 @@ private function applyChange(array $change, string $prefix, array &$report): voi $report[] = "{$label} ایجاد ✓"; } } elseif ($action === 'updated') { + if ($table === 'patients') { + $this->deleteRemovedPatientFiles((int) $recordId, $data); + } DB::table($table)->where('id', $recordId)->update($this->sanitize($data)); $report[] = "{$label} بروزرسانی ✓"; } @@ -554,6 +566,153 @@ private function applyChange(array $change, string $prefix, array &$report): voi } } + private function deletePatientFilesFromDisk(int $patientId): void + { + $fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files']; + $row = DB::table('patients')->find($patientId); + if (! $row) { + return; + } + + foreach ($fileFields as $field) { + if (empty($row->$field)) { + continue; + } + $files = json_decode($row->$field, true) ?? []; + foreach ($files as $path) { + if (! empty($path)) { + Storage::disk('public')->delete(ltrim(str_replace('\\', '/', $path), '/')); + Log::info('sync: deleted patient file from disk', ['file' => $path, 'patient_id' => $patientId]); + } + } + } + } + + private function removePatientFilesFromPending(int $patientId): void + { + $fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files']; + $row = DB::table('patients')->find($patientId); + if (! $row) { + return; + } + + $patientPaths = []; + foreach ($fileFields as $field) { + if (empty($row->$field)) { + continue; + } + $files = json_decode($row->$field, true) ?? []; + foreach ($files as $path) { + if (! empty($path)) { + $patientPaths[] = ltrim(str_replace('\\', '/', $path), '/'); + } + } + } + + if (empty($patientPaths)) { + return; + } + + foreach (['s2c', 'c2s'] as $direction) { + $pending = $this->getPendingFiles($direction); + $cleaned = array_values(array_diff($pending, $patientPaths)); + if (count($cleaned) !== count($pending)) { + $this->savePendingFiles($direction, $cleaned); + Log::info('sync: removed deleted patient files from pending', [ + 'patient_id' => $patientId, + 'direction' => $direction, + 'removed' => count($pending) - count($cleaned), + ]); + } + } + } + private function deleteRemovedPatientFiles(int $patientId, array $newData): void + { + $fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files']; + + foreach ($fileFields as $field) { + if (! array_key_exists($field, $newData)) { + continue; + } + + $row = DB::table('patients')->find($patientId); + if (! $row || empty($row->$field)) { + continue; + } + + $oldFiles = json_decode($row->$field, true) ?? []; + $newRaw = $newData[$field]; + $newFiles = is_array($newRaw) ? $newRaw : (json_decode($newRaw, true) ?? []); + $newFiles = array_map(fn($p) => ltrim(str_replace('\\', '/', $p), '/'), $newFiles); + + $removed = array_diff( + array_map(fn($p) => ltrim(str_replace('\\', '/', $p), '/'), $oldFiles), + $newFiles, + ); + + foreach ($removed as $path) { + if (! empty($path)) { + Storage::disk('public')->delete($path); + Log::info('sync: deleted removed patient file from disk', ['file' => $path, 'patient_id' => $patientId]); + } + } + } + } + + public function pruneOrphanedPending(): int + { + $referenced = $this->getAllReferencedFilePaths(); + $pruned = 0; + + foreach (['s2c', 'c2s'] as $direction) { + $pending = $this->getPendingFiles($direction); + if (empty($pending)) { + continue; + } + + $valid = array_values(array_filter($pending, fn($p) => in_array($p, $referenced))); + $removed = count($pending) - count($valid); + + if ($removed > 0) { + $this->savePendingFiles($direction, $valid); + $pruned += $removed; + Log::info("pruneOrphanedPending: {$removed} orphaned paths removed from {$direction} pending"); + } + } + + return $pruned; + } + + private function getAllReferencedFilePaths(): array + { + $fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files']; + $paths = []; + + $rows = DB::table('patients') + ->where(function ($q) use ($fileFields) { + foreach ($fileFields as $field) { + $q->orWhereNotNull($field); + } + }) + ->get($fileFields); + + foreach ($rows as $row) { + foreach ($fileFields as $field) { + if (empty($row->$field)) { + continue; + } + $files = json_decode($row->$field, true) ?? []; + foreach ($files as $path) { + if ($path) { + $paths[] = ltrim(str_replace('\\', '/', $path), '/'); + } + } + } + } + + return array_unique($paths); + } + public function healMissingFiles(): int {