feat: delete
This commit is contained in:
@@ -6,6 +6,8 @@ use App\Services\CorunaArchive;
|
|||||||
use App\Services\CorunaCrypto;
|
use App\Services\CorunaCrypto;
|
||||||
use App\Services\SettingsService;
|
use App\Services\SettingsService;
|
||||||
use App\Telegram\TelegramBotContext;
|
use App\Telegram\TelegramBotContext;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Nutgram\Laravel\RunningMode\LaravelWebhook;
|
use Nutgram\Laravel\RunningMode\LaravelWebhook;
|
||||||
use SergiX44\Nutgram\Nutgram;
|
use SergiX44\Nutgram\Nutgram;
|
||||||
@@ -44,6 +46,10 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
// settings table may not exist yet during migrate
|
// settings table may not exist yet during migrate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $this->app->runningUnitTests()) {
|
||||||
|
$this->recordLastSql();
|
||||||
|
}
|
||||||
|
|
||||||
// Override Nutgram's production safe_mode (md5 APP_KEY) so webhook updates
|
// Override Nutgram's production safe_mode (md5 APP_KEY) so webhook updates
|
||||||
// are not silently dropped when Telegram secret_token is unset/mismatched.
|
// are not silently dropped when Telegram secret_token is unset/mismatched.
|
||||||
$this->app->afterResolving(Nutgram::class, function (Nutgram $bot): void {
|
$this->app->afterResolving(Nutgram::class, function (Nutgram $bot): void {
|
||||||
@@ -62,4 +68,45 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
$bot->setRunningMode($webhook);
|
$bot->setRunningMode($webhook);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrite last-sql.json before each query so an OOM still leaves the dying SQL.
|
||||||
|
*/
|
||||||
|
private function recordLastSql(): void
|
||||||
|
{
|
||||||
|
$path = storage_path('logs/last-sql.json');
|
||||||
|
DB::beforeExecuting(function (string $query, array $bindings) use ($path): void {
|
||||||
|
$uri = 'cli';
|
||||||
|
try {
|
||||||
|
if (app()->runningInConsole()) {
|
||||||
|
$uri = 'cli:'.implode(' ', $_SERVER['argv'] ?? []);
|
||||||
|
} else {
|
||||||
|
$req = request();
|
||||||
|
$uri = $req->method().' '.$req->getRequestUri();
|
||||||
|
}
|
||||||
|
} catch (\Throwable) {
|
||||||
|
}
|
||||||
|
@file_put_contents($path, json_encode([
|
||||||
|
'at' => date('c'),
|
||||||
|
'mem' => memory_get_usage(true),
|
||||||
|
'uri' => $uri,
|
||||||
|
'sql' => substr($query, 0, 2000),
|
||||||
|
'bindings' => array_slice($bindings, 0, 12),
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
|
});
|
||||||
|
|
||||||
|
DB::listen(function ($event) use ($path): void {
|
||||||
|
if ($event->time < 500 && memory_get_usage(true) < 64 * 1024 * 1024) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$raw = @file_get_contents($path);
|
||||||
|
$last = is_string($raw) ? json_decode($raw, true) : null;
|
||||||
|
Log::warning('slow_or_heavy_query', [
|
||||||
|
'ms' => $event->time,
|
||||||
|
'mem' => memory_get_usage(true),
|
||||||
|
'uri' => is_array($last) ? ($last['uri'] ?? '') : '',
|
||||||
|
'sql' => substr((string) $event->sql, 0, 500),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,20 +67,110 @@ class CorunaArchive
|
|||||||
return $repaired;
|
return $repaired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch an on-disk archive header without loading the file body.
|
||||||
|
*/
|
||||||
|
private function patchCorunaHeaderFile(string $path): void
|
||||||
|
{
|
||||||
|
$size = filesize($path);
|
||||||
|
if (! is_int($size) || $size < 32) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$fh = fopen($path, 'r+b');
|
||||||
|
if (! is_resource($fh)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$head = fread($fh, 32);
|
||||||
|
if (! is_string($head) || strlen($head) < 32 || str_starts_with($head, self::STANDARD_PREFIX)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (! $this->isCorunaHeader($head)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$nextHeaderOffset = unpack('P', substr($head, 0, 8))[1] ^ self::HEADER_XOR;
|
||||||
|
$nextHeaderSize = unpack('P', substr($head, 8, 8))[1] ^ self::HEADER_XOR;
|
||||||
|
$nextHeaderStart = 32 + $nextHeaderOffset;
|
||||||
|
if ($nextHeaderSize <= 0 || $nextHeaderSize > 8_000_000
|
||||||
|
|| $nextHeaderStart + $nextHeaderSize > $size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fseek($fh, $nextHeaderStart) !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$nextHeader = fread($fh, (int) $nextHeaderSize);
|
||||||
|
if (! is_string($nextHeader) || strlen($nextHeader) !== (int) $nextHeaderSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$patched = substr_replace($head, self::STANDARD_PREFIX."\x00\x04", 0, 8);
|
||||||
|
$patched = substr_replace($patched, pack('P', $nextHeaderOffset), 12, 8);
|
||||||
|
$patched = substr_replace($patched, pack('P', $nextHeaderSize), 20, 8);
|
||||||
|
$nextCrc = crc32($nextHeader) & 0xFFFFFFFF;
|
||||||
|
$patched = substr_replace($patched, pack('V', $nextCrc), 28, 4);
|
||||||
|
$startCrc = crc32(substr($patched, 12, 20)) & 0xFFFFFFFF;
|
||||||
|
$patched = substr_replace($patched, pack('V', $startCrc), 8, 4);
|
||||||
|
if (fseek($fh, 0) !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fwrite($fh, $patched);
|
||||||
|
} finally {
|
||||||
|
fclose($fh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function extract(string $wireData, string $destDir, string $batchBase = '0'): array
|
public function extract(string $wireData, string $destDir, string $batchBase = '0'): array
|
||||||
{
|
{
|
||||||
if (! is_dir($destDir)) {
|
if (! is_dir($destDir)) {
|
||||||
mkdir($destDir, 0755, true);
|
mkdir($destDir, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$archive = $destDir.'/capture.7z';
|
||||||
|
file_put_contents($archive, $wireData);
|
||||||
try {
|
try {
|
||||||
$repaired = $this->repairHeader($wireData);
|
$this->patchCorunaHeaderFile($archive);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable) {
|
||||||
$repaired = $wireData;
|
}
|
||||||
|
|
||||||
|
return $this->runSevenZip($archive, $destDir, $batchBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract from a path without loading the whole archive into PHP.
|
||||||
|
*/
|
||||||
|
public function extractPath(string $srcPath, string $destDir, string $batchBase = '0'): array
|
||||||
|
{
|
||||||
|
if (! is_file($srcPath)) {
|
||||||
|
throw new RuntimeException('archive missing');
|
||||||
|
}
|
||||||
|
if (! is_dir($destDir)) {
|
||||||
|
mkdir($destDir, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$archive = $destDir.'/capture.7z';
|
$archive = $destDir.'/capture.7z';
|
||||||
file_put_contents($archive, $repaired);
|
$in = fopen($srcPath, 'rb');
|
||||||
|
$out = fopen($archive, 'wb');
|
||||||
|
if (! is_resource($in) || ! is_resource($out)) {
|
||||||
|
throw new RuntimeException('cannot copy archive');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
stream_copy_to_stream($in, $out);
|
||||||
|
} finally {
|
||||||
|
fclose($in);
|
||||||
|
fclose($out);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$this->patchCorunaHeaderFile($archive);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->runSevenZip($archive, $destDir, $batchBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{ok: bool, stderr: string, files: list<string>, password_recipe: string}
|
||||||
|
*/
|
||||||
|
private function runSevenZip(string $archive, string $destDir, string $batchBase): array
|
||||||
|
{
|
||||||
$password = $this->crypto->archivePassword($batchBase);
|
$password = $this->crypto->archivePassword($batchBase);
|
||||||
$membersDir = $destDir.'/members';
|
$membersDir = $destDir.'/members';
|
||||||
@mkdir($membersDir, 0755, true);
|
@mkdir($membersDir, 0755, true);
|
||||||
|
|||||||
@@ -98,10 +98,9 @@ class PhotoArchiveIngest
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$bytes = (string) file_get_contents($abs);
|
|
||||||
$work = storage_path('app/c2/check/'.$device->device_id.'/'.date('YmdHis').'_'.uniqid());
|
$work = storage_path('app/c2/check/'.$device->device_id.'/'.date('YmdHis').'_'.uniqid());
|
||||||
try {
|
try {
|
||||||
$extracted = $this->archiveFor($flavor)->extract($bytes, $work, $batchBase);
|
$extracted = $this->archiveFor($flavor)->extractPath($abs, $work, $batchBase);
|
||||||
if (! empty($extracted['files'])) {
|
if (! empty($extracted['files'])) {
|
||||||
$this->ingest->ingestPhotos($device, $extracted['files'], $photoMeta);
|
$this->ingest->ingestPhotos($device, $extracted['files'], $photoMeta);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user