diff --git a/build.gradle b/build.gradle index 0c7f38c..a608a24 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,8 @@ dependencies { // implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.21' // implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' // implementation 'io.insert-koin:koin-core:3.5.0' - implementation name: 'testlib-fat-0.1.2-all (1)' + implementation name: 'testlib-fat-0.2.1-all' + } tasks.named('test') { diff --git a/libs/testlib-fat-0.1.2-all (1).jar b/libs/testlib-fat-0.1.2-all (1).jar deleted file mode 100644 index 0bfd6bf..0000000 Binary files a/libs/testlib-fat-0.1.2-all (1).jar and /dev/null differ diff --git a/libs/testlib-fat-0.1.2-all.jar b/libs/testlib-fat-0.2.1-all.jar similarity index 60% rename from libs/testlib-fat-0.1.2-all.jar rename to libs/testlib-fat-0.2.1-all.jar index aab9092..c911c08 100644 Binary files a/libs/testlib-fat-0.1.2-all.jar and b/libs/testlib-fat-0.2.1-all.jar differ diff --git a/src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java b/src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java index b0cc444..cce0f0d 100644 --- a/src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java @@ -1,6 +1,11 @@ package ru.soune.nocopy.configuration; +import com.vrt.AudioFilePathProvider; import com.vrt.fileprotection.FileProtector; +import com.vrt.fileprotection.audio.AudioLocalSearch; +import com.vrt.fileprotection.image.ImageLocalSearch; +import com.vrt.fileprotection.image.ImageUniqueCheck; +import com.vrt.fileprotection.image.phash.PerceptualHashHelper; import lombok.AllArgsConstructor; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; @@ -21,14 +26,23 @@ public class ApplicationConfig { } @Bean - public com.vrt.NoCopyFileService noCopyFileService( + public com.vrt. + NoCopyFileService noCopyFileService( FileProtector.FileProvider fileProvider, - FileProtector.ProcessingListener processingListener) { + FileProtector.ProcessingListener processingListener, + ImageUniqueCheck imageUniqueCheck, + ImageLocalSearch imageLocalSearch, + AudioLocalSearch audioLocalSearch, + AudioFilePathProvider audioFilePathProvider) { return new com.vrt.NoCopyFileService( Collections.emptyList(), fileProvider, - processingListener + processingListener, + imageUniqueCheck, + imageLocalSearch, + audioLocalSearch, + audioFilePathProvider ); } } diff --git a/src/main/java/ru/soune/nocopy/controller/ApiController.java b/src/main/java/ru/soune/nocopy/controller/ApiController.java index 414dba6..529f163 100644 --- a/src/main/java/ru/soune/nocopy/controller/ApiController.java +++ b/src/main/java/ru/soune/nocopy/controller/ApiController.java @@ -26,20 +26,17 @@ import ru.soune.nocopy.dto.file.ChunkUploadResponse; import ru.soune.nocopy.dto.file.CompleteUploadResponse; import ru.soune.nocopy.dto.file.FileEntityResponse; import ru.soune.nocopy.dto.file.UploadProgress; -import ru.soune.nocopy.entity.AuthToken; -import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.ProtectionStatus; import ru.soune.nocopy.entity.file.UploadStatus; import ru.soune.nocopy.exception.*; import ru.soune.nocopy.handler.*; -import ru.soune.nocopy.repository.AuthTokenRepository; -import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.service.FileSimilarityService; import ru.soune.nocopy.service.auth.AuthService; import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileUploadService; +import ru.soune.nocopy.util.FileUtil; import java.io.IOException; import java.nio.file.Files; @@ -70,6 +67,8 @@ public class ApiController { private final NoCopyFileService noCopyFileService; + private final FileUtil fileUtil; + @PostMapping("/v{version}/data") public ResponseEntity handlePostRequest(@RequestBody BaseRequest request, @PathVariable("version") int version) { @@ -147,7 +146,7 @@ public class ApiController { SimilarityFilter filter = SimilarityFilter.builder() .similarityLevels(similarityLevels) .build(); - Page similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable); + Page similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable); String messageDesc; MessageCode success; @@ -365,45 +364,22 @@ public class ApiController { @GetMapping("/protect/{fileId}") public ResponseEntity protect( @PathVariable(required = false) String fileId) { - Optional fileEntity = fileEntityRepository.findById(fileId); + Optional optionalFileEntity = fileEntityRepository.findById(fileId); - if (!fileEntity.isPresent()) { + if (!optionalFileEntity.isPresent()) { return ResponseEntity.notFound().build(); } - - FileProtector.FileInfo fileInfo = createFileInfo(fileEntity.get()); + FileEntity fileEntity = optionalFileEntity.get(); + FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity); noCopyFileService.addFile(fileInfo); - fileEntity.get().setProtectionStatus(ProtectionStatus.PROCESSING); - - fileEntityRepository.save(fileEntity.get()); + fileEntity.setProtectionStatus(ProtectionStatus.PROCESSING); + fileEntityRepository.save(fileEntity); return ResponseEntity.ok().build(); } - private FileProtector.FileInfo createFileInfo(FileEntity fileEntity) { - FileProtector.Type type = determineFileType(fileEntity.getMimeType()); - - return new FileProtector.FileInfo(type, fileEntity.getId(), String.valueOf(fileEntity.getUserId())); - } - - private FileProtector.Type determineFileType(String mimeType) { - if (mimeType == null) { - return FileProtector.Type.IMAGE; - } - - if (mimeType.startsWith("image")) { - return FileProtector.Type.IMAGE; - } else if (mimeType.startsWith("video")) { - return FileProtector.Type.VIDEO; - } else if (mimeType.startsWith("audio")) { - return FileProtector.Type.AUDIO; - } else { - return FileProtector.Type.IMAGE; - } - } - private ResponseEntity checkForDuplicates(String uploadId) throws IOException { Optional uploadedFile = fileEntityRepository.findByUploadSessionId(uploadId); @@ -412,7 +388,7 @@ public class ApiController { } FileEntity fileEntity = uploadedFile.get(); - List similarFiles = fileSimilarityService.findSimilarFiles(fileEntity.getId()); + List similarFiles = fileSimilarityService.findSimilarFiles(fileEntity.getId()); if (hasDuplicate(similarFiles)) { return handleDuplicate(fileEntity, similarFiles); @@ -421,11 +397,11 @@ public class ApiController { return null; } - private boolean hasDuplicate(List similarFiles) { + private boolean hasDuplicate(List similarFiles) { return similarFiles.stream().anyMatch(f -> f.getHammingDistance() <= 5); } - private ResponseEntity handleDuplicate(FileEntity fileEntity, List similarFiles) + private ResponseEntity handleDuplicate(FileEntity fileEntity, List similarFiles) throws IOException { fileEntityService.deleteFromDisk(fileEntity); @@ -511,23 +487,4 @@ public class ApiController { } return contentType; } - - private Long getUserIdFromToken(String tokenHeader) { - String token = tokenHeader.replace("Bearer ", ""); - AuthToken authToken = authTokenRepository.findByToken(token) - .orElseThrow(() -> new NotFoundAuthToken("Token not found")); - return authToken.getUser().getId(); - } - - private boolean isPreviewSupported(String mimeType) { - if (mimeType == null) { - return false; - } - - return mimeType.startsWith("image") || - mimeType.startsWith("text") || - mimeType.equals("pdf") || - mimeType.startsWith("video") || - mimeType.startsWith("audio"); - } } \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/dto/file/SimilarFileResponse.java b/src/main/java/ru/soune/nocopy/dto/file/SimilarFileDTO.java similarity index 82% rename from src/main/java/ru/soune/nocopy/dto/file/SimilarFileResponse.java rename to src/main/java/ru/soune/nocopy/dto/file/SimilarFileDTO.java index f7aae6e..45fb75c 100644 --- a/src/main/java/ru/soune/nocopy/dto/file/SimilarFileResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/SimilarFileDTO.java @@ -5,10 +5,11 @@ import lombok.Value; @Value @Builder -public class SimilarFileResponse { +public class SimilarFileDTO { String fileId; String originalFileName; Long fileSize; Integer hammingDistance; String similarityLevel; + Long ownerId; } diff --git a/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java b/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java index 724af3f..dd193af 100644 --- a/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java @@ -76,6 +76,10 @@ public class FileEntity { @Column(name = "protected_at") private LocalDateTime protectedAt; + @Column(name = "signature") + @JsonIgnore + private String signature; + @OneToOne(mappedBy = "file", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIgnore private ImageHashEntity imageHash; diff --git a/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java b/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java index abfb7d8..26d6df6 100644 --- a/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java @@ -13,8 +13,8 @@ import java.util.Optional; @Repository public interface FileEntityRepository extends JpaRepository { - List findByUserId(Long userId); + Optional findByUserIdAndChecksum(Long userId, String imageHash); List findByUserIdAndStatus(Long userId, FileStatus status); @@ -40,5 +40,11 @@ public interface FileEntityRepository extends JpaRepository @Query("SELECT f FROM FileEntity f WHERE f.status = :status") List searchFileEntityByStatus(@Param("status") FileStatus status); + @Query("SELECT f FROM FileEntity f WHERE f.id = :fileId") + FileEntity findByFileId(@Param("fileId") String fileId); + + @Query("SELECT f FROM FileEntity f WHERE f.signature = :signature") + FileEntity findBySignature(@Param("signature") String signature); + long countByUserId(Long userId); } diff --git a/src/main/java/ru/soune/nocopy/repository/ImageHashRepository.java b/src/main/java/ru/soune/nocopy/repository/ImageHashRepository.java index ee0aac2..c0d8d7f 100644 --- a/src/main/java/ru/soune/nocopy/repository/ImageHashRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/ImageHashRepository.java @@ -4,7 +4,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import ru.soune.nocopy.entity.file.ImageHashEntity; +import java.util.List; + @Repository public interface ImageHashRepository extends JpaRepository { void deleteByFileId(String fileId); + List findByFileId(String fileId); + ImageHashEntity findByHash64HiAndHash64Lo(Integer hash64Hi, Integer hash64Lo); } diff --git a/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java b/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java index dea9334..51f1808 100644 --- a/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java +++ b/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java @@ -5,12 +5,14 @@ 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.SimilarFileDTO; 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 ru.soune.nocopy.util.FileUtil; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -19,26 +21,28 @@ import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class FileSimilarityService { + private final ImageSimilarityRepository repository; private final ImageHashRepository hashRepository; - public List findSimilarFiles(String fileId) { + private final FileUtil fileUtil; + + public List findSimilarFiles(String fileId) { var imageHashEntity = hashRepository.findById(fileId) .orElseThrow(() -> new RuntimeException("Hash not found")); Integer hash64Hi = imageHashEntity.getHash64Hi(); Integer hash64Lo = imageHashEntity.getHash64Lo(); - List candidates = - repository.findCandidates(fileId); + List candidates = repository.findCandidates(fileId); return candidates.stream() .map(c -> { Integer cHi = c.getHash64Hi(); Integer cLo = c.getHash64Lo(); - int hamming = hamming64(hash64Hi, hash64Lo, cHi, cLo); + int hamming = fileUtil.hamming64(hash64Hi, hash64Lo, cHi, cLo); String level; if (hamming <= 5) { @@ -49,7 +53,7 @@ public class FileSimilarityService { level = "DIFFERENT"; } - return SimilarFileResponse.builder() + return SimilarFileDTO.builder() .fileId(c.getFileId()) .originalFileName(c.getOriginalFileName()) .fileSize(c.getFileSize()) @@ -62,6 +66,52 @@ public class FileSimilarityService { .toList(); } + public List findDuplicateByHammingDistance(String fileId, int hammingDistance, + int duplicate, int similar) { + List candidates = repository.findCandidates(fileId); + + var imageHashEntity = hashRepository.findById(fileId) + .orElseThrow(() -> new RuntimeException("Hash not found")); + + Integer hash64Hi = imageHashEntity.getHash64Hi(); + Integer hash64Lo = imageHashEntity.getHash64Lo(); + + return candidates.stream() + .map(c -> { + Integer cHi = c.getHash64Hi(); + Integer cLo = c.getHash64Lo(); + + int hamming = fileUtil.hamming64(hash64Hi, hash64Lo, cHi, cLo); + + return new AbstractMap.SimpleEntry<>(c, hamming); + }) + .filter(entry -> entry.getValue() <= hammingDistance) + .sorted((a, b) -> Integer.compare(a.getValue(), b.getValue())) + .map(entry -> { + SimilarImageProjection similarImageProjection = entry.getKey(); + int hamming = entry.getValue(); + + String level; + if (hamming <= duplicate) { + level = "DUPLICATE"; + } else if (hamming <= similar) { + level = "SIMILAR"; + } else { + level = "DIFFERENT"; + } + + return SimilarFileDTO.builder() + .fileId(similarImageProjection.getFileId()) + .originalFileName(similarImageProjection.getOriginalFileName()) + .fileSize(similarImageProjection.getFileSize()) + .hammingDistance(hamming) + .similarityLevel(level) + .ownerId(similarImageProjection.getUserId()) + .build(); + }) + .toList(); + } + public List findDuplicatedByHash(Integer hash64Hi, Integer hash64Lo) { List duplicates = repository.findExactDuplicates(hash64Hi,hash64Lo); @@ -72,7 +122,7 @@ public class FileSimilarityService { return duplicates; } - public Page findSimilarFiles(String fileId, SimilarityFilter filter, Pageable pageable) { + public Page findSimilarFiles(String fileId, SimilarityFilter filter, Pageable pageable) { var imageHashEntity = hashRepository.findById(fileId) .orElseThrow(() -> new RuntimeException("Hash not found")); @@ -85,10 +135,10 @@ public class FileSimilarityService { ? filter.getSimilarityLevels() : List.of("DUPLICATE", "SIMILAR", "DIFFERENT"); - List allResults = candidates.stream() + List allResults = candidates.stream() .map(c -> createSimilarFileResponse(c, hash64Hi, hash64Lo)) .filter(response -> similarityLevels.contains(response.getSimilarityLevel())) - .sorted(Comparator.comparingInt(SimilarFileResponse::getHammingDistance)) + .sorted(Comparator.comparingInt(SimilarFileDTO::getHammingDistance)) .collect(Collectors.toList()); int total = allResults.size(); @@ -98,17 +148,17 @@ public class FileSimilarityService { int fromIndex = Math.min(page * size, total); int toIndex = Math.min(fromIndex + size, total); - List pageContent = allResults.subList(fromIndex, toIndex); + List pageContent = allResults.subList(fromIndex, toIndex); return new PageImpl<>(pageContent, pageable, total); } - private SimilarFileResponse createSimilarFileResponse(SimilarImageProjection similarImageProjection, - Integer hash64Hi, Integer hash64Lo) { + private SimilarFileDTO createSimilarFileResponse(SimilarImageProjection similarImageProjection, + Integer hash64Hi, Integer hash64Lo) { Integer imageProjectionHash64Hi = similarImageProjection.getHash64Hi(); Integer similarImageProjectionHash64Lo = similarImageProjection.getHash64Lo(); - int hamming = hamming64(hash64Hi, hash64Lo, imageProjectionHash64Hi, similarImageProjectionHash64Lo); + int hamming = fileUtil.hamming64(hash64Hi, hash64Lo, imageProjectionHash64Hi, similarImageProjectionHash64Lo); String level; if (hamming <= 5) { @@ -119,7 +169,7 @@ public class FileSimilarityService { level = "DIFFERENT"; } - return SimilarFileResponse.builder() + return SimilarFileDTO.builder() .fileId(similarImageProjection.getFileId()) .originalFileName(similarImageProjection.getOriginalFileName()) .fileSize(similarImageProjection.getFileSize()) @@ -127,9 +177,4 @@ public class FileSimilarityService { .similarityLevel(level) .build(); } - - private int hamming64(int aHi, int aLo, int bHi, int bLo) { - return Integer.bitCount(aHi ^ bHi) - + Integer.bitCount(aLo ^ bLo); - } } diff --git a/src/main/java/ru/soune/nocopy/service/ImageHashService.java b/src/main/java/ru/soune/nocopy/service/ImageHashService.java index b7b6882..7df67bc 100644 --- a/src/main/java/ru/soune/nocopy/service/ImageHashService.java +++ b/src/main/java/ru/soune/nocopy/service/ImageHashService.java @@ -1,5 +1,7 @@ package ru.soune.nocopy.service; +import com.vrt.fileprotection.image.phash.PHash; +import com.vrt.fileprotection.image.phash.PerceptualHashHelper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -7,15 +9,10 @@ import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.ImageHashEntity; import ru.soune.nocopy.repository.ImageHashRepository; -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.BufferedImage; +import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; -import java.util.Arrays; import java.util.Map; @Service @@ -26,11 +23,15 @@ public class ImageHashService { private final ImageHashRepository repository; public Map calculateHash(Path imagePath) throws IOException { - long hash64 = computePhash64(imagePath); + File file = imagePath.toFile(); + PHash pHash = PerceptualHashHelper.INSTANCE.generateDCTPerceptualHash(file); + Long firstPart = pHash.getFirstPart(); + Long secondPart = pHash.getSecondPart(); + Map hash = Map.of( + "hi", Math.toIntExact(firstPart), + "low", Math.toIntExact(secondPart)); - return Map.of( - "hi", high32(hash64), - "low", low32(hash64)); + return hash; } public void create(FileEntity file, Map stringIntegerMap) { @@ -44,111 +45,4 @@ public class ImageHashService { repository.save(entity); } - - private long computePhash64(Path path) throws IOException { - try (InputStream is = Files.newInputStream(path)) { - BufferedImage image = ImageIO.read(is); - if (image == null) throw new IOException("Cannot read image file: " + path); - - BufferedImage gray = toGrayScale(image); - BufferedImage resized = resizeImage(gray, 32, 32); - - double[][] dct = applyDCT(resized); - double[][] topLeft = extractTopLeft8x8(dct); - double median = calculateMedian(topLeft, true); - - return create64BitHashLong(topLeft, median); - } - } - - private BufferedImage toGrayScale(BufferedImage image) { - BufferedImage gray = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY); - Graphics2D g = gray.createGraphics(); - g.drawImage(image, 0, 0, null); - g.dispose(); - return gray; - } - - private BufferedImage resizeImage(BufferedImage image, int width, int height) { - BufferedImage resized = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); - Graphics2D g = resized.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g.drawImage(image, 0, 0, width, height, null); - g.dispose(); - return resized; - } - - private double[][] applyDCT(BufferedImage image) { - int w = image.getWidth(); - int h = image.getHeight(); - double[][] pixels = new double[h][w]; - - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int gray = image.getRGB(x, y) & 0xFF; - pixels[y][x] = gray; - } - } - - double[][] dct = new double[h][w]; - for (int u = 0; u < h; u++) { - double au = u == 0 ? 1.0 / Math.sqrt(h) : Math.sqrt(2.0 / h); - for (int v = 0; v < w; v++) { - double av = v == 0 ? 1.0 / Math.sqrt(w) : Math.sqrt(2.0 / w); - double sum = 0.0; - for (int x = 0; x < h; x++) { - for (int y = 0; y < w; y++) { - sum += pixels[x][y] * - Math.cos((2 * x + 1) * u * Math.PI / (2.0 * h)) * - Math.cos((2 * y + 1) * v * Math.PI / (2.0 * w)); - } - } - dct[u][v] = au * av * sum; - } - } - return dct; - } - - private double[][] extractTopLeft8x8(double[][] dct) { - double[][] block = new double[8][8]; - for (int i = 0; i < 8; i++) System.arraycopy(dct[i], 0, block[i], 0, 8); - return block; - } - - private double calculateMedian(double[][] block, boolean excludeDC) { - double[] vals = new double[63]; - int idx = 0; - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 8; j++) { - if (i == 0 && j == 0 && excludeDC) continue; - vals[idx++] = block[i][j]; - } - } - Arrays.sort(vals); - return (vals.length % 2 == 0) ? - (vals[vals.length / 2 - 1] + vals[vals.length / 2]) / 2.0 : - vals[vals.length / 2]; - } - - private long create64BitHashLong(double[][] block, double median) { - long hash = 0L; - - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 8; j++) { - if (i == 0 && j == 0) continue; - hash <<= 1; - if (block[i][j] > median) hash |= 1; - } - } - - return hash; - } - - private int high32(long hash64) { - return (int) (hash64 >>> 32); - } - - private int low32(long hash64) { - return (int) hash64; - } } diff --git a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java index 1f3464e..4522691 100644 --- a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java @@ -9,6 +9,7 @@ import ru.soune.nocopy.dto.file.FileResponse; import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.entity.file.ProtectionStatus; import ru.soune.nocopy.exception.DuplicateImageException; import ru.soune.nocopy.exception.FileEntityNotFoundException; import ru.soune.nocopy.repository.FileEntityRepository; @@ -16,6 +17,7 @@ import ru.soune.nocopy.repository.SimilarImageProjection; import ru.soune.nocopy.service.FileSimilarityService; import ru.soune.nocopy.service.ImageHashService; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -110,6 +112,39 @@ public class FileEntityService { return convertToResponse(fileEntity, version); } + @Transactional(readOnly = true) + public FileEntityResponse getByFilePath(String filePath, int version) { + FileEntity fileEntity = fileEntityRepository.findByFilePath(filePath) + .orElseThrow(() -> new FileEntityNotFoundException("Path: " + filePath)); + + return convertToResponse(fileEntity, version); + } + + + @Transactional(readOnly = true) + public FileResponse getAllUserFiles(Long userId, int version) { + List fileEntities = fileEntityRepository.findByUserIdAndStatus( + userId, FileStatus.ACTIVE); + + List files = fileEntities.stream() + .map(file -> convertToResponse(file, version)) + .collect(Collectors.toList()); + + long totalSize = fileEntities.stream() + .mapToLong(FileEntity::getFileSize) + .sum(); + + return FileResponse.builder() + .files(files) + .totalCount(files.size()) + .totalSize(totalSize) + .formattedTotalSize(formatFileSize(totalSize)) + .page(1) + .pageSize(files.size()) + .build(); + } + + @Transactional(readOnly = true) public FileResponse getUserFiles(Long userId, int page, int pageSize, int version) { List allFiles = fileEntityRepository.findByUserIdAndStatus( @@ -178,6 +213,95 @@ public class FileEntityService { return totalSize != null ? totalSize : 0L; } + public void changeStatus(ProtectionStatus newStatus, String fileId) { + FileEntity fileEntity = fileEntityRepository.findById(fileId) + .orElseThrow(() -> new FileEntityNotFoundException(fileId)); + fileEntity.setProtectionStatus(newStatus); + + fileEntityRepository.save(fileEntity); + } + + @Transactional + public void writeProtectedFile(String id, byte[] data, String fileExt) throws IOException { + FileEntity fileEntity = fileEntityRepository.findById(id) + .orElseThrow(() -> new RuntimeException("File not found: " + id)); + + String extension = determineFileExtension(fileExt, fileEntity); + Path protectedFilePath = prepareProtectedPath(fileEntity, extension); + + if (Files.exists(protectedFilePath)) { + Files.delete(protectedFilePath); + } + + Files.write(protectedFilePath, data); + + fileEntity.setProtectedFilePath(protectedFilePath.toString()); + fileEntity.setProtectedAt(LocalDateTime.now()); + fileEntity.setUpdatedAt(LocalDateTime.now()); + fileEntity.setFileExtension(extension); + fileEntity.setProtectionStatus(ProtectionStatus.PROTECTED); + + fileEntityRepository.save(fileEntity); + } + + public FileEntity findBySignature(String signature) { + return fileEntityRepository.findBySignature(signature); + } + + public File getFileById(String id) { + try { + FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() -> + new RuntimeException("File not found: " + id)); + + File file = new File(fileEntity.getFilePath()); + + if (!file.exists()) { + throw new RuntimeException("File not found on disk: " + fileEntity.getFilePath()); + } + + return file; + } catch (Exception e) { + log.error("Error getting file: {}", id, e); + return null; + } + } + + public Path prepareProtectedPath(FileEntity fileEntity, String extension) throws IOException { + Path originalPath = Paths.get(fileEntity.getFilePath()); + + String pathStr = originalPath.toString(); + pathStr = pathStr.replaceFirst("/uploads/uploads/", "/uploads/protected/"); + + Path protectedPath = Paths.get(pathStr); + String fileName = protectedPath.getFileName().toString(); + + if (extension != null) { + String nameWithoutExt = fileName; + int lastDotIndex = fileName.lastIndexOf('.'); + if (lastDotIndex > 0) { + nameWithoutExt = fileName.substring(0, lastDotIndex); + } + fileName = nameWithoutExt + "." + extension; + + protectedPath = protectedPath.getParent().resolve(fileName); + } + + Files.createDirectories(protectedPath.getParent()); + + return protectedPath; + } + + public void updateSignature(String signature, String fileId) throws IOException { + FileEntity fileEntity = fileEntityRepository.findByFileId(fileId); + fileEntity.setSignature(signature); + fileEntity.setProtectionStatus(ProtectionStatus.PROTECTED); + fileEntity.setUpdatedAt(LocalDateTime.now()); + fileEntity.setProtectedAt(LocalDateTime.now()); + fileEntity.setProtectedFilePath(prepareProtectedPath(fileEntity, fileEntity.getFileExtension()).toString()); + + fileEntityRepository.save(fileEntity); + } + private boolean checkFileExistsOnDisk(String filePath) { try { return Files.exists(Paths.get(filePath)); @@ -187,6 +311,14 @@ public class FileEntityService { } } + private String determineFileExtension(String fileExt, FileEntity fileEntity) { + if (fileExt != null && !fileExt.trim().isEmpty()) { + return fileExt; + } else { + return fileEntity.getFileExtension(); + } + } + private FileEntityResponse convertToResponse(FileEntity fileEntity, int version) { boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath()); diff --git a/src/main/java/ru/soune/nocopy/service/file/NoCopyProcessingListener.java b/src/main/java/ru/soune/nocopy/service/file/NoCopyProcessingListener.java deleted file mode 100644 index 684a460..0000000 --- a/src/main/java/ru/soune/nocopy/service/file/NoCopyProcessingListener.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.soune.nocopy.service.file; - -import com.vrt.fileprotection.FileProtector; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import ru.soune.nocopy.entity.file.FileEntity; -import ru.soune.nocopy.entity.file.ProtectionStatus; -import ru.soune.nocopy.exception.FileEntityNotFoundException; -import ru.soune.nocopy.repository.FileEntityRepository; - -@Slf4j -@Component -@AllArgsConstructor -public class NoCopyProcessingListener implements FileProtector.ProcessingListener { - - private final FileEntityRepository fileEntityRepository; - - @Override - public void onStartProcessing(FileProtector.FileInfo fileInfo) { - changeStatus(ProtectionStatus.PROCESSING, fileInfo.getId()); - } - - @Override - public void onProcessingFailed(FileProtector.FileInfo fileInfo) { - changeStatus(ProtectionStatus.FAILED, fileInfo.getId()); - } - - @Override - public void onSavingFailed(FileProtector.FileInfo fileInfo) { - changeStatus(ProtectionStatus.FAILED_SAVE, fileInfo.getId()); - } - - @Override - public void onFinish(FileProtector.FileInfo fileInfo) { - changeStatus(ProtectionStatus.PROTECTED, fileInfo.getId());; - } - - private void changeStatus(ProtectionStatus newStatus, String fileId) { - FileEntity fileEntity = fileEntityRepository.findById(fileId) - .orElseThrow(() -> new FileEntityNotFoundException(fileId)); - fileEntity.setProtectionStatus(newStatus); - - fileEntityRepository.save(fileEntity); - } -} diff --git a/src/main/java/ru/soune/nocopy/service/file/ProtectionFileProvider.java b/src/main/java/ru/soune/nocopy/service/file/ProtectionFileProvider.java deleted file mode 100644 index 7f106c3..0000000 --- a/src/main/java/ru/soune/nocopy/service/file/ProtectionFileProvider.java +++ /dev/null @@ -1,166 +0,0 @@ -package ru.soune.nocopy.service.file; - -import com.vrt.fileprotection.FileProtector; -import com.vrt.fileprotection.OperationResult; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.springframework.stereotype.Component; -import ru.soune.nocopy.entity.file.FileEntity; -import ru.soune.nocopy.repository.FileEntityRepository; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.LocalDateTime; - -@Slf4j -@Component -@RequiredArgsConstructor -public class ProtectionFileProvider implements FileProtector.FileProvider { - - - private final FileEntityRepository fileRepository; - - @Nullable - @Override - public File getImageFile(@NotNull String id) { - return getFileById(id); - } - - @Nullable - @Override - public File getVideoFile(@NotNull String id) { - return getFileById(id); - } - - @Nullable - @Override - public File getAudioFile(@NotNull String id) { - return getFileById(id); - } - - @NotNull - @Override - public com.vrt.fileprotection.OperationResult writeSignature(@NotNull String s, @NotNull byte[] bytes) { - return null; - } - - @NotNull - @Override - public com.vrt.fileprotection.OperationResult writeAudioFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) { - try { - writeProtectedFile(id, data, fileExt); - return OperationResult.Companion.success(); - } catch (Exception e) { - log.error("Failed to create protected file: {}", id, e); - return OperationResult.Companion.failure("Failed to create protected file"); - } - } - - @NotNull - @Override - public com.vrt.fileprotection.OperationResult writeVideoFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) { - try { - writeProtectedFile(id, data, fileExt); - return OperationResult.Companion.success(); - } catch (Exception e) { - log.error("Failed to create protected file: {}", id, e); - return OperationResult.Companion.failure("Failed to create protected file"); - } - } - - @NotNull - @Override - public com.vrt.fileprotection.OperationResult writeImageFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) { - try { - writeProtectedFile(id, data, fileExt); - return OperationResult.Companion.success(); - } catch (Exception e) { - log.error("Failed to create protected file: {}", id, e); - return OperationResult.Companion.failure("Failed to create protected file"); - } - } - - @Nullable - @Override - public File getSignature(@NotNull String s) { - return null; - } - - private void writeProtectedFile(String id, byte[] data, String fileExt) throws IOException { - FileEntity fileEntity = fileRepository.findById(id) - .orElseThrow(() -> new RuntimeException("File not found: " + id)); - - String extension = determineFileExtension(fileExt, fileEntity); - Path protectedFilePath = prepareProtectedPath(fileEntity, extension); - - if (Files.exists(protectedFilePath)) { - Files.delete(protectedFilePath); - } - - Files.write(protectedFilePath, data); - - fileEntity.setProtectedFilePath(protectedFilePath.toString()); - fileEntity.setProtectedAt(LocalDateTime.now()); - fileEntity.setUpdatedAt(LocalDateTime.now()); - fileEntity.setFileExtension(extension); - - fileRepository.save(fileEntity); - } - - - private File getFileById(String id) { - try { - FileEntity fileEntity = fileRepository.findById(id).orElseThrow(() -> - new RuntimeException("File not found: " + id)); - - File file = new File(fileEntity.getFilePath()); - - if (!file.exists()) { - throw new RuntimeException("File not found on disk: " + fileEntity.getFilePath()); - } - - return file; - } catch (Exception e) { - log.error("Error getting file: {}", id, e); - return null; - } - } - - private String determineFileExtension(String fileExt, FileEntity fileEntity) { - if (fileExt != null && !fileExt.trim().isEmpty()) { - return fileExt; - } else { - return fileEntity.getFileExtension(); - } - } - - private Path prepareProtectedPath(FileEntity fileEntity, String extension) throws IOException { - Path originalPath = Paths.get(fileEntity.getFilePath()); - - String pathStr = originalPath.toString(); - pathStr = pathStr.replaceFirst("/uploads/uploads/", "/uploads/protected/"); - - Path protectedPath = Paths.get(pathStr); - String fileName = protectedPath.getFileName().toString(); - - if (extension != null) { - String nameWithoutExt = fileName; - int lastDotIndex = fileName.lastIndexOf('.'); - if (lastDotIndex > 0) { - nameWithoutExt = fileName.substring(0, lastDotIndex); - } - fileName = nameWithoutExt + "." + extension; - - protectedPath = protectedPath.getParent().resolve(fileName); - } - - Files.createDirectories(protectedPath.getParent()); - - return protectedPath; - } -} diff --git a/src/main/java/ru/soune/nocopy/service/file/impl/AudioFilePathProviderImpl.java b/src/main/java/ru/soune/nocopy/service/file/impl/AudioFilePathProviderImpl.java new file mode 100644 index 0000000..45b8058 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/file/impl/AudioFilePathProviderImpl.java @@ -0,0 +1,44 @@ +package ru.soune.nocopy.service.file.impl; + +import com.vrt.AudioFilePathProvider; +import com.vrt.fileprotection.FileProtector; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Service; +import ru.soune.nocopy.entity.file.FileEntity; +import ru.soune.nocopy.repository.FileEntityRepository; +import ru.soune.nocopy.service.file.FileEntityService; + +import java.io.IOException; +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AudioFilePathProviderImpl implements AudioFilePathProvider { + + private final FileEntityService fileEntityService; + + private final FileEntityRepository fileEntityRepository; + + @Override + public @NotNull String providePath(@NotNull FileProtector.FileInfo fileInfo) { + String filePath; + Optional optionalFileEntity = fileEntityRepository.findById(fileInfo.getId()); + + if (optionalFileEntity.isEmpty()) { + throw new NullPointerException(); + } + + FileEntity fileEntity = optionalFileEntity.get(); + + try { + filePath = fileEntityService.prepareProtectedPath(fileEntity, fileEntity.getFileExtension()).toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return filePath; + } +} diff --git a/src/main/java/ru/soune/nocopy/service/file/impl/AudioLocalSearchImpl.java b/src/main/java/ru/soune/nocopy/service/file/impl/AudioLocalSearchImpl.java new file mode 100644 index 0000000..34834e9 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/file/impl/AudioLocalSearchImpl.java @@ -0,0 +1,39 @@ +package ru.soune.nocopy.service.file.impl; + +import com.vrt.fileprotection.FileProtector; +import com.vrt.fileprotection.audio.AudioLocalSearch; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.stereotype.Service; +import ru.soune.nocopy.service.file.FileEntityService; +import ru.soune.nocopy.util.FileUtil; + +import java.io.IOException; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AudioLocalSearchImpl implements AudioLocalSearch { + + private final FileEntityService fileEntityService; + + private final FileUtil fileUtil; + + @Override + public @Nullable FileProtector.FileInfo findBySignature(@NotNull String signature) { + return fileUtil.createFileInfo(fileEntityService.findBySignature(signature)); + } + + @Override + public void updateSignature(@NotNull String signature, @NotNull FileProtector.FileInfo fileInfo) { + try { + fileEntityService.updateSignature(signature, fileInfo.getId()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + +} diff --git a/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java b/src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java similarity index 99% rename from src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java rename to src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java index 587cdc1..ce561b9 100644 --- a/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java +++ b/src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java @@ -1,4 +1,4 @@ -package ru.soune.nocopy.service.file; +package ru.soune.nocopy.service.file.impl; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -23,6 +23,8 @@ import ru.soune.nocopy.repository.FileUploadSessionRepository; import ru.soune.nocopy.repository.SimilarImageProjection; import ru.soune.nocopy.service.FileSimilarityService; import ru.soune.nocopy.service.ImageHashService; +import ru.soune.nocopy.service.file.FileEntityService; +import ru.soune.nocopy.service.file.FileUploadService; import java.io.*; import java.nio.file.*; diff --git a/src/main/java/ru/soune/nocopy/service/file/impl/ImageLocalSearchImpl.java b/src/main/java/ru/soune/nocopy/service/file/impl/ImageLocalSearchImpl.java new file mode 100644 index 0000000..1ecf8c7 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/file/impl/ImageLocalSearchImpl.java @@ -0,0 +1,54 @@ +package ru.soune.nocopy.service.file.impl; + +import com.vrt.fileprotection.FileProtector; +import com.vrt.fileprotection.image.ImageLocalSearch; +import com.vrt.fileprotection.image.phash.PHash; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.stereotype.Service; +import ru.soune.nocopy.dto.file.SimilarFileDTO; +import ru.soune.nocopy.entity.file.FileEntity; +import ru.soune.nocopy.entity.file.ImageHashEntity; +import ru.soune.nocopy.repository.FileEntityRepository; +import ru.soune.nocopy.repository.ImageHashRepository; +import ru.soune.nocopy.service.FileSimilarityService; +import ru.soune.nocopy.util.FileUtil; + +import java.util.List; +@Slf4j +@Service +@RequiredArgsConstructor +public class ImageLocalSearchImpl implements ImageLocalSearch { + + private final FileSimilarityService fileSimilarityService; + + private final ImageHashRepository imageHashRepository; + + private final FileUtil fileUtil; + + private final FileEntityRepository fileEntityRepository; + + @Override + public @NotNull List find(@NotNull FileProtector.FileInfo fileInfo) { + List duplicateByHammingDistance = + fileSimilarityService.findDuplicateByHammingDistance(fileInfo.getId(), 10, + 5, 10); + + return duplicateByHammingDistance.stream() + .map(fileUtil::convertToResult) + .toList(); + } + + @Override + public @Nullable FileProtector.FileInfo findByPHash(@NotNull PHash pHash) { + ImageHashEntity imageHashEntity = + imageHashRepository.findByHash64HiAndHash64Lo(Math.toIntExact(pHash.getFirstPart()), + Math.toIntExact(pHash.getSecondPart())); + FileEntity file = fileEntityRepository.findByFileId(imageHashEntity.getFileId()); + + return new FileProtector.FileInfo(FileProtector.Type.IMAGE, imageHashEntity.getFileId(), + String.valueOf(file.getUserId())); + } +} diff --git a/src/main/java/ru/soune/nocopy/service/file/impl/ImageUniqueCheckImpl.java b/src/main/java/ru/soune/nocopy/service/file/impl/ImageUniqueCheckImpl.java new file mode 100644 index 0000000..c883a4d --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/file/impl/ImageUniqueCheckImpl.java @@ -0,0 +1,34 @@ +package ru.soune.nocopy.service.file.impl; + +import com.vrt.fileprotection.FileProtector; +import com.vrt.fileprotection.OperationResult; +import com.vrt.fileprotection.image.ImageUniqueCheck; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Service; +import ru.soune.nocopy.dto.file.SimilarFileDTO; +import ru.soune.nocopy.service.FileSimilarityService; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ImageUniqueCheckImpl implements ImageUniqueCheck { + + private final FileSimilarityService fileSimilarityService; + + @Override + public @NotNull OperationResult check(@NotNull FileProtector.FileInfo fileInfo) { + String fileId = fileInfo.getId(); + List duplicateByHammingDistance = + fileSimilarityService.findDuplicateByHammingDistance(fileId, 3, 3, 4); + + if (duplicateByHammingDistance == null || duplicateByHammingDistance.isEmpty()) { + return OperationResult.Companion.success(); + } else { + return OperationResult.Companion.failure("Duplicate file"); + } + } +} diff --git a/src/main/java/ru/soune/nocopy/service/file/impl/NoCopyProcessingListenerImpl.java b/src/main/java/ru/soune/nocopy/service/file/impl/NoCopyProcessingListenerImpl.java new file mode 100644 index 0000000..257cf65 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/file/impl/NoCopyProcessingListenerImpl.java @@ -0,0 +1,36 @@ +package ru.soune.nocopy.service.file.impl; + +import com.vrt.fileprotection.FileProtector; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.soune.nocopy.entity.file.ProtectionStatus; +import ru.soune.nocopy.service.file.FileEntityService; + +@Slf4j +@Component +@AllArgsConstructor +public class NoCopyProcessingListenerImpl implements FileProtector.ProcessingListener { + + private final FileEntityService fileEntityService; + + @Override + public void onStartProcessing(FileProtector.FileInfo fileInfo) { + fileEntityService.changeStatus(ProtectionStatus.PROCESSING, fileInfo.getId()); + } + + @Override + public void onProcessingFailed(FileProtector.FileInfo fileInfo) { + fileEntityService.changeStatus(ProtectionStatus.FAILED, fileInfo.getId()); + } + + @Override + public void onSavingFailed(FileProtector.FileInfo fileInfo) { + fileEntityService.changeStatus(ProtectionStatus.FAILED_SAVE, fileInfo.getId()); + } + + @Override + public void onFinish(FileProtector.FileInfo fileInfo) { + fileEntityService.changeStatus(ProtectionStatus.PROTECTED, fileInfo.getId());; + } +} diff --git a/src/main/java/ru/soune/nocopy/service/file/impl/ProtectionFileProviderImpl.java b/src/main/java/ru/soune/nocopy/service/file/impl/ProtectionFileProviderImpl.java new file mode 100644 index 0000000..62c9aa1 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/file/impl/ProtectionFileProviderImpl.java @@ -0,0 +1,84 @@ +package ru.soune.nocopy.service.file.impl; + +import com.vrt.fileprotection.FileProtector; +import com.vrt.fileprotection.OperationResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.stereotype.Component; +import ru.soune.nocopy.service.file.FileEntityService; + +import java.io.File; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ProtectionFileProviderImpl implements FileProtector.FileProvider { + + private final FileEntityService fileEntityService; + + @Nullable + @Override + public File getImageFile(@NotNull String id) { + return fileEntityService.getFileById(id); + } + + @Nullable + @Override + public File getVideoFile(@NotNull String id) { + return fileEntityService.getFileById(id); + } + + @Nullable + @Override + public File getAudioFile(@NotNull String id) { + return fileEntityService.getFileById(id); + } + + @Override + public @Nullable File getSignature() { + return null; + } + + @NotNull + @Override + public com.vrt.fileprotection.OperationResult writeAudioFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) { + try { + fileEntityService.writeProtectedFile(id, data, fileExt); + return OperationResult.Companion.success(); + } catch (Exception e) { + log.error("Failed to create protected file: {}", id, e); + return OperationResult.Companion.failure("Failed to create protected file"); + } + } + + @NotNull + @Override + public com.vrt.fileprotection.OperationResult writeVideoFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) { + try { + fileEntityService.writeProtectedFile(id, data, fileExt); + return OperationResult.Companion.success(); + } catch (Exception e) { + log.error("Failed to create protected file: {}", id, e); + return OperationResult.Companion.failure("Failed to create protected file"); + } + } + + @NotNull + @Override + public com.vrt.fileprotection.OperationResult writeImageFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) { + try { + fileEntityService.writeProtectedFile(id, data, fileExt); + return OperationResult.Companion.success(); + } catch (Exception e) { + log.error("Failed to create protected file: {}", id, e); + return OperationResult.Companion.failure("Failed to create protected file"); + } + } + + @Override + public @NotNull OperationResult writeSignature(@NotNull byte[] bytes) { + return null; + } +} diff --git a/src/main/java/ru/soune/nocopy/util/FileUtil.java b/src/main/java/ru/soune/nocopy/util/FileUtil.java new file mode 100644 index 0000000..bad535d --- /dev/null +++ b/src/main/java/ru/soune/nocopy/util/FileUtil.java @@ -0,0 +1,48 @@ +package ru.soune.nocopy.util; + +import com.vrt.fileprotection.FileProtector; +import com.vrt.fileprotection.image.ImageLocalSearch; +import com.vrt.fileprotection.image.ImageScore; +import org.springframework.stereotype.Service; +import ru.soune.nocopy.dto.file.SimilarFileDTO; +import ru.soune.nocopy.entity.file.FileEntity; + +@Service +public class FileUtil { + public ImageLocalSearch.Result convertToResult(SimilarFileDTO response) { + FileProtector.FileInfo fileInfo = + new FileProtector.FileInfo(FileProtector.Type.IMAGE, response.getFileId(), + String.valueOf(response.getOwnerId())); + ImageScore imageScore = new ImageScore(ImageScore.Rate.valueOf(response.getSimilarityLevel()), + response.getHammingDistance()); + + return new ImageLocalSearch.Result(fileInfo, imageScore); + } + + public FileProtector.FileInfo createFileInfo(FileEntity fileEntity) { + FileProtector.Type type = determineFileType(fileEntity.getMimeType()); + + return new FileProtector.FileInfo(type, fileEntity.getId(), String.valueOf(fileEntity.getUserId())); + } + + public int hamming64(int aHi, int aLo, int bHi, int bLo) { + return Integer.bitCount(aHi ^ bHi) + + Integer.bitCount(aLo ^ bLo); + } + + private FileProtector.Type determineFileType(String mimeType) { + if (mimeType == null) { + return FileProtector.Type.IMAGE; + } + + if (mimeType.startsWith("image")) { + return FileProtector.Type.IMAGE; + } else if (mimeType.startsWith("video")) { + return FileProtector.Type.VIDEO; + } else if (mimeType.startsWith("audio")) { + return FileProtector.Type.AUDIO; + } else { + return FileProtector.Type.IMAGE; + } + } +}