fix: db
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Services;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\Photo;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class DsResultStore
|
||||
@@ -14,6 +15,10 @@ class DsResultStore
|
||||
/** @var list<string> */
|
||||
private const IMAGE_EXT = ['png', 'jpg', 'jpeg', 'heic', 'heif', 'gif', 'webp', 'bmp', 'tif', 'tiff'];
|
||||
|
||||
private const MAX_CHUNKS = 512;
|
||||
|
||||
private const MAX_ASSEMBLED_BYTES = 80 * 1024 * 1024;
|
||||
|
||||
public function __construct(
|
||||
private readonly IngestService $ingest,
|
||||
) {}
|
||||
@@ -49,8 +54,11 @@ class DsResultStore
|
||||
$index = array_key_exists('chunk_index', $payload) ? (int) $payload['chunk_index'] : null;
|
||||
|
||||
if ($total > 1 && $index !== null) {
|
||||
$assembled = $this->acceptChunk($device, $commandId, $filename, $index, $total, $bytes);
|
||||
if ($assembled === null) {
|
||||
if ($total > self::MAX_CHUNKS || $index < 0 || $index >= $total) {
|
||||
return ['stored' => false, 'reason' => 'bad_chunks', 'filename' => $filename];
|
||||
}
|
||||
$assembledPath = $this->acceptChunk($device, $commandId, $filename, $index, $total, $bytes);
|
||||
if ($assembledPath === null) {
|
||||
return [
|
||||
'stored' => false,
|
||||
'reason' => 'chunk_pending',
|
||||
@@ -59,39 +67,76 @@ class DsResultStore
|
||||
'total_chunks' => $total,
|
||||
];
|
||||
}
|
||||
$bytes = $assembled;
|
||||
if ($assembledPath === false) {
|
||||
return ['stored' => false, 'reason' => 'too_large', 'filename' => $filename];
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->persistFile($device, $filename, $commandId, $assembledPath);
|
||||
} finally {
|
||||
@unlink($assembledPath);
|
||||
}
|
||||
}
|
||||
|
||||
$hash = hash('sha256', $bytes);
|
||||
return $this->persistBytes($device, $filename, $commandId, $bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function persistBytes(Device $device, string $filename, string $commandId, string $bytes): array
|
||||
{
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'ds_photo_');
|
||||
if ($tmp === false) {
|
||||
return ['stored' => false, 'reason' => 'tmp_failed', 'filename' => $filename];
|
||||
}
|
||||
file_put_contents($tmp, $bytes);
|
||||
|
||||
try {
|
||||
return $this->persistFile($device, $filename, $commandId, $tmp);
|
||||
} finally {
|
||||
@unlink($tmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function persistFile(Device $device, string $filename, string $commandId, string $absPath): array
|
||||
{
|
||||
$hash = hash_file('sha256', $absPath) ?: '';
|
||||
$size = (int) filesize($absPath);
|
||||
if ($this->alreadyIngested($device, $filename, $hash)) {
|
||||
return [
|
||||
'stored' => false,
|
||||
'reason' => 'duplicate',
|
||||
'filename' => $filename,
|
||||
'size' => strlen($bytes),
|
||||
'size' => $size,
|
||||
];
|
||||
}
|
||||
|
||||
$rel = 'c2/ds-results/'.$device->device_id.'/'.$commandId.'/'.$filename;
|
||||
Storage::disk('local')->put($rel, $bytes);
|
||||
$stream = fopen($absPath, 'rb');
|
||||
if ($stream === false) {
|
||||
return ['stored' => false, 'reason' => 'read_failed', 'filename' => $filename];
|
||||
}
|
||||
Storage::disk('local')->put($rel, $stream);
|
||||
if (is_resource($stream)) {
|
||||
fclose($stream);
|
||||
}
|
||||
$this->markSeen($device, $hash, $filename);
|
||||
|
||||
$photo = false;
|
||||
if ($this->isImage($filename) && $device->albumStorageEnabled()) {
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'ds_photo_');
|
||||
if ($tmp !== false) {
|
||||
file_put_contents($tmp, $bytes);
|
||||
$this->ingest->ingestPhotos($device, [$tmp]);
|
||||
@unlink($tmp);
|
||||
$photo = true;
|
||||
}
|
||||
$this->ingest->ingestPhotos($device, [$absPath]);
|
||||
$photo = true;
|
||||
}
|
||||
|
||||
return [
|
||||
'stored' => true,
|
||||
'path' => $rel,
|
||||
'photo' => $photo,
|
||||
'size' => strlen($bytes),
|
||||
'size' => $size,
|
||||
'filename' => $filename,
|
||||
];
|
||||
}
|
||||
@@ -140,6 +185,9 @@ class DsResultStore
|
||||
return 'c2/ds-results/'.$device->device_id.'/.seen/'.$hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream chunks to a temp file. null = still waiting, false = over size / I/O fail.
|
||||
*/
|
||||
private function acceptChunk(
|
||||
Device $device,
|
||||
string $commandId,
|
||||
@@ -147,7 +195,7 @@ class DsResultStore
|
||||
int $index,
|
||||
int $total,
|
||||
string $bytes,
|
||||
): ?string {
|
||||
): string|false|null {
|
||||
$dir = 'c2/ds-chunks/'.$device->device_id.'/'.$commandId.'/'.$filename;
|
||||
Storage::disk('local')->put($dir.'/'.$index, $bytes);
|
||||
Storage::disk('local')->put($dir.'/total', (string) $total);
|
||||
@@ -158,13 +206,45 @@ class DsResultStore
|
||||
}
|
||||
}
|
||||
|
||||
$out = '';
|
||||
for ($i = 0; $i < $total; $i++) {
|
||||
$out .= (string) Storage::disk('local')->get($dir.'/'.$i);
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'ds_asm_');
|
||||
if ($tmp === false) {
|
||||
Storage::disk('local')->deleteDirectory($dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
$fh = fopen($tmp, 'wb');
|
||||
if ($fh === false) {
|
||||
@unlink($tmp);
|
||||
Storage::disk('local')->deleteDirectory($dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$written = 0;
|
||||
for ($i = 0; $i < $total; $i++) {
|
||||
$chunk = (string) Storage::disk('local')->get($dir.'/'.$i);
|
||||
$len = strlen($chunk);
|
||||
if ($written + $len > self::MAX_ASSEMBLED_BYTES) {
|
||||
unset($chunk);
|
||||
fclose($fh);
|
||||
@unlink($tmp);
|
||||
Storage::disk('local')->deleteDirectory($dir);
|
||||
Log::warning('ds result assembled too large', [
|
||||
'device_id' => $device->device_id,
|
||||
'filename' => $filename,
|
||||
'total_chunks' => $total,
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
fwrite($fh, $chunk);
|
||||
$written += $len;
|
||||
unset($chunk);
|
||||
}
|
||||
fclose($fh);
|
||||
Storage::disk('local')->deleteDirectory($dir);
|
||||
|
||||
return $out;
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
private function isVideo(string $filename): bool
|
||||
|
||||
Reference in New Issue
Block a user