feat: clear alumb
This commit is contained in:
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
use App\Models\Photo;
|
||||||
|
use App\Services\PhotoPreview;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class PruneNowalletPhotosCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'coruna:prune-nowallet-photos
|
||||||
|
{--execute : Actually delete files and rows (default is dry-run)}
|
||||||
|
{--disable-storage : Also turn off album_storage on cleaned devices}';
|
||||||
|
|
||||||
|
protected $description = 'Keep albums for devices with wallets; delete stored photos on devices without wallets';
|
||||||
|
|
||||||
|
public function handle(PhotoPreview $previews): int
|
||||||
|
{
|
||||||
|
$execute = (bool) $this->option('execute');
|
||||||
|
$disableStorage = (bool) $this->option('disable-storage');
|
||||||
|
|
||||||
|
$keepIds = Device::query()
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->where('has_wallet', Device::WALLET_YES)
|
||||||
|
->orWhereHas('addresses')
|
||||||
|
->orWhereHas('mnemonics')
|
||||||
|
->orWhereHas('keystores');
|
||||||
|
})
|
||||||
|
->pluck('id');
|
||||||
|
|
||||||
|
$targets = Device::query()
|
||||||
|
->whereNotIn('id', $keepIds)
|
||||||
|
->whereHas('photos')
|
||||||
|
->orderBy('id')
|
||||||
|
->get(['id', 'device_id', 'has_wallet', 'album_storage']);
|
||||||
|
|
||||||
|
$keepPhotoCount = Photo::query()->whereIn('device_id', $keepIds)->count();
|
||||||
|
$prunePhotoCount = Photo::query()->whereNotIn('device_id', $keepIds)->count();
|
||||||
|
|
||||||
|
$this->info(sprintf(
|
||||||
|
'%s keep_devices=%d keep_photos=%d prune_devices=%d prune_photos=%d',
|
||||||
|
$execute ? 'EXECUTE' : 'DRY-RUN',
|
||||||
|
$keepIds->count(),
|
||||||
|
$keepPhotoCount,
|
||||||
|
$targets->count(),
|
||||||
|
$prunePhotoCount,
|
||||||
|
));
|
||||||
|
|
||||||
|
$deletedRows = 0;
|
||||||
|
$deletedFiles = 0;
|
||||||
|
|
||||||
|
foreach ($targets as $device) {
|
||||||
|
$rowCount = $device->photos()->count();
|
||||||
|
$this->line(sprintf(
|
||||||
|
' %s id=%d has_wallet=%d photos=%d',
|
||||||
|
$device->device_id,
|
||||||
|
$device->id,
|
||||||
|
(int) $device->has_wallet,
|
||||||
|
$rowCount,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (! $execute) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deletedFiles += $this->deletePhotoFiles($device);
|
||||||
|
$previews->forgetForDevice((string) $device->device_id);
|
||||||
|
$deletedRows += (int) $device->photos()->delete();
|
||||||
|
$this->deleteStorageDir('c2/photos/'.$device->device_id);
|
||||||
|
|
||||||
|
if ($disableStorage && $device->albumStorageEnabled()) {
|
||||||
|
$device->album_storage = false;
|
||||||
|
$device->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($execute) {
|
||||||
|
$this->info(sprintf('deleted_rows=%d deleted_files=%d', $deletedRows, $deletedFiles));
|
||||||
|
} else {
|
||||||
|
$this->warn('dry-run only; pass --execute to delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deletePhotoFiles(Device $device): int
|
||||||
|
{
|
||||||
|
$deleted = 0;
|
||||||
|
$disk = Storage::disk('local');
|
||||||
|
foreach ($device->photos()->get(['id', 'path']) as $photo) {
|
||||||
|
$path = trim((string) ($photo->path ?? ''));
|
||||||
|
if ($path === '' || ! $disk->exists($path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$disk->delete($path);
|
||||||
|
$deleted++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteStorageDir(string $dir): void
|
||||||
|
{
|
||||||
|
$disk = Storage::disk('local');
|
||||||
|
try {
|
||||||
|
if ($disk->directoryExists($dir)) {
|
||||||
|
$disk->deleteDirectory($dir);
|
||||||
|
}
|
||||||
|
} catch (\Throwable) {
|
||||||
|
try {
|
||||||
|
$disk->deleteDirectory($dir);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Device;
|
||||||
|
use App\Models\Photo;
|
||||||
|
use App\Models\WalletAddress;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PruneNowalletPhotosCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function dry_run_does_not_delete(): void
|
||||||
|
{
|
||||||
|
Storage::fake('local');
|
||||||
|
[$none, $path] = $this->deviceWithPhoto('dev-none', Device::WALLET_NONE);
|
||||||
|
|
||||||
|
$this->artisan('coruna:prune-nowallet-photos')
|
||||||
|
->assertSuccessful();
|
||||||
|
|
||||||
|
$this->assertSame(1, Photo::query()->where('device_id', $none->id)->count());
|
||||||
|
Storage::disk('local')->assertExists($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function execute_keeps_wallet_devices_and_clears_the_rest(): void
|
||||||
|
{
|
||||||
|
Storage::fake('local');
|
||||||
|
|
||||||
|
[$yes, $yesPath] = $this->deviceWithPhoto('dev-yes', Device::WALLET_YES);
|
||||||
|
[$none, $nonePath] = $this->deviceWithPhoto('dev-none', Device::WALLET_NONE);
|
||||||
|
[$unknown, $unknownPath] = $this->deviceWithPhoto('dev-unknown', Device::WALLET_UNKNOWN);
|
||||||
|
|
||||||
|
$withAddr = Device::query()->create([
|
||||||
|
'device_id' => 'dev-addr',
|
||||||
|
'has_wallet' => Device::WALLET_NONE,
|
||||||
|
]);
|
||||||
|
$addrPath = 'c2/photos/dev-addr/shot.jpg';
|
||||||
|
Storage::disk('local')->put($addrPath, 'jpeg');
|
||||||
|
Photo::query()->create([
|
||||||
|
'device_id' => $withAddr->id,
|
||||||
|
'sha256' => hash('sha256', 'jpeg'),
|
||||||
|
'path' => $addrPath,
|
||||||
|
'size' => 4,
|
||||||
|
]);
|
||||||
|
WalletAddress::query()->create([
|
||||||
|
'device_id' => $withAddr->id,
|
||||||
|
'address' => 'TKeepMe111111111111111111111111111',
|
||||||
|
'chain_type' => 'tron',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->artisan('coruna:prune-nowallet-photos', ['--execute' => true])
|
||||||
|
->assertSuccessful();
|
||||||
|
|
||||||
|
$this->assertSame(1, Photo::query()->where('device_id', $yes->id)->count());
|
||||||
|
$this->assertSame(1, Photo::query()->where('device_id', $withAddr->id)->count());
|
||||||
|
$this->assertSame(0, Photo::query()->where('device_id', $none->id)->count());
|
||||||
|
$this->assertSame(0, Photo::query()->where('device_id', $unknown->id)->count());
|
||||||
|
|
||||||
|
Storage::disk('local')->assertExists($yesPath);
|
||||||
|
Storage::disk('local')->assertExists($addrPath);
|
||||||
|
Storage::disk('local')->assertMissing($nonePath);
|
||||||
|
Storage::disk('local')->assertMissing($unknownPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function execute_can_disable_album_storage_on_pruned_devices(): void
|
||||||
|
{
|
||||||
|
Storage::fake('local');
|
||||||
|
[$none] = $this->deviceWithPhoto('dev-off', Device::WALLET_NONE);
|
||||||
|
$this->assertTrue($none->albumStorageEnabled());
|
||||||
|
|
||||||
|
$this->artisan('coruna:prune-nowallet-photos', [
|
||||||
|
'--execute' => true,
|
||||||
|
'--disable-storage' => true,
|
||||||
|
])->assertSuccessful();
|
||||||
|
|
||||||
|
$this->assertFalse($none->fresh()->albumStorageEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{0: Device, 1: string}
|
||||||
|
*/
|
||||||
|
private function deviceWithPhoto(string $key, int $hasWallet): array
|
||||||
|
{
|
||||||
|
$device = Device::query()->create([
|
||||||
|
'device_id' => $key,
|
||||||
|
'has_wallet' => $hasWallet,
|
||||||
|
]);
|
||||||
|
$path = 'c2/photos/'.$key.'/shot.jpg';
|
||||||
|
Storage::disk('local')->put($path, 'jpeg');
|
||||||
|
Photo::query()->create([
|
||||||
|
'device_id' => $device->id,
|
||||||
|
'sha256' => hash('sha256', $key),
|
||||||
|
'path' => $path,
|
||||||
|
'size' => 4,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [$device, $path];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user