- toogle chapter visibility
- delete chapter cbz
- preferred ContentSource.php and modal
- minor fixes
This commit is contained in:
Jérémy Guillot
2024-07-21 22:21:04 +02:00
parent fafff5014c
commit c56f72b813
17 changed files with 474 additions and 82 deletions

View File

@@ -9,6 +9,7 @@ use App\Manager\Toolbar\Factory\ToolbarFactory;
use App\Message\DownloadChapter;
use App\Message\RefreshMetadata;
use App\Repository\ChapterRepository;
use App\Repository\ContentSourceRepository;
use App\Repository\MangaRepository;
use App\Service\CbzService;
use App\Service\MangadexProvider;
@@ -37,16 +38,17 @@ class MangaController extends AbstractController
private ImageManager $imageManager;
public function __construct(
private readonly string $projectDir,
private readonly MangaRepository $mangaRepository,
private readonly ChapterRepository $chapterRepository,
private readonly MessageBusInterface $bus,
private readonly CbzService $cbzService,
private readonly ToolbarFactory $toolbarFactory,
private readonly MangadexProvider $mangadexProvider,
private readonly EntityManagerInterface $entityManager,
private readonly NotificationService $notificationService,
private readonly SluggerInterface $slugger
private readonly string $projectDir,
private readonly MangaRepository $mangaRepository,
private readonly ChapterRepository $chapterRepository,
private readonly MessageBusInterface $bus,
private readonly CbzService $cbzService,
private readonly ToolbarFactory $toolbarFactory,
private readonly MangadexProvider $mangadexProvider,
private readonly EntityManagerInterface $entityManager,
private readonly NotificationService $notificationService,
private readonly SluggerInterface $slugger,
private readonly ContentSourceRepository $contentSourceRepository
)
{
$this->imageManager = new ImageManager(new Driver());
@@ -81,11 +83,13 @@ class MangaController extends AbstractController
}
$form = $this->createForm(MangaEditType::class, $manga);
$contentSources = $this->contentSourceRepository->findAll();
return $this->render('manga/show_chapters.html.twig', [
'manga' => $manga,
'toolbar' => $this->toolbarFactory->createToolbar('chapter_list', ['mangaId' => $manga->getId(), 'isMonitored' => (int) $manga->isMonitored()])->getGroups(),
'toolbar' => $this->toolbarFactory->createToolbar('chapter_list', ['mangaId' => $manga->getId(), 'isMonitored' => (int)$manga->isMonitored()])->getGroups(),
'form' => $form->createView(),
'contentSources' => $contentSources,
]);
}
@@ -94,6 +98,7 @@ class MangaController extends AbstractController
{
try {
foreach ($manga->getChapters() as $chapter) {
file_exists($chapter->getCbzPath()) ?? unlink($chapter->getCbzPath());
$this->entityManager->remove($chapter);
}
$this->entityManager->remove($manga);
@@ -125,6 +130,30 @@ class MangaController extends AbstractController
return new JsonResponse(['errors' => $errors], 400);
}
#[Route('/manga/{id}/preferred-sources', name: 'manga_preferred_sources', methods: ['POST'])]
public function updatePreferredSources(
Request $request,
Manga $manga,
ContentSourceRepository $contentSourceRepository
): JsonResponse
{
$data = json_decode($request->getContent(), true);
$preferredSourceIds = $data['preferredSources'] ?? [];
$preferredSources = $contentSourceRepository->findBy(['id' => $preferredSourceIds]);
// This will maintain the order of the sources as they were sent in the request
$orderedPreferredSources = array_map(
fn($id) => current(array_filter($preferredSources, fn($s) => $s->getId() == $id)),
$preferredSourceIds
);
$manga->setPreferredSources(array_filter($orderedPreferredSources));
$this->entityManager->flush();
return new JsonResponse(['success' => true]);
}
public function _chaptersByManga(int $id): Response
{
$manga = $this->mangaRepository->find($id);
@@ -157,6 +186,33 @@ class MangaController extends AbstractController
]);
}
#[Route('/delete_cbz/{id}', name: 'app_delete_cbz')]
public function deleteChapterCbz(Chapter $chapter): JsonResponse
{
$cbzPath = $chapter->getCbzPath();
if (!$cbzPath) {
return new JsonResponse(['error' => 'No CBZ path for this chapter.'], 400);
}
file_exists($cbzPath) ?? unlink($cbzPath);
$chapter->setCbzPath(null);
$this->entityManager->persist($chapter);
$this->entityManager->flush();
return new JsonResponse(['success' => 'CBZ file deleted.'], 200);
}
#[Route('/hide_chapter/{id}', name: 'app_hide_chapter')]
public function hideChapter(Chapter $chapter): JsonResponse
{
$chapter->setVisible(false);
$this->entityManager->persist($chapter);
$this->entityManager->flush();
return new JsonResponse(['success' => 'Chapter hidden.'], 200);
}
#[Route('/manga/read/{mangaSlug}/{chapterNumber}/{pageNumber}', name: 'app_manga_read')]
public function readChapterPage(string $mangaSlug, float $chapterNumber, int $pageNumber = 1): Response
{
@@ -171,17 +227,7 @@ class MangaController extends AbstractController
}
if (is_null($chapter->getCbzPath())) {
$currentPage = $chapter->getPageByNumber($pageNumber);
if (!$currentPage) {
throw $this->createNotFoundException("La page demandée n'existe pas.");
}
return $this->render('manga/manga_reader.html.twig', [
'manga' => $manga,
'chapter' => $chapter,
'pages' => $chapter->getPagesLink(),
'currentPage' => $currentPage,
]);
throw $this->createNotFoundException("Le chapitre demandé n'a pas été scrapé.");
}
$pageContent = $this->cbzService->getPageContent($chapter->getCbzPath(), $pageNumber);
@@ -226,7 +272,9 @@ class MangaController extends AbstractController
->setAuthor($request->request->get('author'))
->setPublicationYear($request->request->get('publicationYear'))
->setRating($request->request->get('rating'))
->setExternalId($request->request->get('externalId'));
->setExternalId($request->request->get('externalId'))
->setMonitored(false)
;
// Traitement de l'image
$imageUrl = $request->request->get('imageUrl');
@@ -327,7 +375,8 @@ class MangaController extends AbstractController
$volumeChapters = $this->chapterRepository->findBy([
'manga' => $manga,
'volume' => $volume
'volume' => $volume,
'visible' => true
]);
if (empty($volumeChapters)) {
@@ -374,7 +423,8 @@ class MangaController extends AbstractController
$volumeChapters = $this->chapterRepository->findBy([
'manga' => $manga,
'volume' => $volume
'volume' => $volume,
'visible' => true
]);
if (empty($volumeChapters)) {

View File

@@ -45,6 +45,9 @@ class Chapter
#[ORM\Column(length: 255, nullable: true)]
private ?string $cbzPath = null;
#[ORM\Column]
private ?bool $visible = true;
public function __construct()
{
$this->pagesLink = new ArrayCollection();
@@ -194,4 +197,16 @@ class Chapter
return $this;
}
public function isVisible(): ?bool
{
return $this->visible;
}
public function setVisible(bool $visible): static
{
$this->visible = $visible;
return $this;
}
}

View File

@@ -118,4 +118,13 @@ class ContentSource
return $this;
}
public function getCleanBaseUrl(): string
{
return preg_replace(
'/^(https?:\/\/)?(www\.)?|\/+$/',
'',
$this->baseUrl
);
}
}

View File

@@ -62,10 +62,14 @@ class Manga
#[ORM\Column(type: Types::JSON, nullable: true)]
private ?array $AlternativeSlugs = null;
#[ORM\ManyToMany(targetEntity: ContentSource::class)]
private Collection $preferredSources;
public function __construct()
{
$this->chapters = new ArrayCollection();
$this->createdAt = new \DateTimeImmutable();
$this->preferredSources = new ArrayCollection();
}
public function getId(): ?int
@@ -280,4 +284,38 @@ class Manga
return $this;
}
/**
* @return Collection<int, ContentSource>
*/
public function getPreferredSources(): Collection
{
return $this->preferredSources;
}
public function addPreferredSource(ContentSource $preferredSource): static
{
if (!$this->preferredSources->contains($preferredSource)) {
$this->preferredSources->add($preferredSource);
}
return $this;
}
public function removePreferredSource(ContentSource $preferredSource): static
{
$this->preferredSources->removeElement($preferredSource);
return $this;
}
public function setPreferredSources(array $sources): self
{
$this->preferredSources->clear();
foreach ($sources as $source) {
$this->addPreferredSource($source);
}
return $this;
}
}

View File

@@ -17,7 +17,7 @@ class ChapterListToolbar extends Toolbar
->addToLeftGroup(new ToolbarDivider())
->addToLeftGroup(new ToolbarButton('keyboard', 'Rename chapters', 'toolbar#renameChapters'))
->addToLeftGroup(new ToolbarButton('file-zipper', 'Manage cbz', 'toolbar#manageCbz', $contextData))
->addToLeftGroup(new ToolbarButton('history', 'History', 'toolbar#history', $contextData))
->addToLeftGroup(new ToolbarButton('gear', 'Preferred Sources', 'toolbar#editPreferredSources', $contextData))
->addToRightGroup(new ToolbarButton('bookmark', $monitoredTitle, 'toolbar#monitoring', array_merge($contextData, ['buttonClass' => $monitoredColor])))

