123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- <?php
- namespace App\Console\Commands;
- use Illuminate\Console\Command;
- use App\Avatar;
- use App\Profile;
- use App\User;
- use Cache;
- use Storage;
- use App\Services\AccountService;
- use App\Util\Lexer\PrettyNumber;
- use Illuminate\Support\Str;
- use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
- class AvatarStorage extends Command
- {
- /**
- * The name and signature of the console command.
- *
- * @var string
- */
- protected $signature = 'avatar:storage';
- /**
- * The console command description.
- *
- * @var string
- */
- protected $description = 'Manage avatar storage';
- public $found = 0;
- public $notFetched = 0;
- public $fixed = 0;
- public $missing = 0;
- /**
- * Execute the console command.
- *
- * @return int
- */
- public function handle()
- {
- $this->info('Pixelfed Avatar Storage Manager');
- $this->line(' ');
- $segments = [
- [
- 'Local',
- Avatar::whereNull('is_remote')->count(),
- PrettyNumber::size(Avatar::whereNull('is_remote')->sum('size'))
- ],
- [
- 'Remote',
- Avatar::whereIsRemote(true)->count(),
- PrettyNumber::size(Avatar::whereIsRemote(true)->sum('size'))
- ],
- [
- 'Cached (CDN)',
- Avatar::whereNotNull('cdn_url')->count(),
- PrettyNumber::size(Avatar::whereNotNull('cdn_url')->sum('size'))
- ],
- [
- 'Uncached',
- Avatar::whereNull('cdn_url')->count(),
- PrettyNumber::size(Avatar::whereNull('cdn_url')->sum('size'))
- ],
- [
- '------------',
- '----------',
- '-----'
- ],
- [
- 'Total',
- Avatar::count(),
- PrettyNumber::size(Avatar::sum('size'))
- ],
- ];
- $this->table(
- ['Segment', 'Count', 'Space Used'],
- $segments
- );
- $this->line(' ');
- if((bool) config_cache('pixelfed.cloud_storage')) {
- $this->info('✅ - Cloud storage configured');
- $this->line(' ');
- }
- if(config('instance.avatar.local_to_cloud')) {
- $this->info('✅ - Store avatars on cloud filesystem');
- $this->line(' ');
- }
- if((bool) config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud')) {
- $disk = Storage::disk(config_cache('filesystems.cloud'));
- $exists = $disk->exists('cache/avatars/default.jpg');
- $state = $exists ? '✅' : '❌';
- $msg = $state . ' - Cloud default avatar exists';
- $this->info($msg);
- }
- $options = (bool) config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud') ?
- [
- 'Cancel',
- 'Upload default avatar to cloud',
- 'Move local avatars to cloud',
- 'Re-fetch remote avatars'
- ] : [
- 'Cancel',
- 'Re-fetch remote avatars'
- ];
- $this->missing = Profile::where('created_at', '<', now()->subDays(1))->doesntHave('avatar')->count();
- if($this->missing != 0) {
- $options[] = 'Fix missing avatars';
- }
- $choice = $this->choice(
- 'Select action:',
- $options,
- 0
- );
- return $this->handleChoice($choice);
- }
- protected function handleChoice($id)
- {
- switch ($id) {
- case 'Cancel':
- return;
- break;
- case 'Upload default avatar to cloud':
- return $this->uploadDefaultAvatar();
- break;
- case 'Move local avatars to cloud':
- return $this->uploadAvatarsToCloud();
- break;
- case 'Re-fetch remote avatars':
- return $this->refetchRemoteAvatars();
- break;
- case 'Fix missing avatars':
- return $this->fixMissingAvatars();
- break;
- }
- }
- protected function uploadDefaultAvatar()
- {
- if(!$this->confirm('Are you sure you want to upload the default avatar to the cloud storage disk?')) {
- return;
- }
- $disk = Storage::disk(config_cache('filesystems.cloud'));
- $disk->put('cache/avatars/default.jpg', Storage::get('public/avatars/default.jpg'));
- Avatar::whereMediaPath('public/avatars/default.jpg')->update(['cdn_url' => $disk->url('cache/avatars/default.jpg')]);
- $this->info('Successfully uploaded default avatar to cloud storage!');
- $this->info($disk->url('cache/avatars/default.jpg'));
- }
- protected function uploadAvatarsToCloud()
- {
- if(!(bool) config_cache('pixelfed.cloud_storage') || !config('instance.avatar.local_to_cloud')) {
- $this->error('Enable cloud storage and avatar cloud storage to perform this action');
- return;
- }
- $confirm = $this->confirm('Are you sure you want to move local avatars to cloud storage?');
- if(!$confirm) {
- $this->error('Aborted action');
- return;
- }
- $disk = Storage::disk(config_cache('filesystems.cloud'));
- if($disk->missing('cache/avatars/default.jpg')) {
- $disk->put('cache/avatars/default.jpg', Storage::get('public/avatars/default.jpg'));
- }
- Avatar::whereNull('is_remote')->chunk(5, function($avatars) use($disk) {
- foreach($avatars as $avatar) {
- if($avatar->media_path === 'public/avatars/default.jpg') {
- $avatar->cdn_url = $disk->url('cache/avatars/default.jpg');
- $avatar->save();
- } else {
- if(!$avatar->media_path || !Str::of($avatar->media_path)->startsWith('public/avatars/')) {
- continue;
- }
- $ext = pathinfo($avatar->media_path, PATHINFO_EXTENSION);
- $newPath = 'cache/avatars/' . $avatar->profile_id . '/avatar_' . strtolower(Str::random(6)) . '.' . $ext;
- $existing = Storage::disk('local')->get($avatar->media_path);
- if(!$existing) {
- continue;
- }
- $newMediaPath = $disk->put($newPath, $existing);
- $avatar->media_path = $newPath;
- $avatar->cdn_url = $disk->url($newPath);
- $avatar->save();
- }
- Cache::forget('avatar:' . $avatar->profile_id);
- Cache::forget(AccountService::CACHE_KEY . $avatar->profile_id);
- }
- });
- }
- protected function refetchRemoteAvatars()
- {
- if(!$this->confirm('Are you sure you want to refetch all remote avatars? This could take a while.')) {
- return;
- }
- if((bool) config_cache('pixelfed.cloud_storage') == false && config_cache('federation.avatars.store_local') == false) {
- $this->error('You have cloud storage disabled and local avatar storage disabled, we cannot refetch avatars.');
- return;
- }
- $count = Profile::has('avatar')
- ->with('avatar')
- ->whereNull('user_id')
- ->count();
- $this->info('Found ' . $count . ' remote avatars to re-fetch');
- $this->line(' ');
- $bar = $this->output->createProgressBar($count);
- Profile::has('avatar')
- ->with('avatar')
- ->whereNull('user_id')
- ->chunk(50, function($profiles) use($bar) {
- foreach($profiles as $profile) {
- $avatar = $profile->avatar;
- $avatar->last_fetched_at = null;
- $avatar->save();
- RemoteAvatarFetch::dispatch($profile)->onQueue('low');
- $bar->advance();
- }
- });
- $this->line(' ');
- $this->line(' ');
- $this->info('Finished dispatching avatar refetch jobs!');
- $this->line(' ');
- $this->info('This may take a few minutes to complete, you may need to run "php artisan cache:clear" after the jobs are processed.');
- $this->line(' ');
- }
- protected function incr($name)
- {
- switch($name) {
- case 'found':
- $this->found = $this->found + 1;
- break;
- case 'notFetched':
- $this->notFetched = $this->notFetched + 1;
- break;
- case 'fixed':
- $this->fixed++;
- break;
- }
- }
- protected function fixMissingAvatars()
- {
- if(!$this->confirm('Are you sure you want to fix missing avatars?')) {
- return;
- }
- $this->info('Found ' . $this->missing . ' accounts with missing profiles');
- Profile::where('created_at', '<', now()->subDays(1))
- ->doesntHave('avatar')
- ->chunk(50, function($profiles) {
- foreach($profiles as $profile) {
- Avatar::updateOrCreate([
- 'profile_id' => $profile->id
- ], [
- 'media_path' => 'public/avatars/default.jpg',
- 'is_remote' => $profile->domain == null && $profile->private_key == null
- ]);
- $this->incr('fixed');
- }
- });
- $this->line(' ');
- $this->line(' ');
- $this->info('Fixed ' . $this->fixed . ' accounts with a blank avatar');
- }
- }
|