diff --git a/app/Console/Commands/ClearTrackCache.php b/app/Console/Commands/ClearTrackCache.php new file mode 100644 index 00000000..ab534a13 --- /dev/null +++ b/app/Console/Commands/ClearTrackCache.php @@ -0,0 +1,111 @@ +. + */ + +namespace Poniverse\Ponyfm\Console\Commands; + +use Carbon\Carbon; +use File; +use Illuminate\Console\Command; +use Illuminate\Support\Facades\Cache; +use Poniverse\Ponyfm\TrackFile; + +class ClearTrackCache extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'track-cache:clear + [--tracks=expired : Clear only [expired] (default) or [all] cached tracks.] + [--force : Skip all prompts.]'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Clears cached tracks. Defaults to expired tracks. Usage: php artisan track-cache:clear [--tracks=expired|all]'; + + /** + * Create a new command instance. + * + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + if ($this->option('tracks') == 'all') { + // Get all cacheable tracks + $trackFiles = TrackFile::where('is_cacheable', true)->get(); + } else { + // Get all expired tracks + $trackFiles = TrackFile::where('is_cacheable', true) + ->where('expiration', '<=', Carbon::now()) + ->get(); + + } + + // Delete above tracks + if (count($trackFiles) === 0) { + $this->info('No tracks found. Exiting.'); + } else { + + if ($this->option('force') || $this->confirm(count($trackFiles) . ' tracks found. Proceed to delete? [y|N]', false)) { + + $count = 0; + + foreach ($trackFiles as $trackFile) { + + // Set expiration to null (so can be re-cached upon request) + $trackFile->expiration = null; + $trackFile->update(); + + // Delete files if exists + if (File::exists($trackFile->getFile())) { + $count++; + File::delete($trackFile->getFile()); + + // Remove the cached file sizes for main, trackfile and format + Cache::forget($trackFile->getCacheKey('filesize')); + Cache::forget($trackFile->track()->getCacheKey('filesize-' . $trackFile->format)); + Cache::forget($trackFile->track()->album()->getCacheKey('filesize-' . $trackFile->format)); + + $this->info('Deleted ' . $trackFile->getFile()); + } + + } + $this->info($count . ' files deleted. Deletion complete. Exiting.'); + } else { + $this->info('Deletion cancelled. Exiting.'); + } + + } + } + +} \ No newline at end of file diff --git a/app/Console/Commands/RebuildTrackCache.php b/app/Console/Commands/RebuildTrackCache.php new file mode 100644 index 00000000..e2fc36e5 --- /dev/null +++ b/app/Console/Commands/RebuildTrackCache.php @@ -0,0 +1,210 @@ +. + */ + +namespace Poniverse\Ponyfm\Console\Commands; + +use Illuminate\Console\Command; +use Poniverse\Ponyfm\Track; +use Poniverse\Ponyfm\TrackFile; +use Symfony\Component\HttpFoundation\File\File; + +class RebuildTrackCache extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'track-cache:rebuild'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Rebuilds the track cache for when $CacheableFormats is changed. Deletes cacheable files and encodes missing files which are not cacheable.'; + + /** + * Create a new command instance. + * + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $this->info('This will run \'php artisan down\' if you proceed and \'php artisan up\' when complete.'); + + if ($this->option('force') || $this->confirm('Are you sure you want to delete all to-be-cached files and encode missing non-cached files? [y|N]', + false) + ) { + + $this->call('down'); + + //========================================================================================================== + // Delete previously cached tracks + //========================================================================================================== + + // Find files which are cacheable and NOT master + $trackFiles = TrackFile::where('is_cacheable', true) + ->where('is_master', false) + ->get(); + + // Delete above files + if (count($trackFiles) == 0) { + $this->info('No tracks found. Continuing.'); + } else { + $this->info(count($trackFiles) . ' tracks found.'); + $count = 0; + + foreach ($trackFiles as $trackFile) { + // Clear expiration so will be re-cached on next request + $trackFile->expiration = null; + $trackFile->update(); + + // Delete files + if (File::exists($trackFile->getFile())) { + $count++; + File::delete($trackFile->getFile()); + $this->info('Deleted ' . $trackFile->getFile()); + } + } + + $this->info($count . ' files deleted. Deletion complete. Continuing.'); + } + + //========================================================================================================== + // Update the database entries for cacheable files - non-cacheable to cacheable + //========================================================================================================== + + $this->info('--- Step 2/4 - Updating is_cacheable entries in database. ---'); + + // Find files which are meant to be cacheable and NOT master, but currently not cacheable + $trackFiles = TrackFile::where('is_cacheable', false) + ->whereIn('format', array_keys(Track::$CacheableFormats)) + ->where('is_master', false) + ->get(); + + $formats = []; + + // Set above files to cacheable in the database + foreach ($trackFiles as $trackFile) { + // Let user know which formats, previously not cached, were made cacheable + $formats[] = $trackFile->format; + + $trackFile->expiration = null; + $trackFile->is_cacheable = true; + $trackFile->update(); + } + + $this->info('Format(s) set from non-cacheable to cacheable: ' . implode(' ', array_unique($formats))); + $this->info(count($trackFiles) . ' non-cacheable tracks set to cacheable.'); + + //========================================================================================================== + // Update the database entries for cacheable files - cacheable to non-cacheable + //========================================================================================================== + + // Find files which are NOT meant to be cacheable, but currently cacheable + $trackFiles = TrackFile::where('is_cacheable', true) + ->whereNotIn('format', array_keys(Track::$CacheableFormats)) + ->get(); + + $formats = []; + + // Set above files to non-cacheable in the database + foreach ($trackFiles as $trackFile) { + // Let user know which formats, previously not cached, were made cacheable + $formats[] = $trackFile->format; + + $trackFile->expiration = null; + $trackFile->is_cacheable = false; + $trackFile->update(); + } + + $this->info('Format(s) set from cacheable to non-cacheable: ' . implode(' ', array_unique($formats))); + $this->info(count($trackFiles) . ' cacheable tracks set to non-cacheable.'); + + //========================================================================================================== + // Delete files which have now been marked as cacheable + //========================================================================================================== + + $this->info('--- Step 3/4 - Deleting now-cacheable files. ---'); + + // Find files which are cacheable and NOT master + $trackFiles = TrackFile::whereIn('format', array_keys(Track::$CacheableFormats)) + ->where('is_master', false) + ->get(); + + // Delete above files + if (count($trackFiles) == 0) { + $this->info('No tracks to delete found. Continuing.'); + } else { + $count = 0; + foreach ($trackFiles as $trackFile) { + // Delete files if files exist; double-check that they are NOT master files + if (File::exists($trackFile->getFile()) && $trackFile->is_master == false) { + $count++; + File::delete($trackFile->getFile()); + $this->info('Deleted ' . $trackFile->getFile()); + } + } + $this->info(sprintf('%d files deleted out of %d tracks. Continuing.', $count, count($trackFiles))); + } + + //========================================================================================================== + // Encode missing (i.e., now non-cacheable) files + //========================================================================================================== + + $this->info('--- Step 4/4 - Encoding missing files. ---'); + + // Get non-cacheable files + $trackFiles = TrackFile::where('is_cacheable', false)->get(); + + // Record the above files which do not exist (i.e., have not been encoded yet) + $trackFileIds = []; + $count = 0; + foreach ($trackFiles as $trackFile) { + if (!File::exists($trackFile->getFile())) { + $count++; + $trackFileIds[] = $trackFile->id; + } + } + + // Encode recorded files + $encodeTrackFileCommand = new EncodeTrackFileCommand($trackFileIds); + $encodeTrackFileCommand->execute(); + + $this->info($count . ' tracks encoded.'); + + $this->call('up'); + $this->info('Rebuild complete. Exiting.'); + + } else { + $this->info('Rebuild cancelled. Exiting.'); + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 94c282af..6f76ff2b 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -39,6 +39,8 @@ class Kernel extends ConsoleKernel \Poniverse\Ponyfm\Console\Commands\FixYearZeroLogs::class, \Poniverse\Ponyfm\Console\Commands\BootstrapLocalEnvironment::class, \Poniverse\Ponyfm\Console\Commands\PoniverseApiSetup::class, + \Poniverse\Ponyfm\Console\Commands\ClearTrackCache::class, + \Poniverse\Ponyfm\Console\Commands\RebuildTrackCache::class, ]; /**