improvements #26
@ -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);
|
||||
|
||||
@ -4,4 +4,8 @@ public class UnprocessableException extends RuntimeException {
|
||||
public UnprocessableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnprocessableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user