View File

@@ -6,8 +6,8 @@ use App\Entity\ContentSource;
use App\Message\DownloadChapter;
use App\Repository\ChapterRepository;
use App\Repository\ContentSourceRepository;
use App\Service\MangaScraperService;
use App\Service\NotificationService;
use App\Service\Scraper\MangaScraperService;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -40,7 +40,16 @@ readonly class DownloadChapterHandler
throw new BadRequestHttpException('Chapter already downloaded');
}
$sources = $this->contentSourceRepository->findAll();
$manga = $chapter->getManga();
$preferredSources = $manga->getPreferredSources()->toArray();
$allSources = $this->contentSourceRepository->findAll();
$filteredSources = array_udiff($allSources, $preferredSources, function ($a, $b) {
return $a->getId() - $b->getId();
});
$sources = array_merge($preferredSources, $filteredSources);
$sources[] =
(new ContentSource())
->setBaseUrl('https://api.mangadex.org/')

View File

@@ -227,7 +227,6 @@ readonly class MangadexProvider implements MetadataProviderInterface
$mergedChapters = [];
foreach ($allChapters as $chapter) {
$number = $chapter->getNumber();
$existingChapter = $manga->getChapterByNumber($number);
if ($existingChapter) {
if ($existingChapter->getExternalId() !== $chapter->getExternalId() && is_null($existingChapter->getExternalId())) {

View File

@@ -37,6 +37,7 @@ class JavascriptScraper extends AbstractScraper
$imagePath = $tempDir . '/' . $imageName;
file_put_contents($imagePath, file_get_contents($page['image_url']));
$this->dispatchProgressEvent($chapter, $index + 1, count($pageData));
$page['local_image_url'] = $imagePath;
}
@@ -95,7 +96,7 @@ class JavascriptScraper extends AbstractScraper
{
$chapterSelector = $contentSource->getChapterSelector();
if (!$chapterSelector) {
return; // Si aucun sélecteur n'est défini, on ne fait rien
return;
}
$crawler = $pantherClient->waitFor($chapterSelector);