Added:
- activity on menu - starting activity page
This commit is contained in:
52
assets/controllers/activity_controller.js
Normal file
52
assets/controllers/activity_controller.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import {Controller} from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||||
|
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
|
||||||
|
*/
|
||||||
|
/* stimulusFetch: 'lazy' */
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ['activity']
|
||||||
|
|
||||||
|
// ...
|
||||||
|
async connect() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/activity/status`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
// Handle the response data as needed
|
||||||
|
this.activityTarget.innerHTML = data.length;
|
||||||
|
if (data.length > 0) {
|
||||||
|
this.activityTarget.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const mercureHubUrl = 'https://localhost/.well-known/mercure';
|
||||||
|
const eventSource = new EventSource(`${mercureHubUrl}?topic=activity`);
|
||||||
|
|
||||||
|
eventSource.onmessage = (event) => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.processing !== undefined && data.pending !== undefined) {
|
||||||
|
let totalActivities = data.processing.length + data.pending.length;
|
||||||
|
this.activityTarget.innerHTML = totalActivities;
|
||||||
|
if (totalActivities > 0) {
|
||||||
|
this.activityTarget.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource
|
||||||
|
.onerror = (event) => {
|
||||||
|
console.error('EventSource failed:', event);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,10 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: kernel.event_listener, event: 'manga.scraped', method: 'onMangaScraped' }
|
- { name: kernel.event_listener, event: 'manga.scraped', method: 'onMangaScraped' }
|
||||||
|
|
||||||
|
App\EventSubscriber\QueueStatusSubscriber:
|
||||||
|
tags:
|
||||||
|
- { name: kernel.event_subscriber }
|
||||||
|
|
||||||
App\Controller\MenuController:
|
App\Controller\MenuController:
|
||||||
tags: [ 'controller.service_arguments' ]
|
tags: [ 'controller.service_arguments' ]
|
||||||
|
|
||||||
|
|||||||
113
src/Controller/ActivityController.php
Normal file
113
src/Controller/ActivityController.php
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Message\DownloadChapter;
|
||||||
|
use App\Repository\ChapterRepository;
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class ActivityController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(private Connection $connection, private readonly ChapterRepository $chapterRepository)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/activity', name: 'app_activity')]
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
$queueStatus = $this->getQueueStatus();
|
||||||
|
$decodedPending = $this->decodeMessages($queueStatus['pending']);
|
||||||
|
$decodedProcessing = $this->decodeMessages($queueStatus['processing']);
|
||||||
|
|
||||||
|
$status = array_merge(
|
||||||
|
$this->buildStatusActivity($decodedPending),
|
||||||
|
$this->buildStatusActivity($decodedProcessing)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->render('activity/index.html.twig', [
|
||||||
|
'controller_name' => 'ActivityController',
|
||||||
|
'status' => $status,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/activity/status', name: 'app_activity_status', methods: ['GET'])]
|
||||||
|
public function getStatus(): JsonResponse
|
||||||
|
{
|
||||||
|
$queueStatus = $this->getQueueStatus();
|
||||||
|
$decodedPending = $this->decodeMessages($queueStatus['pending']);
|
||||||
|
$decodedProcessing = $this->decodeMessages($queueStatus['processing']);
|
||||||
|
$status = array_merge(
|
||||||
|
$this->buildStatusActivity($decodedPending),
|
||||||
|
$this->buildStatusActivity($decodedProcessing)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO refactorer ce code avec celui du QueueStatusSubscriber
|
||||||
|
private function getQueueStatus(): array
|
||||||
|
{
|
||||||
|
// Requête pour récupérer les messages en attente
|
||||||
|
$sqlPending = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NULL';
|
||||||
|
$pending = $this->connection->fetchAllAssociative($sqlPending, ['queue' => 'default']);
|
||||||
|
|
||||||
|
// Requête pour récupérer les messages en cours de traitement
|
||||||
|
$sqlProcessing = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NOT NULL';
|
||||||
|
$processing = $this->connection->fetchAllAssociative($sqlProcessing, ['queue' => 'default']);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'pending' => $pending,
|
||||||
|
'processing' => $processing
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildStatusActivity(array $activity): array
|
||||||
|
{
|
||||||
|
$status = [];
|
||||||
|
foreach ($activity as $envelope) {
|
||||||
|
$envelope = $envelope['body'];
|
||||||
|
if ($envelope instanceof Envelope) {
|
||||||
|
if (!$envelope->getMessage() instanceof DownloadChapter) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chapter = $this->chapterRepository->find($envelope->getMessage()->getChapterId());
|
||||||
|
$manga = $chapter->getManga();
|
||||||
|
$status[] = [
|
||||||
|
'manga' => $manga->getTitle(),
|
||||||
|
'volume' => $chapter->getVolume(),
|
||||||
|
'chapter' => $chapter->getNumber(),
|
||||||
|
'title' => $chapter->getTitle(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decodeMessages(array $messages): array
|
||||||
|
{
|
||||||
|
$decodedMessages = [];
|
||||||
|
|
||||||
|
foreach ($messages as $message) {
|
||||||
|
$decodedMessages[] = [
|
||||||
|
'id' => $message['id'],
|
||||||
|
'body' => $this->decodeMessageBody($message['body']),
|
||||||
|
'headers' => json_decode($message['headers'], true),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decodedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decodeMessageBody(string $body)
|
||||||
|
{
|
||||||
|
return unserialize(stripcslashes($body));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,26 +5,86 @@ namespace App\Controller;
|
|||||||
use App\Entity\Chapter;
|
use App\Entity\Chapter;
|
||||||
use App\Entity\ContentSource;
|
use App\Entity\ContentSource;
|
||||||
use App\Entity\Manga;
|
use App\Entity\Manga;
|
||||||
|
use App\Message\DownloadChapter;
|
||||||
|
use App\Repository\ChapterRepository;
|
||||||
use App\Repository\MangaRepository;
|
use App\Repository\MangaRepository;
|
||||||
use App\Service\MangadexProvider;
|
use App\Service\MangadexProvider;
|
||||||
use App\Service\MangaScraperService;
|
use App\Service\MangaScraperService;
|
||||||
use App\Service\MangaUpdatesMetadataProvider;
|
use App\Service\MangaUpdatesMetadataProvider;
|
||||||
use App\Service\SushiScanProviderService;
|
use App\Service\SushiScanProviderService;
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
class TestController extends AbstractController
|
class TestController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private MangadexProvider $mangadexProvider, private MangaRepository $mangaRepository)
|
public function __construct(
|
||||||
|
private MangadexProvider $mangadexProvider,
|
||||||
|
private MangaRepository $mangaRepository,
|
||||||
|
private MessageBusInterface $bus,
|
||||||
|
private Connection $connection,
|
||||||
|
private SerializerInterface $serializer,
|
||||||
|
private readonly ChapterRepository $chapterRepository
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/test', name: 'test')]
|
#[Route('/test', name: 'test')]
|
||||||
public function test(): Response
|
public function test(): Response
|
||||||
{
|
{
|
||||||
$manga = $this->mangaRepository->find(8);
|
$sqlPending = 'SELECT * FROM messenger_messages WHERE queue_name = :queue';
|
||||||
|
$pending = $this->connection->fetchAllAssociative($sqlPending, ['queue' => 'default']);
|
||||||
|
|
||||||
dd($this->mangadexProvider->getFeed($manga));
|
// // Requête pour récupérer les messages en cours de traitement
|
||||||
|
// $sqlProcessing = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NOT NULL';
|
||||||
|
// $processing = $this->connection->fetchAllAssociative($sqlProcessing, ['queue' => 'default']);
|
||||||
|
|
||||||
|
// dd($pending);
|
||||||
|
$decoded = $this->decodeMessages($pending);
|
||||||
|
|
||||||
|
$status = [];
|
||||||
|
foreach($decoded as $message) {
|
||||||
|
$message = $message['body'];
|
||||||
|
if($message instanceof Envelope) {
|
||||||
|
$chapter = $this->chapterRepository->find($message->getMessage()->getChapterId());
|
||||||
|
$manga = $chapter->getManga();
|
||||||
|
$status[] = [
|
||||||
|
'manga' => $manga->getTitle(),
|
||||||
|
'volume' => $chapter->getVolume(),
|
||||||
|
'chapter' => $chapter->getNumber(),
|
||||||
|
'title' => $chapter->getTitle(),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->bus->dispatch(new DownloadChapter(1));
|
||||||
|
|
||||||
|
dd($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decodeMessages(array $messages): array
|
||||||
|
{
|
||||||
|
$decodedMessages = [];
|
||||||
|
|
||||||
|
foreach ($messages as $message) {
|
||||||
|
$decodedMessages[] = [
|
||||||
|
'id' => $message['id'],
|
||||||
|
'body' => $this->decodeMessageBody($message['body']),
|
||||||
|
'headers' => json_decode($message['headers'], true),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decodedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decodeMessageBody(string $body)
|
||||||
|
{
|
||||||
|
return unserialize(stripcslashes($body));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/Event/PageScrappingProgressEvent.php
Normal file
36
src/Event/PageScrappingProgressEvent.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Event;
|
||||||
|
|
||||||
|
use Symfony\Contracts\EventDispatcher\Event;
|
||||||
|
|
||||||
|
class PageScrappingProgressEvent extends Event
|
||||||
|
{
|
||||||
|
public const NAME = 'page.scrapping.progress';
|
||||||
|
|
||||||
|
private int $chapterId;
|
||||||
|
private int $pageIndex;
|
||||||
|
private int $totalPages;
|
||||||
|
|
||||||
|
public function __construct(int $chapterId, int $pageIndex, int $totalPages)
|
||||||
|
{
|
||||||
|
$this->chapterId = $chapterId;
|
||||||
|
$this->pageIndex = $pageIndex;
|
||||||
|
$this->totalPages = $totalPages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChapterId(): int
|
||||||
|
{
|
||||||
|
return $this->chapterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPageIndex(): int
|
||||||
|
{
|
||||||
|
return $this->pageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalPages(): int
|
||||||
|
{
|
||||||
|
return $this->totalPages;
|
||||||
|
}
|
||||||
|
}
|
||||||
146
src/EventSubscriber/QueueStatusSubscriber.php
Normal file
146
src/EventSubscriber/QueueStatusSubscriber.php
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
|
use App\Event\PageScrappingProgressEvent;
|
||||||
|
use App\Message\DownloadChapter;
|
||||||
|
use App\Repository\ChapterRepository;
|
||||||
|
use App\Service\ActivityService;
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||||
|
use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent;
|
||||||
|
use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent;
|
||||||
|
|
||||||
|
class QueueStatusSubscriber implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ActivityService $activityService,
|
||||||
|
private Connection $connection,
|
||||||
|
private ChapterRepository $chapterRepository
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
WorkerMessageReceivedEvent::class => 'onMessageReceived',
|
||||||
|
WorkerMessageHandledEvent::class => 'onMessageHandled',
|
||||||
|
WorkerMessageFailedEvent::class => 'onMessageFailed',
|
||||||
|
PageScrappingProgressEvent::NAME => 'onPageScrapingProgress',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMessageReceived(WorkerMessageReceivedEvent $event): void
|
||||||
|
{
|
||||||
|
$envelope = $event->getEnvelope();
|
||||||
|
$message = $envelope->getMessage();
|
||||||
|
|
||||||
|
if ($message instanceof DownloadChapter) {
|
||||||
|
$this->activityService->sendUpdate($this->getActivity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMessageHandled(WorkerMessageHandledEvent $event): void
|
||||||
|
{
|
||||||
|
$envelope = $event->getEnvelope();
|
||||||
|
$message = $envelope->getMessage();
|
||||||
|
|
||||||
|
if ($message instanceof DownloadChapter) {
|
||||||
|
$this->activityService->sendUpdate($this->getActivity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMessageFailed(WorkerMessageFailedEvent $event): void
|
||||||
|
{
|
||||||
|
$envelope = $event->getEnvelope();
|
||||||
|
$message = $envelope->getMessage();
|
||||||
|
|
||||||
|
if ($message instanceof DownloadChapter) {
|
||||||
|
$this->activityService->sendUpdate($this->getActivity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageScrapingProgress(PageScrappingProgressEvent $event): void
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'status' => 'Page scraping progress',
|
||||||
|
'chapterId' => $event->getChapterId(),
|
||||||
|
'pageIndex' => $event->getPageIndex(),
|
||||||
|
'totalPages' => $event->getTotalPages(),
|
||||||
|
];
|
||||||
|
$this->activityService->sendUpdate($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getActivity(): array
|
||||||
|
{
|
||||||
|
$queueStatus = $this->getQueueStatus();
|
||||||
|
return [
|
||||||
|
'processing' => $this->buildStatusActivity($this->decodeMessages($queueStatus['processing'])),
|
||||||
|
'pending' => $this->buildStatusActivity($this->decodeMessages($queueStatus['pending']))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO refactorer ce code avec celui du ActivityController
|
||||||
|
private function buildStatusActivity(array $activity): array
|
||||||
|
{
|
||||||
|
$status = [];
|
||||||
|
foreach ($activity as $envelope) {
|
||||||
|
$envelope = $envelope['body'];
|
||||||
|
if ($envelope instanceof Envelope) {
|
||||||
|
if (!$envelope->getMessage() instanceof DownloadChapter) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chapter = $this->chapterRepository->find($envelope->getMessage()->getChapterId());
|
||||||
|
$manga = $chapter->getManga();
|
||||||
|
$status[] = [
|
||||||
|
'manga' => $manga->getTitle(),
|
||||||
|
'volume' => $chapter->getVolume(),
|
||||||
|
'chapter' => $chapter->getNumber(),
|
||||||
|
'title' => $chapter->getTitle(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getQueueStatus(): array
|
||||||
|
{
|
||||||
|
// Requête pour récupérer les messages en attente
|
||||||
|
$sqlPending = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NULL';
|
||||||
|
$pending = $this->connection->fetchAllAssociative($sqlPending, ['queue' => 'default']);
|
||||||
|
|
||||||
|
// Requête pour récupérer les messages en cours de traitement
|
||||||
|
$sqlProcessing = 'SELECT * FROM messenger_messages WHERE queue_name = :queue AND available_at IS NOT NULL';
|
||||||
|
$processing = $this->connection->fetchAllAssociative($sqlProcessing, ['queue' => 'default']);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'pending' => $pending,
|
||||||
|
'processing' => $processing
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decodeMessages(array $messages): array
|
||||||
|
{
|
||||||
|
$decodedMessages = [];
|
||||||
|
|
||||||
|
foreach ($messages as $message) {
|
||||||
|
$decodedMessages[] = [
|
||||||
|
'id' => $message['id'],
|
||||||
|
'body' => $this->decodeMessageBody($message['body']),
|
||||||
|
'headers' => json_decode($message['headers'], true),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decodedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decodeMessageBody(string $body)
|
||||||
|
{
|
||||||
|
return unserialize(stripcslashes($body));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,10 +33,10 @@ readonly class DownloadChapterHandler
|
|||||||
{
|
{
|
||||||
$chapter = $this->chapterRepository->find($message->getChapterId());
|
$chapter = $this->chapterRepository->find($message->getChapterId());
|
||||||
if (!$chapter) {
|
if (!$chapter) {
|
||||||
$this->notificationService->sendUpdate('notification', ['status' => 'error', 'message' => 'Chapter not found.']);
|
$this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Chapter not found.']);
|
||||||
throw new BadRequestHttpException('Chapter not found');
|
throw new BadRequestHttpException('Chapter not found');
|
||||||
} elseif ($chapter->getLocalPath() !== null) {
|
} elseif ($chapter->getLocalPath() !== null) {
|
||||||
$this->notificationService->sendUpdate('notification', ['status' => 'error', 'message' => 'Chapter already scraped.']);
|
$this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Chapter already scraped.']);
|
||||||
throw new BadRequestHttpException('Chapter already downloaded');
|
throw new BadRequestHttpException('Chapter already downloaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ readonly class DownloadChapterHandler
|
|||||||
$scrapedSuccessfully = true;
|
$scrapedSuccessfully = true;
|
||||||
break;
|
break;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->notificationService->sendUpdate('notification', [
|
$this->notificationService->sendUpdate([
|
||||||
'status' => 'warning',
|
'status' => 'warning',
|
||||||
'message' => 'An error occurred while scraping with source: ' . $source->getBaseUrl() . '. Trying next source...'
|
'message' => 'An error occurred while scraping with source: ' . $source->getBaseUrl() . '. Trying next source...'
|
||||||
]);
|
]);
|
||||||
@@ -74,13 +74,13 @@ readonly class DownloadChapterHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$scrapedSuccessfully) {
|
if (!$scrapedSuccessfully) {
|
||||||
$this->notificationService->sendUpdate('notification', [
|
$this->notificationService->sendUpdate([
|
||||||
'status' => 'error',
|
'status' => 'error',
|
||||||
'message' => 'All sources failed to scrape the chapter ' . $chapter->getManga()->getTitle() . ' ' . $chapter->getNumber() . '.'
|
'message' => 'All sources failed to scrape the chapter ' . $chapter->getManga()->getTitle() . ' ' . $chapter->getNumber() . '.'
|
||||||
]);
|
]);
|
||||||
throw new Exception('All sources failed to scrape the chapter ' . $chapter->getManga()->getTitle() . ' ' . $chapter->getNumber() . '.');
|
throw new Exception('All sources failed to scrape the chapter ' . $chapter->getManga()->getTitle() . ' ' . $chapter->getNumber() . '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->notificationService->sendUpdate('notification', ['status' => 'success', 'message' => 'Chapter scraped successfully.']);
|
$this->notificationService->sendUpdate(['status' => 'success', 'message' => 'Chapter scraped successfully.']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/Service/ActivityService.php
Normal file
20
src/Service/ActivityService.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use Symfony\Component\Mercure\HubInterface;
|
||||||
|
use Symfony\Component\Mercure\Update;
|
||||||
|
|
||||||
|
class ActivityService
|
||||||
|
{
|
||||||
|
public function __construct(private HubInterface $hub)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendUpdate(mixed $data): void
|
||||||
|
{
|
||||||
|
$update = new Update('activity', json_encode($data));
|
||||||
|
$this->hub->publish($update);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace App\Service;
|
|||||||
use App\Entity\Chapter;
|
use App\Entity\Chapter;
|
||||||
use App\Entity\Manga;
|
use App\Entity\Manga;
|
||||||
use App\Entity\ContentSource;
|
use App\Entity\ContentSource;
|
||||||
|
use App\Event\PageScrappingProgressEvent;
|
||||||
use App\EventSubscriber\MangaScrapedEvent;
|
use App\EventSubscriber\MangaScrapedEvent;
|
||||||
use Exception;
|
use Exception;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
@@ -143,6 +144,9 @@ class MangaScraperService
|
|||||||
|
|
||||||
$this->downloadAndSaveImage($pageUrl, $imagePath);
|
$this->downloadAndSaveImage($pageUrl, $imagePath);
|
||||||
|
|
||||||
|
$event = new PageScrappingProgressEvent($chapter->getId(), count($pageData) + 1, count($results['chapter']['dataSaver']));
|
||||||
|
$this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME);
|
||||||
|
|
||||||
$pageData[] = [
|
$pageData[] = [
|
||||||
'image_url' => $pageUrl,
|
'image_url' => $pageUrl,
|
||||||
'local_image_url' => sprintf('/manga-images/%s/%s/%s', $mangaTitle, $chapterNumber, $imageName),
|
'local_image_url' => sprintf('/manga-images/%s/%s/%s', $mangaTitle, $chapterNumber, $imageName),
|
||||||
@@ -215,6 +219,9 @@ class MangaScraperService
|
|||||||
|
|
||||||
$this->downloadAndSaveImage($page['image_url'], $imagePath);
|
$this->downloadAndSaveImage($page['image_url'], $imagePath);
|
||||||
|
|
||||||
|
$event = new PageScrappingProgressEvent($chapter->getId(), count($pageData) + 1, 0);
|
||||||
|
$this->eventDispatcher->dispatch($event, PageScrappingProgressEvent::NAME);
|
||||||
|
|
||||||
$pageData[] = [
|
$pageData[] = [
|
||||||
'image_url' => $page['image_url'],
|
'image_url' => $page['image_url'],
|
||||||
'local_image_url' => sprintf('/manga-images/%s/%s/%s', $mangaTitle, $chapterNumber, $imageName),
|
'local_image_url' => sprintf('/manga-images/%s/%s/%s', $mangaTitle, $chapterNumber, $imageName),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use Symfony\Component\String\Slugger\SluggerInterface;
|
|||||||
|
|
||||||
readonly class MangadexProvider implements MetadataProviderInterface
|
readonly class MangadexProvider implements MetadataProviderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private ClientInterface $client, private SluggerInterface $slugger)
|
public function __construct(private ClientInterface $client, private SluggerInterface $slugger, private NotificationService $notificationService)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,12 +22,17 @@ readonly class MangadexProvider implements MetadataProviderInterface
|
|||||||
return new ArrayCollection();
|
return new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
$results = $this->client->get('/manga', [
|
$results = $this->client->get('/manga', [
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'contentRating' => ['safe', 'suggestive'],
|
'contentRating' => ['safe', 'suggestive'],
|
||||||
'includes' => ['cover_art', 'author'],
|
'includes' => ['cover_art', 'author'],
|
||||||
'limit' => 25
|
'limit' => 25
|
||||||
]);
|
]);
|
||||||
|
}catch(\Exception $e){
|
||||||
|
$this->notificationService->sendUpdate('notification', ['status' => 'error', 'message' => 'An error occurred while fetching data from Mangadex.']);
|
||||||
|
return new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
$mangas = [];
|
$mangas = [];
|
||||||
foreach ($results['data'] as $result) {
|
foreach ($results['data'] as $result) {
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ class NotificationService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendUpdate(string $topic, mixed $data): void
|
public function sendUpdate(mixed $data): void
|
||||||
{
|
{
|
||||||
$update = new Update($topic, json_encode($data));
|
$update = new Update('notification', json_encode($data));
|
||||||
$this->hub->publish($update);
|
$this->hub->publish($update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
templates/activity/index.html.twig
Normal file
39
templates/activity/index.html.twig
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% block body %}
|
||||||
|
{# TODO styliser cette page #}
|
||||||
|
<table class="min-w-full bg-white">
|
||||||
|
<thead class="bg-gray-800 text-white">
|
||||||
|
<tr>
|
||||||
|
<th class="w-1/12 py-2 px-4">
|
||||||
|
<input type="checkbox">
|
||||||
|
</th>
|
||||||
|
<th class="w-2/12 py-2 px-4 text-left">Manga</th>
|
||||||
|
<th class="w-1/12 py-2 px-4 text-left">Volume</th>
|
||||||
|
<th class="w-4/12 py-2 px-4 text-left">Chapter</th>
|
||||||
|
<th class="w-1/12 py-2 px-4 text-left">Title</th>
|
||||||
|
<th class="w-1/12 py-2 px-4 text-left">Actions</th>
|
||||||
|
|
||||||
|
<th class="w-1/12 py-2 px-4"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-gray-700">
|
||||||
|
{% for manga in status %}
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="py-2 px-4 text-center">
|
||||||
|
<input type="checkbox">
|
||||||
|
</td>
|
||||||
|
<td class="py-2 px-4">{{ manga.manga }}</td>
|
||||||
|
<td class="py-2 px-4">{{ manga.volume }}</td>
|
||||||
|
<td class="py-2 px-4">{{ manga.chapter }}</td>
|
||||||
|
<td class="py-2 px-4">{{ manga.title }}</td>
|
||||||
|
<td class="py-2 px-4 text-center">
|
||||||
|
<button class="text-red-500 hover:text-red-700">×</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="mt-4">
|
||||||
|
<span>Total records: {{ status|length }}</span>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -21,9 +21,18 @@
|
|||||||
<i class="fas fa-calendar-alt mr-2"></i>
|
<i class="fas fa-calendar-alt mr-2"></i>
|
||||||
<span>Calendrier</span>
|
<span>Calendrier</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="mb-4 pl-8 flex items-center hover:text-green-600">
|
<li class="mb-4 pl-8 hover:text-green-600">
|
||||||
|
<a href="{{ path('app_activity') }}">
|
||||||
|
<div data-controller="activity" class="flex flew-row justify-between">
|
||||||
|
<div class="flex flex-row">
|
||||||
<i class="fas fa-clock mr-2"></i>
|
<i class="fas fa-clock mr-2"></i>
|
||||||
<span>Activité</span>
|
<span>Activité</span>
|
||||||
|
</div>
|
||||||
|
{# TODO le texte doit être blanc au survol #}
|
||||||
|
<span data-activity-target="activity"
|
||||||
|
class="bg-green-500 rounded px-2 mr-4 hover:text-white hidden"></span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="mb-4 pl-8 flex items-center hover:text-green-600">
|
<li class="mb-4 pl-8 flex items-center hover:text-green-600">
|
||||||
<i class="fas fa-cog mr-2"></i>
|
<i class="fas fa-cog mr-2"></i>
|
||||||
|
|||||||
Reference in New Issue
Block a user