fix: retry failed file transfers on next sync using pending queue in osurg_initials

This commit is contained in:
SajjadMahmoody 2026-06-01 00:16:34 +03:30
parent 4a85e07804
commit 0e16ea6052
1 changed files with 194 additions and 139 deletions

View File

@ -118,15 +118,15 @@ public function sync(): array
} else { } else {
$report[] = 'هشدار: peer_cursor پیش نرفت — برخی تغییرات دریافتی اعمال نشد و دفعه بعد retry می‌شه.'; $report[] = 'هشدار: peer_cursor پیش نرفت — برخی تغییرات دریافتی اعمال نشد و دفعه بعد retry می‌شه.';
} }
if ($this->isFtpConfigured()) {
$this->transferFilesViaFtp($peerChanges, 's2c', $report);
} else {
$this->syncFilesViaHttp($peerChanges, $baseUrl, $report);
}
} }
$report[] = count($peerChanges) . ' تغییر از سیستم مقابل دریافت شد.'; if ($this->isFtpConfigured()) {
$this->transferFilesViaFtp($peerChanges ?? [], 's2c', $report);
} else {
$this->syncFilesViaHttp($peerChanges ?? [], $baseUrl, $report);
}
$report[] = count($peerChanges ?? []) . ' تغییر از سیستم مقابل دریافت شد.';
} else { } else {
$report[] = 'خطا در دریافت تغییرات از سیستم مقابل: ' . $pullResponse->status(); $report[] = 'خطا در دریافت تغییرات از سیستم مقابل: ' . $pullResponse->status();
} }
@ -196,20 +196,9 @@ public function applyChanges(array $changes): array
private function transferFilesViaFtp(array $changes, string $side, array &$report): void private function transferFilesViaFtp(array $changes, string $side, array &$report): void
{ {
if (empty($changes)) {
return;
}
$ftp = $this->connectFtp();
if (! $ftp) {
$report[] = 'خطا در اتصال FTP به سیستم مقابل';
Log::error('FTP connection failed in transferFilesViaFtp');
return;
}
$fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files']; $fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files'];
$localBase = rtrim(Storage::disk('public')->path(''), '/\\');
$remoteBase = rtrim($this->ftpRemotePath, '/\\');
$currentPaths = [];
foreach ($changes as $change) { foreach ($changes as $change) {
$data = $change['changed_data'] ?? []; $data = $change['changed_data'] ?? [];
foreach ($fileFields as $field) { foreach ($fileFields as $field) {
@ -218,101 +207,109 @@ private function transferFilesViaFtp(array $changes, string $side, array &$repor
} }
$files = is_array($data[$field]) ? $data[$field] : json_decode($data[$field], true) ?? []; $files = is_array($data[$field]) ? $data[$field] : json_decode($data[$field], true) ?? [];
foreach ($files as $filePath) { foreach ($files as $filePath) {
if (! $filePath) { if ($filePath) {
continue; $currentPaths[] = ltrim(str_replace('\\', '/', $filePath), '/');
}
$filePath = ltrim(str_replace('\\', '/', $filePath), '/');
$localFile = $localBase . '/' . $filePath;
$remoteFile = $remoteBase . '/' . $filePath;
$remoteFolder = dirname($remoteFile);
if ($side === 'c2s') {
if (! file_exists($localFile)) {
$errorMsg = "فایل [{$filePath}] در سیستم محلی یافت نشد";
$report[] = $errorMsg . ' ✗';
Log::warning('FTP upload skipped - local file not found', [
'file' => $filePath,
'local_path' => $localFile,
]);
continue;
}
if (! $this->createFtpDirectory($ftp, $remoteFolder)) {
$errorMsg = "ایجاد پوشه [{$remoteFolder}] برای فایل [{$filePath}] شکست خورد";
$report[] = $errorMsg . ' ✗';
Log::error('FTP mkdir failed', [
'file' => $filePath,
'remote_folder' => $remoteFolder,
]);
continue;
}
if (ftp_put($ftp, $remoteFile, $localFile, FTP_BINARY)) {
$report[] = "فایل [{$filePath}] ارسال شد (FTP) ✓";
Log::info('FTP file uploaded', [
'file' => $filePath,
'size' => filesize($localFile),
]);
} else {
$errorDetail = error_get_last();
$errorMsg = "فایل [{$filePath}] ارسال نشد (FTP)";
if ($errorDetail) {
$errorMsg .= " - " . $errorDetail['message'];
}
$report[] = $errorMsg . ' ✗';
Log::error('FTP upload failed', [
'file' => $filePath,
'local_file' => $localFile,
'remote_file' => $remoteFile,
'error' => $errorDetail,
]);
}
} else {
if (file_exists($localFile)) {
Log::debug('FTP download skipped - file already exists', [
'file' => $filePath,
]);
continue;
}
$localFolder = dirname($localFile);
if (! is_dir($localFolder)) {
if (! mkdir($localFolder, 0755, true)) {
$report[] = "ایجاد پوشه [{$localFolder}] شکست خورد ✗";
Log::error('Local mkdir failed', [
'folder' => $localFolder,
'error' => error_get_last(),
]);
continue;
}
}
if (ftp_get($ftp, $localFile, $remoteFile, FTP_BINARY)) {
$report[] = "فایل [{$filePath}] دریافت شد (FTP) ✓";
Log::info('FTP file downloaded', [
'file' => $filePath,
'size' => filesize($localFile),
]);
} else {
$errorDetail = error_get_last();
$errorMsg = "فایل [{$filePath}] دریافت نشد (FTP)";
if ($errorDetail) {
$errorMsg .= " - " . $errorDetail['message'];
}
$report[] = $errorMsg . ' ✗';
Log::error('FTP download failed', [
'file' => $filePath,
'local_file' => $localFile,
'remote_file' => $remoteFile,
'error' => $errorDetail,
]);
}
} }
} }
} }
} }
$pendingPaths = $this->getPendingFiles($side);
$allPaths = array_values(array_unique(array_merge($pendingPaths, $currentPaths)));
if (empty($allPaths)) {
return;
}
$ftp = $this->connectFtp();
if (! $ftp) {
$report[] = 'خطا در اتصال FTP به سیستم مقابل';
Log::error('FTP connection failed in transferFilesViaFtp');
return;
}
$localBase = rtrim(Storage::disk('public')->path(''), '/\\');
$remoteBase = rtrim($this->ftpRemotePath, '/\\');
$failedPaths = [];
foreach ($allPaths as $filePath) {
$localFile = $localBase . '/' . $filePath;
$remoteFile = $remoteBase . '/' . $filePath;
$remoteFolder = dirname($remoteFile);
if ($side === 'c2s') {
if (! file_exists($localFile)) {
$report[] = "فایل [{$filePath}] در سیستم محلی یافت نشد ✗";
Log::warning('FTP upload skipped - local file not found', [
'file' => $filePath,
'local_path' => $localFile,
]);
continue;
}
if (! $this->createFtpDirectory($ftp, $remoteFolder)) {
$report[] = "ایجاد پوشه [{$remoteFolder}] برای فایل [{$filePath}] شکست خورد ✗";
Log::error('FTP mkdir failed', ['file' => $filePath, 'remote_folder' => $remoteFolder]);
$failedPaths[] = $filePath;
continue;
}
if (ftp_put($ftp, $remoteFile, $localFile, FTP_BINARY)) {
$report[] = "فایل [{$filePath}] ارسال شد (FTP) ✓";
Log::info('FTP file uploaded', ['file' => $filePath, 'size' => filesize($localFile)]);
} else {
$errorDetail = error_get_last();
$errorMsg = "فایل [{$filePath}] ارسال نشد (FTP)";
if ($errorDetail) {
$errorMsg .= ' - ' . $errorDetail['message'];
}
$report[] = $errorMsg . ' ✗';
$failedPaths[] = $filePath;
Log::error('FTP upload failed', [
'file' => $filePath, 'local_file' => $localFile,
'remote_file' => $remoteFile, 'error' => $errorDetail,
]);
}
} else {
if (file_exists($localFile)) {
Log::debug('FTP download skipped - file already exists', ['file' => $filePath]);
continue;
}
$localFolder = dirname($localFile);
if (! is_dir($localFolder)) {
if (! mkdir($localFolder, 0755, true)) {
$report[] = "ایجاد پوشه [{$localFolder}] شکست خورد ✗";
$failedPaths[] = $filePath;
Log::error('Local mkdir failed', ['folder' => $localFolder, 'error' => error_get_last()]);
continue;
}
}
if (ftp_get($ftp, $localFile, $remoteFile, FTP_BINARY)) {
$report[] = "فایل [{$filePath}] دریافت شد (FTP) ✓";
Log::info('FTP file downloaded', ['file' => $filePath, 'size' => filesize($localFile)]);
} else {
$errorDetail = error_get_last();
$errorMsg = "فایل [{$filePath}] دریافت نشد (FTP)";
if ($errorDetail) {
$errorMsg .= ' - ' . $errorDetail['message'];
}
$report[] = $errorMsg . ' ✗';
$failedPaths[] = $filePath;
Log::error('FTP download failed', [
'file' => $filePath, 'local_file' => $localFile,
'remote_file' => $remoteFile, 'error' => $errorDetail,
]);
}
}
}
$this->savePendingFiles($side, $failedPaths);
if (! empty($failedPaths)) {
$report[] = count($failedPaths) . ' فایل در صف retry دفعه بعد قرار گرفت.';
}
ftp_close($ftp); ftp_close($ftp);
Log::info('FTP connection closed'); Log::info('FTP connection closed');
} }
@ -391,6 +388,7 @@ private function pushFilesViaHttp(array $changes, string $baseUrl, array &$repor
{ {
$fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files']; $fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files'];
$currentPaths = [];
foreach ($changes as $change) { foreach ($changes as $change) {
$data = $change['changed_data'] ?? []; $data = $change['changed_data'] ?? [];
foreach ($fileFields as $field) { foreach ($fileFields as $field) {
@ -399,32 +397,51 @@ private function pushFilesViaHttp(array $changes, string $baseUrl, array &$repor
} }
$files = is_array($data[$field]) ? $data[$field] : json_decode($data[$field], true) ?? []; $files = is_array($data[$field]) ? $data[$field] : json_decode($data[$field], true) ?? [];
foreach ($files as $filePath) { foreach ($files as $filePath) {
if (! $filePath || ! Storage::disk('public')->exists($filePath)) { if ($filePath) {
continue; $currentPaths[] = ltrim(str_replace('\\', '/', $filePath), '/');
}
try {
$response = Http::timeout(600)
->withHeader('X-Sync-Token', $this->token)
->attach('file', Storage::disk('public')->get($filePath), basename($filePath))
->post("{$baseUrl}/api/sync/receive-file", ['path' => $filePath]);
if ($response->successful()) {
$report[] = "فایل [{$filePath}] ارسال شد (HTTP) ✓";
} else {
$report[] = "فایل [{$filePath}] ارسال نشد (HTTP) ✗ " . $response->status();
}
} catch (\Throwable $e) {
$report[] = "فایل [{$filePath}] ارسال نشد (HTTP) ✗ " . $e->getMessage();
} }
} }
} }
} }
$pendingPaths = $this->getPendingFiles('c2s');
$allPaths = array_values(array_unique(array_merge($pendingPaths, $currentPaths)));
$failedPaths = [];
foreach ($allPaths as $filePath) {
if (! Storage::disk('public')->exists($filePath)) {
$report[] = "فایل [{$filePath}] در سیستم محلی یافت نشد ✗";
continue;
}
try {
$response = Http::timeout(600)
->withHeader('X-Sync-Token', $this->token)
->attach('file', Storage::disk('public')->get($filePath), basename($filePath))
->post("{$baseUrl}/api/sync/receive-file", ['path' => $filePath]);
if ($response->successful()) {
$report[] = "فایل [{$filePath}] ارسال شد (HTTP) ✓";
} else {
$report[] = "فایل [{$filePath}] ارسال نشد (HTTP) ✗ " . $response->status();
$failedPaths[] = $filePath;
}
} catch (\Throwable $e) {
$report[] = "فایل [{$filePath}] ارسال نشد (HTTP) ✗ " . $e->getMessage();
$failedPaths[] = $filePath;
}
}
$this->savePendingFiles('c2s', $failedPaths);
if (! empty($failedPaths)) {
$report[] = count($failedPaths) . ' فایل در صف retry دفعه بعد قرار گرفت.';
}
} }
private function syncFilesViaHttp(array $changes, string $baseUrl, array &$report): void private function syncFilesViaHttp(array $changes, string $baseUrl, array &$report): void
{ {
$fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files']; $fileFields = ['photos_before', 'photos_after', 'videos', 'audio_files'];
$currentPaths = [];
foreach ($changes as $change) { foreach ($changes as $change) {
$data = $change['changed_data'] ?? []; $data = $change['changed_data'] ?? [];
foreach ($fileFields as $field) { foreach ($fileFields as $field) {
@ -433,24 +450,43 @@ private function syncFilesViaHttp(array $changes, string $baseUrl, array &$repor
} }
$files = is_array($data[$field]) ? $data[$field] : json_decode($data[$field], true) ?? []; $files = is_array($data[$field]) ? $data[$field] : json_decode($data[$field], true) ?? [];
foreach ($files as $filePath) { foreach ($files as $filePath) {
if (! $filePath || Storage::disk('public')->exists($filePath)) { if ($filePath) {
continue; $currentPaths[] = ltrim(str_replace('\\', '/', $filePath), '/');
}
try {
$response = Http::timeout(60)
->withHeader('X-Sync-Token', $this->token)
->get("{$baseUrl}/api/sync/file", ['path' => $filePath]);
if ($response->successful()) {
Storage::disk('public')->put($filePath, $response->body());
$report[] = "فایل [{$filePath}] دریافت شد (HTTP) ✓";
}
} catch (\Throwable $e) {
$report[] = "فایل [{$filePath}] دریافت نشد (HTTP) ✗ " . $e->getMessage();
} }
} }
} }
} }
$pendingPaths = $this->getPendingFiles('s2c');
$allPaths = array_values(array_unique(array_merge($pendingPaths, $currentPaths)));
$failedPaths = [];
foreach ($allPaths as $filePath) {
if (Storage::disk('public')->exists($filePath)) {
continue;
}
try {
$response = Http::timeout(60)
->withHeader('X-Sync-Token', $this->token)
->get("{$baseUrl}/api/sync/file", ['path' => $filePath]);
if ($response->successful()) {
Storage::disk('public')->put($filePath, $response->body());
$report[] = "فایل [{$filePath}] دریافت شد (HTTP) ✓";
} else {
$report[] = "فایل [{$filePath}] دریافت نشد (HTTP) ✗ " . $response->status();
$failedPaths[] = $filePath;
}
} catch (\Throwable $e) {
$report[] = "فایل [{$filePath}] دریافت نشد (HTTP) ✗ " . $e->getMessage();
$failedPaths[] = $filePath;
}
}
$this->savePendingFiles('s2c', $failedPaths);
if (! empty($failedPaths)) {
$report[] = count($failedPaths) . ' فایل در صف retry دفعه بعد قرار گرفت.';
}
} }
private function applyChange(array $change, string $prefix, array &$report): void private function applyChange(array $change, string $prefix, array &$report): void
@ -484,6 +520,25 @@ private function applyChange(array $change, string $prefix, array &$report): voi
} }
} }
private function getPendingFiles(string $direction): array
{
$record = \App\Models\OsurgInitial::where('init_parameter', "sync_pending_{$direction}")->first();
if (! $record || ! $record->attachment) {
return [];
}
return json_decode($record->attachment, true) ?: [];
}
private function savePendingFiles(string $direction, array $files): void
{
$files = array_values(array_unique(array_filter($files)));
\App\Models\OsurgInitial::updateOrCreate(
['init_parameter' => "sync_pending_{$direction}"],
['attachment' => empty($files) ? null : json_encode($files, JSON_UNESCAPED_UNICODE)],
);
}
private function saveOurCursor(string $cursor): void private function saveOurCursor(string $cursor): void
{ {
\App\Models\OsurgInitial::updateOrCreate( \App\Models\OsurgInitial::updateOrCreate(