NCBACK-34 add logic for clear does not exist files,add dto for similar file search controller
Test Workflow / test (push) Successful in 4s

This commit is contained in:
vladp
2026-01-16 14:30:45 +07:00
parent 4a45f40e9f
commit 3d679440f6
7 changed files with 202 additions and 47 deletions
@@ -1,19 +1,25 @@
package ru.soune.nocopy.service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import ru.soune.nocopy.dto.file.SimilarFileResponse;
import ru.soune.nocopy.dto.file.SimilarityFilter;
import ru.soune.nocopy.repository.ImageHashRepository;
import ru.soune.nocopy.repository.ImageSimilarityRepository;
import ru.soune.nocopy.repository.SimilarImageProjection;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class FileSimilarityService {
private final ImageSimilarityRepository repository;
private final ImageHashRepository hashRepository;
public List<SimilarFileResponse> findSimilarFiles(String fileId) {
@@ -55,6 +61,62 @@ public class FileSimilarityService {
.toList();
}
public Page<SimilarFileResponse> findSimilarFiles(String fileId, SimilarityFilter filter, Pageable pageable) {
var imageHashEntity = hashRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("Hash not found"));
Integer hash64Hi = imageHashEntity.getHash64Hi();
Integer hash64Lo = imageHashEntity.getHash64Lo();
List<SimilarImageProjection> candidates = repository.findCandidates(fileId);
List<String> similarityLevels = (filter != null && filter.getSimilarityLevels() != null)
? filter.getSimilarityLevels()
: List.of("DUPLICATE", "SIMILAR", "DIFFERENT");
List<SimilarFileResponse> allResults = candidates.stream()
.map(c -> createSimilarFileResponse(c, hash64Hi, hash64Lo))
.filter(response -> similarityLevels.contains(response.getSimilarityLevel()))
.sorted(Comparator.comparingInt(SimilarFileResponse::getHammingDistance))
.collect(Collectors.toList());
int total = allResults.size();
int page = (pageable != null) ? pageable.getPageNumber() : 0;
int size = (pageable != null) ? pageable.getPageSize() : 20;
int fromIndex = Math.min(page * size, total);
int toIndex = Math.min(fromIndex + size, total);
List<SimilarFileResponse> pageContent = allResults.subList(fromIndex, toIndex);
return new PageImpl<>(pageContent, pageable, total);
}
private SimilarFileResponse createSimilarFileResponse(SimilarImageProjection similarImageProjection,
Integer hash64Hi, Integer hash64Lo) {
Integer imageProjectionHash64Hi = similarImageProjection.getHash64Hi();
Integer similarImageProjectionHash64Lo = similarImageProjection.getHash64Lo();
int hamming = hamming64(hash64Hi, hash64Lo, imageProjectionHash64Hi, similarImageProjectionHash64Lo);
String level;
if (hamming <= 5) {
level = "DUPLICATE";
} else if (hamming <= 12) {
level = "SIMILAR";
} else {
level = "DIFFERENT";
}
return SimilarFileResponse.builder()
.fileId(similarImageProjection.getSimilarFileId())
.originalFileName(similarImageProjection.getOriginalFileName())
.fileSize(similarImageProjection.getFileSize())
.hammingDistance(hamming)
.similarityLevel(level)
.build();
}
private int hamming64(int aHi, int aLo, int bHi, int bLo) {
return Integer.bitCount(aHi ^ bHi)
+ Integer.bitCount(aLo ^ bLo);
@@ -0,0 +1,35 @@
package ru.soune.nocopy.service.file;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.repository.FileEntityRepository;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
@Service
@RequiredArgsConstructor
public class NotExistFilesCleanupService {
@Autowired
private FileEntityRepository fileEntityRepository;
@Transactional
@Scheduled(fixedDelay = 30000)
public void cleanUpNotExistFiles() {
List<FileEntity> fileEntities = fileEntityRepository.searchFileEntityByStatus(FileStatus.DELETED);
for (FileEntity fileEntity : fileEntities) {
boolean exists = Files.exists(Paths.get(fileEntity.getFilePath()));
if (!exists) {
fileEntityRepository.delete(fileEntity);
}
}
}
}