2025-12-22 14:07:44 +07:00
|
|
|
package ru.soune.nocopy.handler;
|
|
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
2026-05-06 12:59:38 +07:00
|
|
|
import com.vrt.NoCopyFileService;
|
|
|
|
|
import com.vrt.fileprotection.FileProtector;
|
|
|
|
|
import com.vrt.fileprotection.NoCopyCheckResult;
|
2025-12-22 14:07:44 +07:00
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
2026-03-20 15:26:20 +07:00
|
|
|
import ru.soune.nocopy.configuration.search.SearchProperties;
|
2025-12-22 14:07:44 +07:00
|
|
|
import ru.soune.nocopy.dto.BaseRequest;
|
|
|
|
|
import ru.soune.nocopy.dto.BaseResponse;
|
|
|
|
|
import ru.soune.nocopy.dto.MessageCode;
|
|
|
|
|
import ru.soune.nocopy.dto.file.ImageSearchRequest;
|
2026-02-11 17:41:48 +07:00
|
|
|
import ru.soune.nocopy.dto.file.YandexSearchResponse;
|
2026-01-28 23:25:16 +07:00
|
|
|
import ru.soune.nocopy.entity.file.FileEntity;
|
2026-04-29 17:16:34 +07:00
|
|
|
import ru.soune.nocopy.entity.file.ImageHashEntity;
|
2026-04-10 20:13:59 +07:00
|
|
|
import ru.soune.nocopy.entity.tokenoperation.OperationType;
|
2026-01-28 23:25:16 +07:00
|
|
|
import ru.soune.nocopy.exception.NotValidFieldException;
|
|
|
|
|
import ru.soune.nocopy.repository.FileEntityRepository;
|
2026-04-29 17:16:34 +07:00
|
|
|
import ru.soune.nocopy.repository.ImageHashRepository;
|
2026-04-29 14:53:45 +07:00
|
|
|
import ru.soune.nocopy.repository.SimilarImageProjection;
|
|
|
|
|
import ru.soune.nocopy.service.FileSimilarityService;
|
|
|
|
|
import ru.soune.nocopy.service.ImageHashService;
|
2026-01-28 23:25:16 +07:00
|
|
|
import ru.soune.nocopy.service.file.CheckCounterService;
|
2026-04-29 14:53:45 +07:00
|
|
|
import ru.soune.nocopy.service.file.FileUploadService;
|
2026-02-11 17:41:48 +07:00
|
|
|
import ru.soune.nocopy.service.search.SearchImageService;
|
2026-02-05 17:25:50 +07:00
|
|
|
import ru.soune.nocopy.service.tariff.TariffConstants;
|
|
|
|
|
import ru.soune.nocopy.service.tariff.TariffInfoService;
|
2025-12-22 14:07:44 +07:00
|
|
|
|
2026-04-29 18:14:15 +07:00
|
|
|
import javax.imageio.ImageIO;
|
|
|
|
|
import java.awt.image.BufferedImage;
|
2026-05-06 13:08:34 +07:00
|
|
|
import java.io.File;
|
2026-02-10 13:26:44 +07:00
|
|
|
import java.io.IOException;
|
2026-04-29 14:53:45 +07:00
|
|
|
import java.nio.file.Files;
|
|
|
|
|
import java.nio.file.Path;
|
|
|
|
|
import java.nio.file.Paths;
|
2026-02-12 11:58:05 +07:00
|
|
|
import java.util.*;
|
2026-02-10 13:26:44 +07:00
|
|
|
import java.util.concurrent.TimeoutException;
|
2026-01-28 23:25:16 +07:00
|
|
|
|
2025-12-22 14:07:44 +07:00
|
|
|
@Slf4j
|
|
|
|
|
@Component
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public class ImageFoundRequestHandler implements RequestHandler {
|
|
|
|
|
|
|
|
|
|
private final ObjectMapper objectMapper;
|
|
|
|
|
|
2026-02-11 17:41:48 +07:00
|
|
|
private final SearchImageService searchImageService;
|
2025-12-22 14:07:44 +07:00
|
|
|
|
2026-01-28 23:25:16 +07:00
|
|
|
private final CheckCounterService checkCounterService;
|
|
|
|
|
|
|
|
|
|
private final FileEntityRepository fileEntityRepository;
|
|
|
|
|
|
2026-02-05 17:25:50 +07:00
|
|
|
private final TariffInfoService tariffInfoService;
|
|
|
|
|
|
2026-03-20 15:26:20 +07:00
|
|
|
private final SearchProperties searchProperties;
|
|
|
|
|
|
2026-04-29 14:53:45 +07:00
|
|
|
private final FileUploadService fileUploadService;
|
|
|
|
|
|
|
|
|
|
private final ImageHashService imageHashService;
|
|
|
|
|
|
2026-04-29 17:16:34 +07:00
|
|
|
private final ImageHashRepository imageHashRepository;
|
|
|
|
|
|
2026-04-29 14:53:45 +07:00
|
|
|
private final FileSimilarityService fileSimilarityService;
|
|
|
|
|
|
2026-05-06 12:59:38 +07:00
|
|
|
private final NoCopyFileService noCopyFileService;
|
|
|
|
|
|
2025-12-22 14:07:44 +07:00
|
|
|
@Override
|
|
|
|
|
public BaseResponse handle(BaseRequest request) throws Exception {
|
2025-12-22 14:42:27 +07:00
|
|
|
ImageSearchRequest imageSearchRequest = objectMapper.convertValue(request.getMessageBody(),
|
|
|
|
|
ImageSearchRequest.class);
|
2025-12-22 14:07:44 +07:00
|
|
|
|
|
|
|
|
String fileId = imageSearchRequest.getFileId();
|
|
|
|
|
|
2026-01-28 23:25:16 +07:00
|
|
|
FileEntity fileEntity = fileEntityRepository.findById(fileId)
|
|
|
|
|
.orElseThrow(() -> {
|
|
|
|
|
throw new NotValidFieldException("File not found", new BaseResponse(20007,
|
|
|
|
|
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(),
|
2026-03-20 15:26:20 +07:00
|
|
|
Map.of("fileId", fileId)));});
|
2026-03-20 15:26:20 +07:00
|
|
|
|
2026-03-20 15:26:20 +07:00
|
|
|
List<YandexSearchResponse.ImageResult> allYandexImages = new ArrayList<>();
|
|
|
|
|
List<YandexSearchResponse.ImageResult> allGoogleImages = new ArrayList<>();
|
2026-03-20 16:02:11 +07:00
|
|
|
boolean hasTimeout = false;
|
|
|
|
|
|
2026-03-20 15:26:20 +07:00
|
|
|
boolean useGoogle = searchProperties.getEngines().getOrDefault("google",
|
|
|
|
|
new SearchProperties.EngineConfig()).isEnabled();
|
|
|
|
|
boolean useYandex = searchProperties.getEngines().getOrDefault("yandex",
|
|
|
|
|
new SearchProperties.EngineConfig()).isEnabled();
|
2026-03-20 16:02:11 +07:00
|
|
|
|
2026-03-20 15:26:20 +07:00
|
|
|
log.info("Search settings: useGoogle={}, useYandex={}", useGoogle, useYandex);
|
|
|
|
|
|
|
|
|
|
if (useGoogle) {
|
|
|
|
|
try {
|
|
|
|
|
String searchResponseGoogle = searchImageService.searchReverseByPublicUrl(fileEntity, "google_lens",
|
|
|
|
|
"exact_matches");
|
|
|
|
|
log.info("searchResponseGoogle OK");
|
|
|
|
|
allGoogleImages = searchImageService.getAllImagesWithoutPagination(
|
|
|
|
|
searchResponseGoogle, "exact_matches");
|
|
|
|
|
} catch (TimeoutException e) {
|
|
|
|
|
log.warn("Google search timeout for file {}", fileId);
|
|
|
|
|
hasTimeout = true;
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
log.error("Google search failed for file {}", fileId, e);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
log.info("Google search is disabled by settings");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (useYandex) {
|
|
|
|
|
try {
|
|
|
|
|
String searchResponseYandex = searchImageService.searchReverseByPublicUrl(fileEntity, "yandex_reverse_image",
|
|
|
|
|
"visual_matches");
|
|
|
|
|
log.info("searchResponseYandex OK");
|
|
|
|
|
allYandexImages = searchImageService.getAllImagesWithoutPagination(
|
|
|
|
|
searchResponseYandex, "visual_matches");
|
|
|
|
|
} catch (TimeoutException e) {
|
|
|
|
|
log.warn("Yandex search timeout for file {}", fileId);
|
|
|
|
|
hasTimeout = true;
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
log.error("Yandex search failed for file {}", fileId, e);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
log.info("Yandex search is disabled by settings");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (allYandexImages.isEmpty() && allGoogleImages.isEmpty()) {
|
|
|
|
|
log.warn("No results from any search engine");
|
|
|
|
|
String errorMessage = useGoogle || useYandex ? "No results found" : "All search engines are disabled";
|
|
|
|
|
|
|
|
|
|
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
|
|
|
|
errorMessage, new YandexSearchResponse());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<YandexSearchResponse.ImageResult> allUniqueImages;
|
|
|
|
|
if (useGoogle && useYandex) {
|
|
|
|
|
allUniqueImages = searchImageService.removeDuplicateUrls(allYandexImages, allGoogleImages);
|
|
|
|
|
log.info("Merged results from both engines: {} unique images", allUniqueImages.size());
|
|
|
|
|
} else if (useGoogle) {
|
|
|
|
|
allUniqueImages = allGoogleImages;
|
|
|
|
|
log.info("Results only from Google: {} images", allUniqueImages.size());
|
|
|
|
|
} else {
|
|
|
|
|
allUniqueImages = allYandexImages;
|
|
|
|
|
log.info("Results only from Yandex: {} images", allUniqueImages.size());
|
|
|
|
|
}
|
2026-02-19 16:01:10 +07:00
|
|
|
|
2026-04-10 20:13:59 +07:00
|
|
|
tariffInfoService.writeOffTokens(fileEntity.getUserId(), TariffConstants.TOKEN_VALUE_FOR_SEARCH,
|
|
|
|
|
OperationType.SEARCH);
|
2026-03-05 14:27:14 +07:00
|
|
|
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
|
2026-02-05 18:55:54 +07:00
|
|
|
|
2026-02-12 11:58:05 +07:00
|
|
|
int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1;
|
|
|
|
|
int pageSize = 5;
|
2026-04-29 17:16:34 +07:00
|
|
|
|
2026-05-06 12:59:38 +07:00
|
|
|
boolean checkSimilar = imageSearchRequest.getCheckSimilarFirst() != null &&
|
2026-04-29 15:05:56 +07:00
|
|
|
imageSearchRequest.getCheckSimilarFirst();
|
2026-02-12 11:58:05 +07:00
|
|
|
|
2026-04-29 15:05:56 +07:00
|
|
|
if (checkSimilar && !allUniqueImages.isEmpty()) {
|
2026-04-29 14:53:45 +07:00
|
|
|
YandexSearchResponse.ImageResult image = allUniqueImages.getFirst();
|
|
|
|
|
String url = image.getUrl();
|
|
|
|
|
Path tempFile = null;
|
|
|
|
|
try {
|
2026-04-29 18:14:15 +07:00
|
|
|
tempFile = convertToSupportedFormat(fileUploadService.downloadImageFromUrl(url));
|
2026-05-06 13:08:34 +07:00
|
|
|
File file = new File(fileEntity.getFilePath());
|
2026-05-06 12:59:38 +07:00
|
|
|
// String filePath = tempFile.toAbsolutePath().toString();
|
2026-05-06 13:08:34 +07:00
|
|
|
NoCopyCheckResult checkResult = noCopyFileService.checkFile(file,
|
|
|
|
|
FileProtector.Type.IMAGE);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
|
|
|
|
log.info("checkResult: " + checkResult);
|
2026-05-06 12:59:38 +07:00
|
|
|
// Map<String, Long> hashSearchApiFile = imageHashService.calculateHash(Paths.get(filePath));
|
|
|
|
|
// ImageHashEntity checkFileHash = imageHashRepository.findByFileId(fileId);
|
2026-04-29 14:53:45 +07:00
|
|
|
|
2026-05-06 12:59:38 +07:00
|
|
|
// boolean hashesAreEqual = hashSearchApiFile.get("hi").equals(checkFileHash.getHash64Hi())
|
|
|
|
|
// && hashSearchApiFile.get("low").equals(checkFileHash.getHash64Lo());
|
2026-04-29 14:53:45 +07:00
|
|
|
|
2026-05-06 12:59:38 +07:00
|
|
|
// if (!hashesAreEqual) {
|
|
|
|
|
// return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
|
|
|
|
// "Not found similar pictures", new YandexSearchResponse());
|
|
|
|
|
// }
|
2026-04-29 14:53:45 +07:00
|
|
|
} finally {
|
|
|
|
|
if (tempFile != null) {
|
|
|
|
|
try {
|
|
|
|
|
Files.deleteIfExists(tempFile);
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
log.warn("Not delete temp file: {}", tempFile, e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 15:03:02 +07:00
|
|
|
List<YandexSearchResponse.ImageResult> pagedResults = searchImageService.paginateResults(allUniqueImages, page,
|
2026-02-12 11:58:05 +07:00
|
|
|
pageSize * 2);
|
|
|
|
|
|
|
|
|
|
YandexSearchResponse finalResponse = new YandexSearchResponse();
|
|
|
|
|
finalResponse.setImages(pagedResults);
|
|
|
|
|
finalResponse.setPage(page);
|
|
|
|
|
finalResponse.setPageSize(pageSize * 2);
|
2026-03-16 15:03:02 +07:00
|
|
|
finalResponse.setTotalResults(allUniqueImages.size());
|
|
|
|
|
finalResponse.setTotalPages((int) Math.ceil((double) allUniqueImages.size() / (pageSize * 2)));
|
2026-02-11 18:47:58 +07:00
|
|
|
|
2026-02-19 16:01:10 +07:00
|
|
|
String message = hasTimeout ? "Search completed (partial)" : MessageCode.SUCCESS.getDescription();
|
|
|
|
|
|
2025-12-22 14:07:44 +07:00
|
|
|
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
2026-02-19 16:01:10 +07:00
|
|
|
message, finalResponse);
|
2025-12-22 14:07:44 +07:00
|
|
|
}
|
2026-04-29 18:14:15 +07:00
|
|
|
|
|
|
|
|
public Path convertToSupportedFormat(Path imagePath) throws IOException {
|
|
|
|
|
String fileName = imagePath.getFileName().toString().toLowerCase();
|
|
|
|
|
|
|
|
|
|
if (fileName.matches(".*\\.(jpg|jpeg|png|bmp)$")) {
|
|
|
|
|
return imagePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BufferedImage image = ImageIO.read(imagePath.toFile());
|
|
|
|
|
Path pngFile = Files.createTempFile("converted_", ".png");
|
|
|
|
|
ImageIO.write(image, "PNG", pngFile.toFile());
|
|
|
|
|
|
|
|
|
|
return pngFile;
|
|
|
|
|
}
|
2025-12-22 14:07:44 +07:00
|
|
|
}
|