Files
lis/htdocs/app/Console/Commands/BackupUploadFileCommand.php
T
2026-08-02 05:31:32 +07:00

146 lines
5.2 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class BackupUploadFileCommand extends Command
{
protected $signature = 'backup:upload-file
{file : Path file .backup yang akan dikirim}
{--url= : Base URL server cloud backup}
{--token= : Token API cloud backup}
{--name= : Nama backup}
{--chunk-size= : Ukuran chunk dalam byte}
{--retries=5 : Jumlah percobaan ulang setiap chunk}
{--retry-delay=10 : Jeda percobaan ulang dalam detik}
{--timeout=300 : Timeout request upload dalam detik}';
protected $description = 'Upload file backup ke duidev.com secara bertahap/chunked.';
public function handle(): int
{
$file = (string) $this->argument('file');
if (!is_file($file) || !is_readable($file)) {
$this->error('File backup tidak ditemukan atau tidak bisa dibaca: ' . $file);
return Command::FAILURE;
}
$baseUrl = rtrim((string) ($this->option('url') ?: config('cloud_backup.api_url')), '/');
$token = (string) ($this->option('token') ?: config('cloud_backup.api_token'));
$backupName = (string) ($this->option('name') ?: config('cloud_backup.backup_name', 'lismikro'));
$chunkSize = (int) ($this->option('chunk-size') ?: config('cloud_backup.chunk_size', 5 * 1024 * 1024));
$retries = max(1, (int) $this->option('retries'));
$retryDelay = max(0, (int) $this->option('retry-delay'));
$timeout = max(30, (int) $this->option('timeout'));
if ($baseUrl === '' || $token === '') {
$this->error('CLOUD_BACKUP_API_URL dan CLOUD_BACKUP_API_TOKEN wajib diisi.');
return Command::FAILURE;
}
if ($chunkSize < 1024 * 1024) {
$this->error('Chunk size minimal 1 MB.');
return Command::FAILURE;
}
$size = filesize($file);
$checksum = hash_file('sha256', $file);
$totalChunks = (int) ceil($size / $chunkSize);
$uploadId = now()->format('YmdHis') . '_' . Str::random(16);
$endpoint = $baseUrl . '/api/backups/chunk';
$this->info('Upload backup: ' . basename($file));
$this->info('Upload ID: ' . $uploadId);
$this->info('Total chunk: ' . $totalChunks);
$handle = fopen($file, 'rb');
if (!$handle) {
$this->error('Gagal membuka file backup.');
return Command::FAILURE;
}
try {
for ($index = 0; $index < $totalChunks; $index++) {
$chunk = fread($handle, $chunkSize);
if ($chunk === false) {
$this->error('Gagal membaca chunk ke-' . $index);
return Command::FAILURE;
}
$response = $this->uploadChunk(
endpoint: $endpoint,
token: $token,
chunk: $chunk,
chunkIndex: $index,
payload: [
'backup_name' => $backupName,
'upload_id' => $uploadId,
'filename' => basename($file),
'chunk_index' => $index,
'total_chunks' => $totalChunks,
'total_size' => $size,
'checksum' => $checksum,
],
retries: $retries,
retryDelay: $retryDelay,
timeout: $timeout
);
if (!$response || !$response->successful()) {
$this->error('Upload chunk ke-' . $index . ' gagal. HTTP ' . ($response?->status() ?: '-'));
$this->line($response?->body() ?: 'Tidak ada response dari server.');
return Command::FAILURE;
}
$this->line('Chunk ' . ($index + 1) . '/' . $totalChunks . ' terkirim.');
}
} finally {
fclose($handle);
}
$this->info('Upload backup selesai.');
return Command::SUCCESS;
}
private function uploadChunk(
string $endpoint,
string $token,
string $chunk,
int $chunkIndex,
array $payload,
int $retries,
int $retryDelay,
int $timeout
): ?Response {
$response = null;
for ($attempt = 1; $attempt <= $retries; $attempt++) {
try {
$response = Http::withToken($token)
->timeout($timeout)
->attach('chunk', $chunk, 'chunk_' . $chunkIndex . '.part')
->post($endpoint, $payload);
if ($response->successful()) {
return $response;
}
$this->warn('Chunk ' . ($chunkIndex + 1) . ' gagal percobaan ' . $attempt . '/' . $retries . '. HTTP ' . $response->status());
} catch (\Throwable $e) {
$this->warn('Chunk ' . ($chunkIndex + 1) . ' gagal percobaan ' . $attempt . '/' . $retries . ': ' . $e->getMessage());
}
if ($attempt < $retries && $retryDelay > 0) {
sleep($retryDelay);
}
}
return $response;
}
}