AvatarStorageDeepClean.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. <?php
  2. namespace App\Console\Commands;
  3. use Illuminate\Console\Command;
  4. use Cache;
  5. use Storage;
  6. use App\Avatar;
  7. use App\Jobs\AvatarPipeline\AvatarStorageCleanup;
  8. class AvatarStorageDeepClean extends Command
  9. {
  10. /**
  11. * The name and signature of the console command.
  12. *
  13. * @var string
  14. */
  15. protected $signature = 'avatar:storage-deep-clean';
  16. /**
  17. * The console command description.
  18. *
  19. * @var string
  20. */
  21. protected $description = 'Cleanup avatar storage';
  22. protected $shouldKeepRunning = true;
  23. protected $counter = 0;
  24. /**
  25. * Execute the console command.
  26. */
  27. public function handle(): void
  28. {
  29. $this->info(' ____ _ ______ __ ');
  30. $this->info(' / __ \(_) _____ / / __/__ ____/ / ');
  31. $this->info(' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / ');
  32. $this->info(' / ____/ /> </ __/ / __/ __/ /_/ / ');
  33. $this->info(' /_/ /_/_/|_|\___/_/_/ \___/\__,_/ ');
  34. $this->info(' ');
  35. $this->info(' Pixelfed Avatar Deep Cleaner');
  36. $this->line(' ');
  37. $this->info(' Purge/delete old and outdated avatars from remote accounts');
  38. $this->line(' ');
  39. $storage = [
  40. 'cloud' => boolval(config_cache('pixelfed.cloud_storage')),
  41. 'local' => boolval(config_cache('federation.avatars.store_local'))
  42. ];
  43. if(!$storage['cloud'] && !$storage['local']) {
  44. $this->error('Remote avatars are not cached locally, there is nothing to purge. Aborting...');
  45. exit;
  46. }
  47. $start = 0;
  48. if(!$this->confirm('Are you sure you want to proceed?')) {
  49. $this->error('Aborting...');
  50. exit;
  51. }
  52. if(!$this->activeCheck()) {
  53. $this->info('Found existing deep cleaning job');
  54. if(!$this->confirm('Do you want to continue where you left off?')) {
  55. $this->error('Aborting...');
  56. exit;
  57. } else {
  58. $start = Cache::has('cmd:asdp') ? (int) Cache::get('cmd:asdp') : (int) Storage::get('avatar-deep-clean.json');
  59. if($start && $start < 1 || $start > PHP_INT_MAX) {
  60. $this->error('Error fetching cached value');
  61. $this->error('Aborting...');
  62. exit;
  63. }
  64. }
  65. }
  66. $count = Avatar::whereNotNull('cdn_url')->where('is_remote', true)->where('id', '>', $start)->count();
  67. $bar = $this->output->createProgressBar($count);
  68. foreach(Avatar::whereNotNull('cdn_url')->where('is_remote', true)->where('id', '>', $start)->lazyById(10, 'id') as $avatar) {
  69. usleep(random_int(50, 1000));
  70. $this->counter++;
  71. $this->handleAvatar($avatar);
  72. $bar->advance();
  73. }
  74. $bar->finish();
  75. }
  76. protected function updateCache($id)
  77. {
  78. Cache::put('cmd:asdp', $id);
  79. if($this->counter % 5 === 0) {
  80. Storage::put('avatar-deep-clean.json', $id);
  81. }
  82. }
  83. protected function activeCheck()
  84. {
  85. if(Storage::exists('avatar-deep-clean.json') || Cache::has('cmd:asdp')) {
  86. return false;
  87. }
  88. return true;
  89. }
  90. protected function handleAvatar($avatar)
  91. {
  92. $this->updateCache($avatar->id);
  93. $queues = ['feed', 'mmo', 'feed', 'mmo', 'feed', 'feed', 'mmo', 'low'];
  94. $queue = $queues[random_int(0, 7)];
  95. AvatarStorageCleanup::dispatch($avatar)->onQueue($queue);
  96. }
  97. }