This commit is contained in:
@@ -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<String, Object> 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<BaseResponse> findSimilarFiles(
|
||||
@PathVariable("version") int version,
|
||||
@PathVariable String fileId,
|
||||
@PathVariable(required = false) String authToken,
|
||||
@RequestParam(required = false) List<String> similarityLevels,
|
||||
@PageableDefault(size = 20, sort = "hammingDistance") Pageable pageable) {
|
||||
SimilarityFilter filter = SimilarityFilter.builder()
|
||||
.similarityLevels(similarityLevels)
|
||||
.build();
|
||||
Page<SimilarFileDTO> similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable);
|
||||
Page<SimilarFileDTO> similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable, authToken);
|
||||
|
||||
String messageDesc;
|
||||
MessageCode success;
|
||||
@@ -459,11 +463,13 @@ public class ApiController {
|
||||
return null;
|
||||
}
|
||||
|
||||
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk) {
|
||||
private ResponseEntity<BaseResponse> 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();
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ public class ChunkUploadResponse {
|
||||
@JsonProperty("chunk_size")
|
||||
private Long chunkSize;
|
||||
|
||||
@JsonProperty("file_id")
|
||||
private String fileId;
|
||||
|
||||
@JsonProperty("message")
|
||||
private String message;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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<AuthToken, Long> {
|
||||
List<AuthToken> findByExpiresAtBefore(LocalDateTime expiresAtBefore);
|
||||
Optional<AuthToken> findByLastUsedAtBefore(LocalDateTime lastUsedAt);
|
||||
Optional<AuthToken> findByToken(String token);
|
||||
@Query(value = """
|
||||
SELECT a.user_id FROM auth_tokens a WHERE a.token = :token
|
||||
""",
|
||||
nativeQuery = true)
|
||||
Long findUserIdByToken(String token);
|
||||
}
|
||||
|
||||
@@ -46,5 +46,8 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -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<SimilarImageProjection> findCandidatesFromUserFiles(
|
||||
@Param("fileId") String fileId,
|
||||
@Param("userId") Long userId
|
||||
);
|
||||
|
||||
|
||||
@Query(value = """
|
||||
SELECT
|
||||
h.hash64_hi AS hash64Hi,
|
||||
|
||||
@@ -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<SimilarFileDTO> 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<SimilarFileDTO> findSimilarFiles(String fileId, SimilarityFilter filter, Pageable pageable) {
|
||||
public Page<SimilarFileDTO> 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<SimilarImageProjection> candidates = repository.findCandidates(fileId);
|
||||
List<SimilarImageProjection> candidates = authToken.equals("all") ? repository.findCandidates(fileId):
|
||||
repository.findCandidatesFromUserFiles(fileId, authTokenRepository.findUserIdByToken(authToken));
|
||||
|
||||
List<String> similarityLevels = (filter != null && filter.getSimilarityLevels() != null)
|
||||
? filter.getSimilarityLevels()
|
||||
|
||||
@@ -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(() ->
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user