Compare commits
1 Commits
05d66d7cf4
...
098612dbb4
| Author | SHA1 | Date | |
|---|---|---|---|
| 098612dbb4 |
@ -31,6 +31,9 @@ public class RabbitConfig {
|
||||
@Value("${queues.manga-cover-update}")
|
||||
private String mangaCoverUpdateQueue;
|
||||
|
||||
@Value("${queues.manga-content-image-update}")
|
||||
private String mangaContentImageUpdateQueue;
|
||||
|
||||
@Value("${queues.image-fetch}")
|
||||
private String imageFetchQueue;
|
||||
|
||||
@ -52,6 +55,11 @@ public class RabbitConfig {
|
||||
return new Queue(mangaUpdateQueue, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue mangaContentImageUpdateQueue() {
|
||||
return new Queue(mangaContentImageUpdateQueue, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue mangaCoverUpdateQueue() {
|
||||
return new Queue(mangaCoverUpdateQueue, false);
|
||||
@ -68,6 +76,17 @@ public class RabbitConfig {
|
||||
null);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding bindingMangaContentImageUpdateQueue(
|
||||
Queue mangaContentImageUpdateQueue, TopicExchange imageUpdatesExchange) {
|
||||
return new Binding(
|
||||
mangaContentImageUpdateQueue.getName(),
|
||||
Binding.DestinationType.QUEUE,
|
||||
imageUpdatesExchange.getName(),
|
||||
String.format("image.update.%s", ContentType.CONTENT_IMAGE.name().toLowerCase()),
|
||||
null);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue mangaContentIngestQueue() {
|
||||
return new Queue(mangaContentIngestQueue, false);
|
||||
|
||||
@ -3,5 +3,6 @@ package com.magamochi.common.model.enumeration;
|
||||
public enum ContentType {
|
||||
MANGA_COVER,
|
||||
CHAPTER,
|
||||
VOLUME
|
||||
VOLUME,
|
||||
CONTENT_IMAGE,
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import com.magamochi.content.model.entity.MangaContentImage;
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface MangaChapterImageRepository extends JpaRepository<MangaContentImage, Long> {
|
||||
public interface MangaContentImageRepository extends JpaRepository<MangaContentImage, Long> {
|
||||
List<MangaContentImage> findAllByMangaContent(MangaContent mangaContent);
|
||||
|
||||
boolean existsByMangaContent_IdAndPosition(Long mangaContentId, int position);
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.magamochi.content.queue.consumer;
|
||||
|
||||
import com.magamochi.common.queue.command.MangaContentImageIngestCommand;
|
||||
import com.magamochi.content.service.ContentIngestService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Log4j2
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MangaContentImageIngestConsumer {
|
||||
private final ContentIngestService contentIngestService;
|
||||
|
||||
@RabbitListener(queues = "${queues.manga-content-image-ingest}")
|
||||
public void receiveMangaContentImageIngestCommand(MangaContentImageIngestCommand command) {
|
||||
log.info("Received manga content ingest command: {}", command);
|
||||
contentIngestService.ingestImages(
|
||||
command.mangaContentId(), command.url(), command.position(), command.isLast());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.magamochi.content.queue.consumer;
|
||||
|
||||
import com.magamochi.common.queue.command.ImageUpdateCommand;
|
||||
import com.magamochi.content.service.ContentIngestService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Log4j2
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MangaContentImageUpdateConsumer {
|
||||
private final ContentIngestService contentIngestService;
|
||||
|
||||
@RabbitListener(queues = "${queues.manga-content-image-update}")
|
||||
public void receiveMangaContentImageUpdateCommand(ImageUpdateCommand command) {
|
||||
log.info("Received manga content image update command: {}", command);
|
||||
contentIngestService.updateMangaContentImage(command.entityId(), command.imageId());
|
||||
}
|
||||
}
|
||||
@ -2,21 +2,35 @@ package com.magamochi.content.service;
|
||||
|
||||
import com.magamochi.catalog.service.LanguageService;
|
||||
import com.magamochi.catalog.service.MangaContentProviderService;
|
||||
import com.magamochi.common.exception.NotFoundException;
|
||||
import com.magamochi.common.model.enumeration.ContentType;
|
||||
import com.magamochi.common.queue.command.ImageFetchCommand;
|
||||
import com.magamochi.common.queue.producer.ImageFetchProducer;
|
||||
import com.magamochi.content.model.entity.MangaContent;
|
||||
import com.magamochi.content.model.entity.MangaContentImage;
|
||||
import com.magamochi.content.model.repository.MangaContentImageRepository;
|
||||
import com.magamochi.content.model.repository.MangaContentRepository;
|
||||
import com.magamochi.image.service.ImageService;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Log4j2
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ContentIngestService {
|
||||
private final ContentService contentService;
|
||||
private final MangaContentProviderService mangaContentProviderService;
|
||||
private final LanguageService languageService;
|
||||
|
||||
private final MangaContentRepository mangaContentRepository;
|
||||
private final MangaContentImageRepository mangaContentImageRepository;
|
||||
|
||||
private final ImageFetchProducer imageFetchProducer;
|
||||
private final ImageService imageService;
|
||||
|
||||
public void ingest(
|
||||
long mangaContentProviderId,
|
||||
@ -53,4 +67,40 @@ public class ContentIngestService {
|
||||
mangaContentProviderId,
|
||||
mangaContent.getId());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void ingestImages(
|
||||
long mangaContentId, @NotBlank String url, int position, boolean isLast) {
|
||||
log.info(
|
||||
"Ingesting Manga Content Image for MangaContent {}, position {}", mangaContentId, position);
|
||||
|
||||
var mangaContent = contentService.find(mangaContentId);
|
||||
|
||||
if (mangaContentImageRepository.existsByMangaContent_IdAndPosition(mangaContentId, position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var mangaContentImage =
|
||||
mangaContentImageRepository.save(
|
||||
MangaContentImage.builder().mangaContent(mangaContent).position(position).build());
|
||||
|
||||
imageFetchProducer.sendImageFetchCommand(
|
||||
new ImageFetchCommand(mangaContentImage.getId(), ContentType.CONTENT_IMAGE, url));
|
||||
|
||||
if (isLast) {
|
||||
mangaContent.setDownloaded(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateMangaContentImage(long mangaContentImageId, UUID imageId) {
|
||||
var mangaContentImage =
|
||||
mangaContentImageRepository
|
||||
.findById(mangaContentImageId)
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException("Image not found for ID: " + mangaContentImageId));
|
||||
|
||||
var image = imageService.find(imageId);
|
||||
mangaContentImage.setImage(image);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.magamochi.image.service;
|
||||
|
||||
import static java.util.Objects.nonNull;
|
||||
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import com.magamochi.common.model.enumeration.ContentType;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
@ -21,6 +22,8 @@ import org.springframework.stereotype.Service;
|
||||
public class ImageFetchService {
|
||||
private final ImageService imageManagerService;
|
||||
|
||||
private final RateLimiter imageDownloadRateLimiter;
|
||||
|
||||
private final HttpClient httpClient =
|
||||
HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build();
|
||||
private final Tika tika = new Tika();
|
||||
@ -28,6 +31,8 @@ public class ImageFetchService {
|
||||
public UUID fetchImage(String imageUrl, ContentType contentType) {
|
||||
try {
|
||||
var request = HttpRequest.newBuilder(URI.create(imageUrl)).GET().build();
|
||||
|
||||
imageDownloadRateLimiter.acquire();
|
||||
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
|
||||
|
||||
var imageBytes = response.body();
|
||||
|
||||
@ -103,85 +103,4 @@ public class IngestionService {
|
||||
mangaContent.getId(), item.url(), item.position(), isLast));
|
||||
});
|
||||
}
|
||||
|
||||
// @Transactional
|
||||
// public void fetchChapter(Long chapterId) {
|
||||
//
|
||||
// var retryConfig = retryRegistry.retry("ImageDownloadRetry").getRetryConfig();
|
||||
//
|
||||
// var chapterImages =
|
||||
// chapterImagesUrls.entrySet().parallelStream()
|
||||
// .map(
|
||||
// entry -> {
|
||||
// imageDownloadRateLimiter.acquire();
|
||||
//
|
||||
// try {
|
||||
// var finalUrl = new
|
||||
// URI(entry.getValue().trim()).toASCIIString().trim();
|
||||
// var retry =
|
||||
// Retry.of("image-download-" + chapterId + "-" +
|
||||
// entry.getKey(), retryConfig);
|
||||
//
|
||||
// retry
|
||||
// .getEventPublisher()
|
||||
// .onRetry(
|
||||
// event ->
|
||||
// log.warn(
|
||||
// "Retrying image download {}/{}
|
||||
// for chapter {}. Attempt #{}. Error: {}",
|
||||
// entry.getKey() + 1,
|
||||
// chapterImagesUrls.size(),
|
||||
// chapterId,
|
||||
//
|
||||
// event.getNumberOfRetryAttempts(),
|
||||
//
|
||||
// event.getLastThrowable().getMessage()));
|
||||
//
|
||||
// return retry.executeCheckedSupplier(
|
||||
// () -> {
|
||||
// var url = new URL(finalUrl);
|
||||
// var connection = url.openConnection();
|
||||
// connection.setConnectTimeout(5000);
|
||||
// connection.setReadTimeout(5000);
|
||||
//
|
||||
// try (var inputStream =
|
||||
// new
|
||||
// BufferedInputStream(connection.getInputStream())) {
|
||||
// var bytes = inputStream.readAllBytes();
|
||||
//
|
||||
// var image =
|
||||
// oldImageService.uploadImage(
|
||||
// bytes, "image/jpeg", "chapter/" +
|
||||
// chapterId);
|
||||
//
|
||||
// log.info(
|
||||
// "Downloaded image {}/{} for manga {} chapter
|
||||
// {}: {}",
|
||||
// entry.getKey() + 1,
|
||||
// chapterImagesUrls.size(),
|
||||
//
|
||||
// chapter.getMangaContentProvider().getManga().getTitle(),
|
||||
// chapterId,
|
||||
// entry.getValue());
|
||||
//
|
||||
// return MangaContentImage.builder()
|
||||
// .mangaContent(chapter)
|
||||
// .position(entry.getKey())
|
||||
// .image(image)
|
||||
// .build();
|
||||
// }
|
||||
// });
|
||||
// } catch (Throwable e) {
|
||||
// throw new UnprocessableException(
|
||||
// "Could not download image for chapter ID: " + chapterId,
|
||||
// e);
|
||||
// }
|
||||
// })
|
||||
// .toList();
|
||||
//
|
||||
// mangaChapterImageRepository.saveAll(chapterImages);
|
||||
//
|
||||
// chapter.setDownloaded(true);
|
||||
// mangaContentRepository.save(chapter);
|
||||
// }
|
||||
}
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
package com.magamochi.service;
|
||||
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import com.magamochi.common.exception.UnprocessableException;
|
||||
import com.magamochi.content.model.entity.MangaContent;
|
||||
import com.magamochi.content.model.entity.MangaContentImage;
|
||||
import com.magamochi.content.model.repository.MangaChapterImageRepository;
|
||||
import com.magamochi.content.model.repository.MangaContentImageRepository;
|
||||
import com.magamochi.content.model.repository.MangaContentRepository;
|
||||
import com.magamochi.ingestion.providers.ContentProviderFactory;
|
||||
import com.magamochi.model.dto.MangaChapterArchiveDTO;
|
||||
import com.magamochi.model.dto.MangaChapterImagesDTO;
|
||||
import com.magamochi.model.enumeration.ArchiveFileType;
|
||||
import io.github.resilience4j.retry.RetryRegistry;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -28,15 +25,10 @@ import org.springframework.stereotype.Service;
|
||||
@RequiredArgsConstructor
|
||||
public class MangaChapterService {
|
||||
private final MangaContentRepository mangaContentRepository;
|
||||
private final MangaChapterImageRepository mangaChapterImageRepository;
|
||||
private final MangaContentImageRepository mangaContentImageRepository;
|
||||
|
||||
private final OldImageService oldImageService;
|
||||
|
||||
private final ContentProviderFactory contentProviderFactory;
|
||||
|
||||
private final RateLimiter imageDownloadRateLimiter;
|
||||
private final RetryRegistry retryRegistry;
|
||||
|
||||
public MangaChapterImagesDTO getMangaChapterImages(Long chapterId) {
|
||||
var chapter = getMangaChapterThrowIfNotFound(chapterId);
|
||||
|
||||
@ -77,7 +69,7 @@ public class MangaChapterService {
|
||||
throws IOException {
|
||||
var chapter = getMangaChapterThrowIfNotFound(chapterId);
|
||||
|
||||
var chapterImages = mangaChapterImageRepository.findAllByMangaContent(chapter);
|
||||
var chapterImages = mangaContentImageRepository.findAllByMangaContent(chapter);
|
||||
|
||||
var byteArrayOutputStream =
|
||||
switch (archiveFileType) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package com.magamochi.service;
|
||||
|
||||
import com.magamochi.image.model.entity.Image;
|
||||
import com.magamochi.image.model.repository.ImageRepository;
|
||||
import java.io.InputStream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
@ -12,14 +11,6 @@ import org.springframework.stereotype.Service;
|
||||
@RequiredArgsConstructor
|
||||
public class OldImageService {
|
||||
private final OldS3Service oldS3Service;
|
||||
private final ImageRepository imageRepository;
|
||||
|
||||
public Image uploadImage(byte[] data, String contentType, String path) {
|
||||
log.info("Uploading image {} to S3", path);
|
||||
var fileKey = oldS3Service.uploadFile(data, contentType, path);
|
||||
|
||||
return imageRepository.save(Image.builder().objectKey(fileKey).build());
|
||||
}
|
||||
|
||||
public InputStream getImageStream(Image image) {
|
||||
return oldS3Service.getFile(image.getObjectKey());
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
package com.magamochi.service;
|
||||
|
||||
import com.magamochi.catalog.model.entity.Manga;
|
||||
import com.magamochi.catalog.model.entity.MangaContentProvider;
|
||||
import com.magamochi.catalog.model.repository.MangaContentProviderRepository;
|
||||
import com.magamochi.catalog.model.repository.MangaRepository;
|
||||
import com.magamochi.client.NtfyClient;
|
||||
import com.magamochi.common.exception.NotFoundException;
|
||||
import com.magamochi.content.model.entity.MangaContent;
|
||||
import com.magamochi.content.model.repository.MangaContentRepository;
|
||||
import com.magamochi.ingestion.providers.ContentProviderFactory;
|
||||
import com.magamochi.model.dto.*;
|
||||
import com.magamochi.model.entity.UserMangaFollow;
|
||||
import com.magamochi.model.repository.*;
|
||||
@ -28,14 +24,9 @@ public class OldMangaService {
|
||||
private final MangaRepository mangaRepository;
|
||||
private final MangaContentProviderRepository mangaContentProviderRepository;
|
||||
|
||||
private final ContentProviderFactory contentProviderFactory;
|
||||
|
||||
private final UserMangaFollowRepository userMangaFollowRepository;
|
||||
|
||||
private final MangaChapterDownloadProducer mangaChapterDownloadProducer;
|
||||
private final MangaContentRepository mangaContentRepository;
|
||||
|
||||
private final NtfyClient ntfyClient;
|
||||
|
||||
public void fetchAllNotDownloadedChapters(Long mangaProviderId) {
|
||||
var mangaProvider =
|
||||
@ -96,13 +87,6 @@ public class OldMangaService {
|
||||
.orElseThrow(() -> new NotFoundException("Manga not found for ID: " + mangaId));
|
||||
}
|
||||
|
||||
private MangaContentProvider getMangaProviderThrowIfNotFound(Long mangaProviderId) {
|
||||
return mangaContentProviderRepository
|
||||
.findById(mangaProviderId)
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException("Manga Provider not found for ID: " + mangaProviderId));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void follow(Long mangaId) {
|
||||
var user = userService.getLoggedUserThrowIfNotFound();
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
package com.magamochi.service;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import software.amazon.awssdk.core.sync.RequestBody;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.model.*;
|
||||
|
||||
@ -17,17 +15,6 @@ public class OldS3Service {
|
||||
|
||||
private final S3Client s3Client;
|
||||
|
||||
public String uploadFile(byte[] data, String contentType, String path) {
|
||||
var filename = "manga/" + path + "/" + UUID.randomUUID();
|
||||
|
||||
var request =
|
||||
PutObjectRequest.builder().bucket(bucket).key(filename).contentType(contentType).build();
|
||||
|
||||
s3Client.putObject(request, RequestBody.fromBytes(data));
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
public InputStream getFile(String key) {
|
||||
var request = GetObjectRequest.builder().bucket(bucket).key(key).build();
|
||||
|
||||
|
||||
@ -100,6 +100,7 @@ queues:
|
||||
manga-content-image-ingest: ${MANGA_CONTENT_IMAGE_INGEST_QUEUE:mangaContentImageIngest}
|
||||
provider-page-ingest: ${PROVIDER_PAGE_INGEST_QUEUE:providerPageIngest}
|
||||
manga-update: ${MANGA_UPDATE_QUEUE:mangaUpdate}
|
||||
manga-content-image-update: ${MANGA_CONTENT_IMAGE_UPDATE_QUEUE:mangaContentImageUpdate}
|
||||
image-fetch: ${IMAGE_FETCH_QUEUE:imageFetch}
|
||||
manga-cover-update: ${MANGA_COVER_UDPATE_QUEUE:mangaCoverUpdate}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user