pixelfed/pixelfed

View on GitHub
app/Console/Commands/AvatarStorageDeepClean.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Cache;
use Storage;
use App\Avatar;
use App\Jobs\AvatarPipeline\AvatarStorageCleanup;

class AvatarStorageDeepClean extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'avatar:storage-deep-clean';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Cleanup avatar storage';

    protected $shouldKeepRunning = true;
    protected $counter = 0;

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        $this->info('       ____  _           ______         __  ');
        $this->info('      / __ \(_)  _____  / / __/__  ____/ /  ');
        $this->info('     / /_/ / / |/_/ _ \/ / /_/ _ \/ __  /   ');
        $this->info('    / ____/ />  </  __/ / __/  __/ /_/ /    ');
        $this->info('   /_/   /_/_/|_|\___/_/_/  \___/\__,_/     ');
        $this->info(' ');
        $this->info('    Pixelfed Avatar Deep Cleaner');
        $this->line(' ');
        $this->info('    Purge/delete old and outdated avatars from remote accounts');
        $this->line(' ');

        $storage = [
            'cloud' => (bool) config_cache('pixelfed.cloud_storage'),
            'local' => boolval(config_cache('federation.avatars.store_local'))
        ];

        if(!$storage['cloud'] && !$storage['local']) {
            $this->error('Remote avatars are not cached locally, there is nothing to purge. Aborting...');
            exit;
        }

        $start = 0;

        if(!$this->confirm('Are you sure you want to proceed?')) {
            $this->error('Aborting...');
            exit;
        }

        if(!$this->activeCheck()) {
            $this->info('Found existing deep cleaning job');
            if(!$this->confirm('Do you want to continue where you left off?')) {
                $this->error('Aborting...');
                exit;
            } else {
                $start = Cache::has('cmd:asdp') ? (int) Cache::get('cmd:asdp') : (int) Storage::get('avatar-deep-clean.json');

                if($start && $start < 1 || $start > PHP_INT_MAX) {
                    $this->error('Error fetching cached value');
                    $this->error('Aborting...');
                    exit;
                }
            }
        }

        $count = Avatar::whereNotNull('cdn_url')->where('is_remote', true)->where('id', '>', $start)->count();
        $bar = $this->output->createProgressBar($count);

        foreach(Avatar::whereNotNull('cdn_url')->where('is_remote', true)->where('id', '>', $start)->lazyById(10, 'id') as $avatar) {
            usleep(random_int(50, 1000));
            $this->counter++;
            $this->handleAvatar($avatar);
            $bar->advance();
        }
        $bar->finish();
    }

    protected function updateCache($id)
    {
        Cache::put('cmd:asdp', $id);
        if($this->counter % 5 === 0) {
            Storage::put('avatar-deep-clean.json', $id);
        }
    }

    protected function activeCheck()
    {
        if(Storage::exists('avatar-deep-clean.json') || Cache::has('cmd:asdp')) {
            return false;
        }

        return true;
    }

    protected function handleAvatar($avatar)
    {
        $this->updateCache($avatar->id);
        $queues = ['feed', 'mmo', 'feed', 'mmo', 'feed', 'feed', 'mmo', 'low'];
        $queue = $queues[random_int(0, 7)];
        AvatarStorageCleanup::dispatch($avatar)->onQueue($queue);
    }
}