diff --git a/app/Console/Commands/CheckPodcastDownload.php b/app/Console/Commands/CheckPodcastDownload.php new file mode 100644 index 0000000..778f344 --- /dev/null +++ b/app/Console/Commands/CheckPodcastDownload.php @@ -0,0 +1,89 @@ +with('aThirdPartyTrackReferences')->get(); + if ($tasks->count() > 0) { + foreach ($tasks as $task) { + Log::info($task->task_id); + $queue = 'celeryresults.'.$task['task_id']; + $c = new Celery( + host: config('rabbitmq.host'), + login: config('rabbitmq.user'), + password: config('rabbitmq.password'), + vhost: config('rabbitmq.vhost'), + exchange: 'celeryresults', + binding: $queue, + result_expire: 900000 + ); + + try { + $message = $c->getAsyncResultMessage($task->name, $task->task_id); + + if ($message) { + try { + DB::beginTransaction(); + $body = json_decode($message['body'], true); + $result = json_decode($body['result'], true); + + $podcastEpisode = PodcastEpisode::where('id', '=',$result['episodeid'])->first(); + $podcastEpisode->fill([ + 'file_id' => $result['fileid'], + ])->save(); + + $timezone = new DateTimeZone(getenv('APP_TIMEZONE')); + $thirdPartyTrackReference = ThirdPartyTrackReference::where('foreign_id','=',$podcastEpisode->id)->first(); + $thirdPartyTrackReference->fill([ + 'file_id' => $result['fileid'], + 'upload_time' => new DateTime('now', $timezone) + ])->save(); + + $celeryTask = CeleryTask::where('task_id','=',$task->task_id)->where('track_reference','=',$thirdPartyTrackReference->id)->first(); + $celeryTask->fill([ + 'status' => 'SUCCESS' + ])->save(); + + DB::commit(); + } catch (\Exception $e) { + DB::rollBack(); + Log::error($e->getMessage()); + } + } + } catch (\Exception $e) { + Log::error('test '.$e); + } + } + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index fac92ad..275d5b8 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -15,6 +15,9 @@ class Kernel extends ConsoleKernel // $schedule->command('inspire')->hourly(); $schedule->command('telescope:prune --hours=48')->daily(); $schedule->command('app:create-show-schedule')->everyMinute(); + $schedule->command('app:check-podcast-download')->everyFiveSeconds(); + + } /** diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php index 5b348df..a4420fe 100644 --- a/app/Http/Controllers/FileController.php +++ b/app/Http/Controllers/FileController.php @@ -7,8 +7,10 @@ use App\Helpers\LengthFormatter; use App\Lib\RabbitMQSender; use App\Models\File; use App\Models\TrackType; +use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; class FileController extends Controller @@ -54,9 +56,19 @@ class FileController extends Controller $user = Auth::user(); + $apiKey = $request->header('php-auth-user'); + //Accept request only from logged-in users if (!$user) { - throw new \Exception("You must be logged in"); + if ($apiKey != 'some_secret_api_key') { + throw new \Exception("You must be logged in"); + } + //ToDo: check how to work in Legacy, getting user in this way is quite horrible + try { + $user = User::where('type','=','P')->orderBy('id','ASC')->first(); + } catch (\Exception $e) { + Log::error($e->getMessage()); + } } //Mime type list: https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types @@ -138,6 +150,11 @@ class FileController extends Controller $fields['track_type_id'] = TrackType::where('code','MUSICA')->first()->id; } + //Force BPM to int + if (isset($fields['bpm'])) { + $fields['bpm'] = intval($fields['bpm']); + } + //return json_encode(['req' => $fields,'id' => $id]); $file->fill($fields)->save(); diff --git a/app/Http/Controllers/PodcastController.php b/app/Http/Controllers/PodcastController.php index 493c09b..de33f4d 100644 --- a/app/Http/Controllers/PodcastController.php +++ b/app/Http/Controllers/PodcastController.php @@ -7,6 +7,7 @@ use App\Models\Podcast\Podcast; use App\Models\Podcast\PodcastEpisode; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; class PodcastController extends Controller { @@ -53,12 +54,32 @@ class PodcastController extends Controller return $xml->channel; } - public function getEpisodes(Request $request) - { - $xml = simplexml_load_file($request->url, null, LIBXML_NOCDATA); - $xmlArray = (array) $xml->channel; - //episodes are stored in `item` array - return $xmlArray['item']; + public function loadPodcastDataFromXml(Request $request) { + + try { + $xml = simplexml_load_file($request->url, null, LIBXML_NOCDATA); + $xmlArray = (array) $xml->channel; + + $newArray = []; + + foreach ($xmlArray['item'] as $key => $item) { + $item = (array) $item; + $newArray[$key] = $item; + $downloading = PodcastEpisode::where('episode_guid','=',$item['guid'])->first(); + if (!is_null($downloading)) { + $newArray[$key]['imported'] = $downloading->third_party_track_reference->celery_task->status; + } else { + $newArray[$key]['imported'] = 0; + } + } + } catch (\Exception $e) { + $xmlArray = $newArray = false; + } + + return json_encode([ + 'podcast' => $xmlArray, + 'episodes' => $newArray + ]); } protected function save(Request $request) @@ -67,27 +88,40 @@ class PodcastController extends Controller $xml = simplexml_load_file($request->url); $itunes = $xml->channel->children('itunes', TRUE); - $dbPodcast = Podcast::firstOrNew(['id' => $request->id]); - $dbPodcast->fill([ - 'url' => $request->url, - 'title' => $xml->channel->title, - 'creator' => $itunes->author, - 'description' => $itunes->subtitle, - 'language' => $xml->channel->language, - 'copyright' => $xml->channel->copyright, - 'link' => $xml->channel->link, - 'itunes_author'=> $itunes->author, - 'itunes_keywords' => '', - 'itunes_summary' => $itunes->summary, - 'itunes_subtitle' => $itunes->subtitle, - 'itunes_category' => '', - 'itunes_explicit' => $itunes->explicit, - 'owner' => $user->id, - ])->save(); + try { + $dbPodcast = Podcast::firstOrNew(['id' => $request->id]); + DB::beginTransaction(); + $dbPodcast->fill([ + 'url' => $request->url, + 'title' => $request->title, + 'creator' => $itunes->author, + 'description' => $itunes->subtitle, + 'language' => $xml->channel->language, + 'copyright' => $xml->channel->copyright, + 'link' => $xml->channel->link, + 'itunes_author'=> $itunes->author, + 'itunes_keywords' => '', + 'itunes_summary' => $itunes->summary, + 'itunes_subtitle' => $itunes->subtitle, + 'itunes_category' => '', + 'itunes_explicit' => $itunes->explicit, + 'owner' => $user->id, + ])->save(); - return response()->json([ - 'podcast' => $dbPodcast, - 'episodes' => $xml->channel->children('item', TRUE) - ]); + $dbImportedPodcast = ImportedPodcast::firstOrNew(['podcast_id' => $dbPodcast->id]);; + $dbImportedPodcast->fill([ + 'auto_ingest' => !isset($request->auto_ingest) ? true : false, + 'podcast_id' => $dbPodcast->id + ])->save(); + + DB::commit(); + + return response()->json([ + 'podcast' => $dbPodcast, + 'episodes' => $xml->channel->children('item', TRUE) + ]); + } catch (\Exception $e) { + return response()->json(['message' => $e->getMessage()], 500); + } } } diff --git a/app/Http/Controllers/PodcastEpisodeController.php b/app/Http/Controllers/PodcastEpisodeController.php index 3dae856..166d4e3 100644 --- a/app/Http/Controllers/PodcastEpisodeController.php +++ b/app/Http/Controllers/PodcastEpisodeController.php @@ -2,12 +2,16 @@ namespace App\Http\Controllers; +use App\Models\Podcast\CeleryTask; use App\Models\Podcast\Podcast; use App\Models\Podcast\PodcastEpisode; +use App\Models\Podcast\ThirdPartyTrackReference; +use Celery\Celery; +use DateTime; +use DateTimeZone; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; -use Celery\Celery; class PodcastEpisodeController extends Controller { @@ -32,22 +36,6 @@ class PodcastEpisodeController extends Controller if (!$user->id) { throw new \Exception('You must be logged in'); } - return $this->downloadPodcastEpisode($request); - } catch (\Exception $exception) { - return response($exception->getMessage(), 500); - } - } - - private function downloadPodcastEpisode(Request $request) { - $request->validate([ - 'podcast_id' => 'required', - 'episode_url' => 'required', - 'episode_title' => 'required', - ]); - - try { - - $podcast = Podcast::findOrFail($request->podcast_id); $podcastEpisode = new PodcastEpisode(); $podcastEpisode->fill([ @@ -59,6 +47,64 @@ class PodcastEpisodeController extends Controller 'episode_description' => htmlentities($request->episode['description']), ])->save(); + $brokerTask = $this->downloadPodcastEpisode($request, $podcastEpisode); + + $ref = new ThirdPartyTrackReference(); + $ref->fill([ + 'service' => 'podcast', + 'foreign_id' => $podcastEpisode->id, + 'file_id' => null + ])->save(); + + $task = new CeleryTask(); + $timezone = new DateTimeZone(getenv('APP_TIMEZONE')); + $task->fill([ + 'task_id' => $brokerTask->task_id, + 'track_reference' => $ref->id, + 'name' => 'podcast-download', + 'dispatch_time' => new DateTime('now', $timezone), + 'status' => 'PENDING' + ])->save(); + + return $podcastEpisode->toJson(); + + } catch (\Exception $exception) { + return response($exception->getMessage(), 500); + } + } + + public function checkPodcastEpisodeDownload($id) { + $episode = PodcastEpisode::where('id',$id)->firstOrFail(); + return $episode->third_party_track_reference->celery_task->status; + } + + public function savePodcastEpisode(Request $request) { + + if(isset($request->file)) { + try { + $file = (new FileController())->store($request); + } catch (\Exception $e) { + return response($e->getMessage(), 500); + } + if ($file) { + return $file; + } + return json_encode([ + 'id' => 0 + ]); + } + } + + private function downloadPodcastEpisode(Request $request, $podcastEpisode) { + $request->validate([ + 'podcast_id' => 'required', + 'episode_url' => 'required', + 'episode_title' => 'required', + ]); + + try { + $podcast = Podcast::findOrFail($request->podcast_id); + $conn = new Celery( config('rabbitmq.host'), config('rabbitmq.user'), @@ -79,24 +125,9 @@ class PodcastEpisodeController extends Controller 'override_album' => 'false' //ToDo connect $album_override from imported_podcast, ]; - $result = $conn->PostTask('podcast-download', $data, true, 'podcast'); + $taskId = $conn->PostTask('podcast-download', $data, true, 'podcast'); + return $taskId; - return $result->getId(); - - while (!$result->isReady()) { - sleep(1); - } - - - if (!$result->isSuccess()) { - //Todo: throw exception - throw new \Exception('podcast episode id:'.$podcastEpisode->id.' download failed'); - } - //Todo: return ok - return $result; -// $podcastEpisode->fill([ -// '' -// ]); } catch (\Exception $exception) { Log::error($exception->getMessage()); die($exception->getMessage()); diff --git a/app/Models/Podcast/CeleryTask.php b/app/Models/Podcast/CeleryTask.php new file mode 100644 index 0000000..b205dee --- /dev/null +++ b/app/Models/Podcast/CeleryTask.php @@ -0,0 +1,27 @@ +hasOne(ThirdPartyTrackReference::class, 'id', 'track_reference'); + } +} diff --git a/app/Models/Podcast/PodcastEpisode.php b/app/Models/Podcast/PodcastEpisode.php index d3dbd65..bcab8cb 100644 --- a/app/Models/Podcast/PodcastEpisode.php +++ b/app/Models/Podcast/PodcastEpisode.php @@ -29,4 +29,8 @@ class PodcastEpisode extends Model public function podcast() { return $this->belongsTo(Podcast::class); } + + public function third_party_track_reference() { + return $this->hasOne(ThirdPartyTrackReference::class, 'foreign_id', 'id'); + } } diff --git a/app/Models/Podcast/ThirdPartyTrackReference.php b/app/Models/Podcast/ThirdPartyTrackReference.php new file mode 100644 index 0000000..8457fa1 --- /dev/null +++ b/app/Models/Podcast/ThirdPartyTrackReference.php @@ -0,0 +1,36 @@ +hasOne(File::class); + } + + public function podcast_episode() { + return $this->belongsTo(PodcastEpisode::class, 'foreign_id'); + } + + public function celery_task() { + return $this->hasOne(CeleryTask::class, 'track_reference'); + } +} diff --git a/composer.json b/composer.json index 7abb394..62f7faa 100644 --- a/composer.json +++ b/composer.json @@ -18,8 +18,15 @@ "mxl/laravel-job": "^1.6", "php-amqplib/php-amqplib": "^3.7", "spatie/laravel-permission": "^6.13", - "xenos/musicbrainz": "^1.0" + "xenos/musicbrainz": "^1.0", + "libretime/celery-php": "dev-main" }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/libretime/celery-php.git" + } + ], "require-dev": { "fakerphp/faker": "^1.9.1", "laravel/breeze": "*", diff --git a/resources/js/components/content/Podcast.vue b/resources/js/components/content/Podcast.vue index cd272c1..e2d5e63 100644 --- a/resources/js/components/content/Podcast.vue +++ b/resources/js/components/content/Podcast.vue @@ -14,6 +14,7 @@ podcastStore.loadPodcast(basePodcast()); const { items, listData, headers, selected, loading, search, getItems, editItem, deleteItem } = podcast_page(); const url = ref(''); const itemEdited = ref(basePodcast()); +const episodes = ref([]); const bulk = ref(false); const dialog = reactive({ open: false, @@ -21,6 +22,7 @@ const dialog = reactive({ title: '', text: '' }); +const dialogLoading = ref(false); const openDialog = (type, title = '', text = '', bulk = false) => { dialog.open = true @@ -47,12 +49,29 @@ const confirm = (confirm, bulk) => { } } -const confirmAdd = (confirm) => { +const confirmAdd = async (confirm) => { if (confirm) { - podcastStore.updateField({key: 'url', value: url}); - console.log(podcastStore); + dialogLoading.value = true; + await axios.get('/rss_podcast_load', { + params: { + url: url.value, + } + }).then(res => { + if (res.data.podcast) { + podcastStore.updateField({key: 'title', value: res.data.podcast.title}); + podcastStore.updateField({key: 'url', value: url}); + podcastStore.currentPodcastEpisodes = res.data.episodes; + } + closeDialog(); + dialogLoading.value = false; + //episodes.value = res.data.episodes; + openDialog( + 'info', + 'Attenzione', + 'L\'URL inserito non è un feed RSS valido' + ); + }) } - closeDialog() } const edit = (item) => { @@ -60,42 +79,46 @@ const edit = (item) => { } const cancel = (item) => { - bulk.value = Array.isArray(item) - itemEdited.value = item + bulk.value = Array.isArray(item); + itemEdited.value = item; openDialog( 'delete', 'Cancella', bulk.value ? 'Vuoi cancellare i podcast selezionati?' : 'Vuoi cancellare il podcast selezionato?' - ) + ); } const confirmDelete = (confirm, bulk) => { if (confirm) { if (!bulk) { - deleteItem(itemEdited.value.id) + deleteItem(itemEdited.value.id); } else { itemEdited.value.forEach(el => { - deleteItem(el.id) + deleteItem(el.id); }) } } - closeDialog() + closeDialog(); } const closeDialog = () => { - dialog.open = false + dialog.open = false; itemEdited.value = basePodcast(); } const updateSearch = (text) => { - search.value = text + search.value = text; +} + +const resetItemEdited = () => { + podcastStore.currentPodcast = basePodcast(); }