improvements #26

Merged
rov merged 2 commits from improvements into main 2025-12-31 20:03:58 -03:00
5 changed files with 79 additions and 30 deletions
Showing only changes of commit b736178709 - Show all commits

View File

@ -14,11 +14,15 @@ public interface MangaDexClient {
MangaDexMangaDTO getManga(@PathVariable UUID id);
@GetMapping("/manga/{id}/feed")
MangaDexMangaFeedDTO getMangaFeed(@PathVariable UUID id, @RequestParam("contentRating[]") List<String> contentRating);
MangaDexMangaFeedDTO getMangaFeed(
@PathVariable UUID id, @RequestParam("contentRating[]") List<String> contentRating);
@GetMapping("/manga/{id}/feed")
MangaDexMangaFeedDTO getMangaFeed(
@PathVariable UUID id, @RequestParam int limit, @RequestParam int offset, @RequestParam("contentRating[]") List<String> contentRating);
@PathVariable UUID id,
@RequestParam int limit,
@RequestParam int offset,
@RequestParam("contentRating[]") List<String> contentRating);
@GetMapping("/at-home/server/{chapterId}")
MangaChapterDataDTO getMangaChapter(@PathVariable UUID chapterId);

View File

@ -4,4 +4,8 @@ public class UnprocessableException extends RuntimeException {
public UnprocessableException(String message) {
super(message);
}
public UnprocessableException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -10,12 +10,13 @@ import com.magamochi.mangamochi.model.enumeration.ArchiveFileType;
import com.magamochi.mangamochi.model.repository.MangaChapterImageRepository;
import com.magamochi.mangamochi.model.repository.MangaChapterRepository;
import com.magamochi.mangamochi.service.providers.ContentProviderFactory;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryRegistry;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Comparator;
import java.util.List;
@ -39,6 +40,7 @@ public class MangaChapterService {
private final ContentProviderFactory contentProviderFactory;
private final RateLimiter imageDownloadRateLimiter;
private final RetryRegistry retryRegistry;
@Transactional
public void fetchChapter(Long chapterId) {
@ -53,6 +55,8 @@ public class MangaChapterService {
"No images found on provider for Manga Chapter ID: " + chapterId);
}
var retryConfig = retryRegistry.retry("ImageDownloadRetry").getRetryConfig();
var chapterImages =
chapterImagesUrls.entrySet().parallelStream()
.map(
@ -60,33 +64,55 @@ public class MangaChapterService {
imageDownloadRateLimiter.acquire();
try {
var inputStream =
new BufferedInputStream(
new URL(new URI(entry.getValue().trim()).toASCIIString().trim())
.openStream());
var finalUrl = new URI(entry.getValue().trim()).toASCIIString().trim();
var retry =
Retry.of("image-download-" + chapterId + "-" + entry.getKey(), retryConfig);
var bytes = inputStream.readAllBytes();
retry
.getEventPublisher()
.onRetry(
event ->
log.warn(
"Retrying image download {}/{} for chapter {}. Attempt #{}. Error: {}",
entry.getKey() + 1,
chapterImagesUrls.size(),
chapterId,
event.getNumberOfRetryAttempts(),
event.getLastThrowable().getMessage()));
inputStream.close();
var image =
imageService.uploadImage(bytes, "image/jpeg", "chapter/" + chapterId);
return retry.executeCheckedSupplier(
() -> {
var url = new URL(finalUrl);
var connection = url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
log.info(
"Downloaded image {}/{} for manga {} chapter {}: {}",
entry.getKey() + 1,
chapterImagesUrls.size(),
chapter.getMangaProvider().getManga().getTitle(),
chapterId,
entry.getValue());
try (var inputStream =
new BufferedInputStream(connection.getInputStream())) {
var bytes = inputStream.readAllBytes();
return MangaChapterImage.builder()
.mangaChapter(chapter)
.position(entry.getKey())
.image(image)
.build();
} catch (IOException | URISyntaxException e) {
var image =
imageService.uploadImage(
bytes, "image/jpeg", "chapter/" + chapterId);
log.info(
"Downloaded image {}/{} for manga {} chapter {}: {}",
entry.getKey() + 1,
chapterImagesUrls.size(),
chapter.getMangaProvider().getManga().getTitle(),
chapterId,
entry.getValue());
return MangaChapterImage.builder()
.mangaChapter(chapter)
.position(entry.getKey())
.image(image)
.build();
}
});
} catch (Throwable e) {
throw new UnprocessableException(
"Could not download image for chapter ID: " + chapterId);
"Could not download image for chapter ID: " + chapterId, e);
}
})
.toList();

View File

@ -1,5 +1,7 @@
package com.magamochi.mangamochi.service.providers.impl;
import static java.util.Objects.isNull;
import com.google.common.util.concurrent.RateLimiter;
import com.magamochi.mangamochi.client.MangaDexClient;
import com.magamochi.mangamochi.exception.UnprocessableException;
@ -15,8 +17,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import static java.util.Objects.isNull;
@Log4j2
@Service(ContentProviders.MANGA_DEX)
@RequiredArgsConstructor
@ -29,7 +29,10 @@ public class MangaDexProvider implements ContentProvider, ManualImportContentPro
public List<ContentProviderMangaChapterResponseDTO> getAvailableChapters(MangaProvider provider) {
try {
mangaDexRateLimiter.acquire();
var response = mangaDexClient.getMangaFeed(UUID.fromString(provider.getUrl()), List.of("safe", "suggestive", "erotica", "pornographic"));
var response =
mangaDexClient.getMangaFeed(
UUID.fromString(provider.getUrl()),
List.of("safe", "suggestive", "erotica", "pornographic"));
var mangas = new ArrayList<>(response.data());
var totalPages = (int) Math.ceil((double) response.total() / 500);
@ -42,7 +45,11 @@ public class MangaDexProvider implements ContentProvider, ManualImportContentPro
mangaDexRateLimiter.acquire();
var pagedResponse =
mangaDexClient.getMangaFeed(UUID.fromString(provider.getUrl()), 500, i * 500, List.of("safe", "suggestive", "erotica", "pornographic"));
mangaDexClient.getMangaFeed(
UUID.fromString(provider.getUrl()),
500,
i * 500,
List.of("safe", "suggestive", "erotica", "pornographic"));
mangas.addAll(pagedResponse.data());
});
@ -50,7 +57,8 @@ public class MangaDexProvider implements ContentProvider, ManualImportContentPro
log.warn(e.getMessage());
}
// NOTE: this is getting only pt-br and en chapters for now, we may want to make this configurable
// NOTE: this is getting only pt-br and en chapters for now, we may want to make this
// configurable
// later
var languagesToImport = Map.of("pt-br", "pt-BR", "en", "en-US");

View File

@ -74,6 +74,13 @@ resilience4j:
seconds: 5
retry-exceptions:
- feign.FeignException
ImageDownloadRetry:
max-attempts: 3
wait-duration:
seconds: 5
retry-exceptions:
- java.io.IOException
- java.net.SocketTimeoutException
rabbit-mq:
queues: