diff --git a/src/main/java/ru/soune/nocopy/controller/ApiController.java b/src/main/java/ru/soune/nocopy/controller/ApiController.java index d604b1e..4b66f49 100644 --- a/src/main/java/ru/soune/nocopy/controller/ApiController.java +++ b/src/main/java/ru/soune/nocopy/controller/ApiController.java @@ -114,7 +114,8 @@ public class ApiController { @PathVariable("version") int version, @RequestParam(value = "upload_id", required = false) String uploadId, @RequestParam(value = "chunk_number", required = false) Integer chunkNumber, - @RequestParam(value = "chunk", required = false) MultipartFile chunk) { + @RequestParam(value = "chunk", required = false) MultipartFile chunk, + @RequestParam(value = "findSimilar", required = false) Integer findSimilar) { try { if (chunk == null || chunk.isEmpty()) { return buildErrorResponse(uploadId, chunkNumber, "Chunk file null or empty"); @@ -126,9 +127,11 @@ public class ApiController { return buildErrorResponse(uploadId, chunkNumber, "Valid chunk number is required"); } - fileUploadService.uploadChunk(uploadId, chunkNumber, chunk); + UploadProgressResponse uploadProgressResponse = fileUploadService.uploadChunk(uploadId, chunkNumber, + chunk, findSimilar); - return buildSuccessResponse(uploadId, chunkNumber, chunk); + return buildSuccessResponse(uploadId, chunkNumber, chunk, + fileEntityService.findFileIdByPath(uploadProgressResponse.getFilePath())); } catch (DuplicateImageException e) { Map duplicateData = new HashMap<>(); duplicateData.put("duplicateFileId", e.duplicateFileId()); @@ -151,16 +154,17 @@ public class ApiController { } } - @GetMapping("/v{version}/files/{fileId}/similar") + @GetMapping("/v{version}/files/{fileId}/similar/{authToken}") public ResponseEntity findSimilarFiles( @PathVariable("version") int version, @PathVariable String fileId, + @PathVariable(required = false) String authToken, @RequestParam(required = false) List similarityLevels, @PageableDefault(size = 20, sort = "hammingDistance") Pageable pageable) { SimilarityFilter filter = SimilarityFilter.builder() .similarityLevels(similarityLevels) .build(); - Page similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable); + Page similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable, authToken); String messageDesc; MessageCode success; @@ -459,11 +463,13 @@ public class ApiController { return null; } - private ResponseEntity buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk) { + private ResponseEntity buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk, + String fileId) { ChunkUploadResponse responseBody = ChunkUploadResponse.builder() .uploadId(uploadId) .chunkNumber(chunkNumber) .chunkSize(chunk.getSize()) + .fileId(fileId) .message("Chunk uploaded successfully") .build(); diff --git a/src/main/java/ru/soune/nocopy/dto/file/ChunkUploadResponse.java b/src/main/java/ru/soune/nocopy/dto/file/ChunkUploadResponse.java index 09bce18..f2a8f38 100644 --- a/src/main/java/ru/soune/nocopy/dto/file/ChunkUploadResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/ChunkUploadResponse.java @@ -20,6 +20,9 @@ public class ChunkUploadResponse { @JsonProperty("chunk_size") private Long chunkSize; + @JsonProperty("file_id") + private String fileId; + @JsonProperty("message") private String message; } diff --git a/src/main/java/ru/soune/nocopy/dto/file/FileEntityRequest.java b/src/main/java/ru/soune/nocopy/dto/file/FileEntityRequest.java index 4682dac..560fec3 100644 --- a/src/main/java/ru/soune/nocopy/dto/file/FileEntityRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/file/FileEntityRequest.java @@ -17,6 +17,9 @@ public class FileEntityRequest { @JsonProperty("file_id") private String fileId; + @JsonProperty("full_delete") + private Integer fullDelete; + @JsonProperty("upload_session_id") private String uploadSessionId; diff --git a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java index 4676310..7f6ae8f 100644 --- a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java @@ -273,7 +273,7 @@ public class FileEntityHandler implements RequestHandler { FileEntity fileEntity = fileEntityRepository.findById(fileId) .orElseThrow(() -> new FileEntityNotFoundException(fileId)); - if (fileEntity.getStatus().equals(FileStatus.DELETED)) { + if (fileEntity.getStatus().equals(FileStatus.DELETED) || fileRequest.getFullDelete() == 1) { fileEntityService.deleteFromDisk(fileEntity); response = DeleteFileResponse.builder() .fileId(fileRequest.getFileId()) diff --git a/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java index cbd7529..5f5e8bb 100644 --- a/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java @@ -1,6 +1,7 @@ package ru.soune.nocopy.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import ru.soune.nocopy.entity.AuthToken; @@ -13,4 +14,9 @@ public interface AuthTokenRepository extends JpaRepository { List findByExpiresAtBefore(LocalDateTime expiresAtBefore); Optional findByLastUsedAtBefore(LocalDateTime lastUsedAt); Optional findByToken(String token); + @Query(value = """ + SELECT a.user_id FROM auth_tokens a WHERE a.token = :token + """, + nativeQuery = true) + Long findUserIdByToken(String token); } diff --git a/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java b/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java index 26d6df6..7ceb303 100644 --- a/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java @@ -46,5 +46,8 @@ public interface FileEntityRepository extends JpaRepository @Query("SELECT f FROM FileEntity f WHERE f.signature = :signature") FileEntity findBySignature(@Param("signature") String signature); + @Query("SELECT f.id FROM FileEntity f WHERE f.filePath = :filePath") + String findFileIdByFilePath(@Param("filePath") String filePath); + long countByUserId(Long userId); } diff --git a/src/main/java/ru/soune/nocopy/repository/ImageSimilarityRepository.java b/src/main/java/ru/soune/nocopy/repository/ImageSimilarityRepository.java index 6e0acca..345cd43 100644 --- a/src/main/java/ru/soune/nocopy/repository/ImageSimilarityRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/ImageSimilarityRepository.java @@ -31,6 +31,28 @@ public interface ImageSimilarityRepository @Param("fileId") String fileId ); + @Query(value = """ + SELECT + f.id AS id, + f.original_file_name AS originalFileName, + f.file_size AS fileSize, + f.user_id AS userId, + h.hash64_hi AS hash64Hi, + h.hash64_lo AS hash64Lo + FROM image_hashes ref + JOIN image_hashes h + ON ref.file_id <> h.file_id + JOIN file_entities f + ON f.id = h.file_id + WHERE ref.file_id = :fileId AND f.user_id = :userId + """, + nativeQuery = true) + List findCandidatesFromUserFiles( + @Param("fileId") String fileId, + @Param("userId") Long userId + ); + + @Query(value = """ SELECT h.hash64_hi AS hash64Hi, diff --git a/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java b/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java index 8b5449d..adae2bd 100644 --- a/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java +++ b/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Service; import ru.soune.nocopy.dto.file.SimilarFileDTO; import ru.soune.nocopy.dto.file.SimilarityFilter; import ru.soune.nocopy.entity.file.ImageHashEntity; +import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.ImageHashRepository; import ru.soune.nocopy.repository.ImageSimilarityRepository; import ru.soune.nocopy.repository.SimilarImageProjection; @@ -28,6 +29,8 @@ public class FileSimilarityService { private final FileUtil fileUtil; + private final AuthTokenRepository authTokenRepository; + public List findSimilarFiles(String fileId) { var imageHashEntity = hashRepository.findById(fileId) .orElseThrow(() -> new RuntimeException("Hash not found")); @@ -128,14 +131,15 @@ public class FileSimilarityService { return duplicates; } - public Page findSimilarFiles(String fileId, SimilarityFilter filter, Pageable pageable) { + public Page findSimilarFiles(String fileId, SimilarityFilter filter, Pageable pageable, String authToken) { var imageHashEntity = hashRepository.findById(fileId) .orElseThrow(() -> new RuntimeException("Hash not found")); Long hash64Hi = imageHashEntity.getHash64Hi(); Long hash64Lo = imageHashEntity.getHash64Lo(); - List candidates = repository.findCandidates(fileId); + List candidates = authToken.equals("all") ? repository.findCandidates(fileId): + repository.findCandidatesFromUserFiles(fileId, authTokenRepository.findUserIdByToken(authToken)); List similarityLevels = (filter != null && filter.getSimilarityLevels() != null) ? filter.getSimilarityLevels() 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 f55cc81..fd4c8bc 100644 --- a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java @@ -202,7 +202,8 @@ public class FileEntityService { Files.delete(path); - markAsDeleted(fileEntity); +// markAsDeleted(fileEntity); + fileEntityRepository.delete(fileEntity); return true; } @@ -248,6 +249,10 @@ public class FileEntityService { return fileEntityRepository.findBySignature(signature); } + public String findFileIdByPath(String filePath) { + return fileEntityRepository.findFileIdByFilePath(filePath); + } + public File getFileById(String id) { try { FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() -> diff --git a/src/main/java/ru/soune/nocopy/service/file/FileUploadService.java b/src/main/java/ru/soune/nocopy/service/file/FileUploadService.java index d233b63..de8d09d 100644 --- a/src/main/java/ru/soune/nocopy/service/file/FileUploadService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileUploadService.java @@ -14,7 +14,7 @@ public interface FileUploadService { void cancelUpload(String uploadId); - void uploadChunk(String uploadId, Integer chunkNumber, MultipartFile chunkFile); + UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, MultipartFile chunkFile, Integer findSimilar); UploadProgressResponse getUploadProgress(String uploadId); diff --git a/src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java b/src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java index 862a511..258e29c 100644 --- a/src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java +++ b/src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java @@ -177,8 +177,8 @@ public class FileUploadServiceImpl implements FileUploadService { @Override @Transactional - public void uploadChunk(String uploadId, Integer chunkNumber, - MultipartFile chunkFile) { + public UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, + MultipartFile chunkFile, Integer findSimilar) { FileUploadSession session = sessionRepository.findById(uploadId) .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); @@ -204,7 +204,7 @@ public class FileUploadServiceImpl implements FileUploadService { throw new ChunkSizeExceededException(chunkFile.getSize(), chunkSize); } - processChunk(session, chunkNumber, chunkFile); + return processChunk(session, chunkNumber, chunkFile, findSimilar); } @Override @@ -285,12 +285,13 @@ public class FileUploadServiceImpl implements FileUploadService { } } - private UploadProgressResponse processChunk(FileUploadSession session, Integer chunkNumber, MultipartFile chunkFile) { + private UploadProgressResponse processChunk(FileUploadSession session, Integer chunkNumber, MultipartFile chunkFile, + Integer findSimilar) { String chunkPath = null; try { if (session.getChunkPaths().containsKey(chunkNumber)) { - return handleExistingChunk(session, chunkNumber, chunkFile); + return handleExistingChunk(session, chunkNumber, chunkFile, findSimilar); } chunkPath = saveChunkWithIntegrityCheck(session, chunkNumber, chunkFile); @@ -311,7 +312,7 @@ public class FileUploadServiceImpl implements FileUploadService { String finalFilePath = assembleFileSynchronously(session); - if (session.getFileType().startsWith("image")) { + if (session.getFileType().startsWith("image") && findSimilar == 0) { checkForDuplicatesSynchronously(finalFilePath); } @@ -381,7 +382,8 @@ public class FileUploadServiceImpl implements FileUploadService { private UploadProgressResponse handleExistingChunk(FileUploadSession session, Integer chunkNumber, - MultipartFile chunkFile) throws IOException { + MultipartFile chunkFile, + Integer findSimilar) throws IOException { String existingPath = session.getChunkPaths().get(chunkNumber); Path chunkPath = Paths.get(existingPath); @@ -390,7 +392,7 @@ public class FileUploadServiceImpl implements FileUploadService { session.setChunksUploaded(session.getChunksUploaded() - 1); sessionRepository.save(session); - return processChunk(session, chunkNumber, chunkFile); + return processChunk(session, chunkNumber, chunkFile, findSimilar); } long existingSize = Files.size(chunkPath); @@ -401,7 +403,7 @@ public class FileUploadServiceImpl implements FileUploadService { session.setChunksUploaded(session.getChunksUploaded() - 1); sessionRepository.save(session); - return processChunk(session, chunkNumber, chunkFile); + return processChunk(session, chunkNumber, chunkFile, findSimilar); } return UploadProgressResponse.fromSession(session);