33 Commits
Author SHA1 Message Date
backdev 4dd6c4a3c0 dev merge
Test Workflow / test (push) Has been cancelled
2026-04-22 16:02:52 +07:00
backdev da84490198 dev add new action
Test Workflow / test (push) Has been cancelled
2026-04-22 14:23:35 +07:00
backdev b723247b9c dev add new action
Test Workflow / test (push) Has been cancelled
2026-04-22 14:04:57 +07:00
backdev cc43b92f69 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-22 02:06:39 +07:00
backdev d92a9a7d86 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-22 01:50:18 +07:00
backdev 6dec7838ce dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-22 01:06:19 +07:00
backdev 83ed7c5b01 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 21:11:47 +07:00
backdev e66ae81d18 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 19:34:43 +07:00
backdev 6c3742913c dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 19:09:27 +07:00
backdev dfc9a22bc5 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 18:11:36 +07:00
backdev d0be6580e4 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 11:50:25 +07:00
backdev 1dd6a6b7e2 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 11:01:15 +07:00
backdev c33ffe516a dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 10:43:13 +07:00
backdev ac7cc54b5e dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 10:22:42 +07:00
backdev ea7dd5e5df dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 10:11:31 +07:00
backdev c2a649a252 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 09:26:57 +07:00
backdev f680f766cd dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 09:26:03 +07:00
backdev 82827c9a2a dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 03:45:23 +07:00
backdev e6e8461b9b dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 03:27:47 +07:00
backdev 1b2337ea2f dev fix thumbnal path
Test Workflow / test (push) Has been cancelled
2026-04-21 03:11:52 +07:00
backdev 5fc9563a4e dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 02:59:21 +07:00
backdev fdc17b8325 dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 02:21:33 +07:00
backdev 3b68b3337c dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 02:10:17 +07:00
backdev 631066b217 dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 01:52:48 +07:00
backdev 61dcada973 dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 01:43:40 +07:00
backdev 7492b8aa7f dev fix file public endpoint
Test Workflow / test (push) Has been cancelled
2026-04-21 01:31:30 +07:00
backdev 9ad4830edb dev fix file public endpoint
Test Workflow / test (push) Has been cancelled
2026-04-21 01:06:50 +07:00
backdev 6366cbc96b dev change
Test Workflow / test (push) Has been cancelled
2026-04-21 00:36:22 +07:00
backdev 4e0e953253 dev change
Test Workflow / test (push) Has been cancelled
2026-04-20 19:50:32 +07:00
backdev 1476509b48 dev change
Test Workflow / test (push) Has been cancelled
2026-04-20 18:50:47 +07:00
backdev 9cde6f3bb6 dev change logic for get files for search
Test Workflow / test (push) Has been cancelled
2026-04-20 18:31:32 +07:00
backdev 9cd5965b37 dev change logic for get files for search
Test Workflow / test (push) Has been cancelled
2026-04-20 18:16:46 +07:00
backdev 2ab91dee58 dev fix save to storage
Test Workflow / test (push) Has been cancelled
2026-04-20 14:26:10 +07:00
11 changed files with 266 additions and 128 deletions
@@ -1,5 +1,7 @@
package ru.soune.nocopy.controller; package ru.soune.nocopy.controller;
import com.google.api.client.util.IOUtils;
import com.google.common.io.ByteStreams;
import com.vrt.NoCopyFileService; import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector; import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.NoCopyCheckResult; import com.vrt.fileprotection.NoCopyCheckResult;
@@ -7,16 +9,21 @@ import com.vrt.fileprotection.audio.AudioCheckResult;
import com.vrt.fileprotection.documents.DocumentCheckResult; import com.vrt.fileprotection.documents.DocumentCheckResult;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault; import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError; import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import ru.soune.nocopy.dto.BaseRequest; import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse; import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.MessageCode;
@@ -41,9 +48,11 @@ import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.service.register.AuthService; import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.user.moderation.UserVerificationService; import ru.soune.nocopy.service.user.moderation.UserVerificationService;
import ru.soune.nocopy.util.FileUtil; import ru.soune.nocopy.util.FileUtil;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -493,40 +502,35 @@ public class ApiController {
MessageCode.FILE_DELETE.getDescription(), MessageCode.FILE_DELETE.getDescription(),
errorData)); errorData));
} }
//TODO
if (!entityResponse.isExistsOnDisk()) { // if (!entityResponse.isExistsOnDisk()) {
Map<String, Object> errorData = new HashMap<>(); // Map<String, Object> errorData = new HashMap<>();
errorData.put("onDisk", entityResponse.isExistsOnDisk()); // errorData.put("onDisk", entityResponse.isExistsOnDisk());
//
// return ResponseEntity.ok().body(new BaseResponse(20004,
// MessageCode.FILE_NOT_EXIST.getCode(),
// MessageCode.FILE_NOT_EXIST.getDescription(),
// errorData));
// }
return ResponseEntity.ok().body(new BaseResponse(20004, // if (!file.exists()) {
MessageCode.FILE_NOT_EXIST.getCode(), // Map<String, Object> errorData = new HashMap<>();
MessageCode.FILE_NOT_EXIST.getDescription(), // errorData.put("file", file.exists());
errorData)); //
} // return ResponseEntity.ok().body(new BaseResponse(20004,
// MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
// MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
// errorData));
// }
Path filePath = Paths.get(entityResponse.getProtectedFilePath()); byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
File file = cloudStorageService.readFileFromStorageByPath(
entityResponse.getProtectedFilePath());
long fileSize = Files.size(filePath);
if (!file.exists()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("file", file.exists());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData));
}
String contentType = determineContentType(filePath);
return ResponseEntity.ok() return ResponseEntity.ok()
.contentLength(fileSize) .contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, .header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + entityResponse.getFileName() + "\"") "attachment; filename=\"" + entityResponse.getFileName() + "\"")
.body(file); .body(bytes);
} catch (FileEntityNotFoundException e) { } catch (FileEntityNotFoundException e) {
Map<String, Object> errorData = new HashMap<>(); Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", fileId); errorData.put("fileId", fileId);
@@ -543,16 +547,16 @@ public class ApiController {
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(), MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(), MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
errorData)); errorData));
} catch (IOException e) { // } catch (IOException e) {
Map<String, Object> errorData = new HashMap<>(); // Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader); // errorData.put("token", tokenHeader);
errorData.put("fileId", fileId); // errorData.put("fileId", fileId);
errorData.put("version", version); // errorData.put("version", version);
//
return ResponseEntity.ok().body(new BaseResponse(20004, // return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(), // MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(), // MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(),
errorData)); // errorData));
} }
} }
@@ -625,13 +629,4 @@ public class ApiController {
return errorDetail; return errorDetail;
} }
private String determineContentType(Path filePath) throws IOException {
String contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
return contentType;
}
} }
@@ -1,12 +1,16 @@
package ru.soune.nocopy.controller; package ru.soune.nocopy.controller;
import com.google.api.client.util.IOUtils;
import com.google.common.io.ByteStreams;
import com.vrt.NoCopyFileService; import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector; import com.vrt.fileprotection.FileProtector;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import ru.soune.nocopy.dto.file.CheckStatus; import ru.soune.nocopy.dto.file.CheckStatus;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.ProtectionStatus; import ru.soune.nocopy.entity.file.ProtectionStatus;
@@ -18,13 +22,14 @@ import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.FileStorageService; import ru.soune.nocopy.service.file.FileStorageService;
import ru.soune.nocopy.service.file.ImageResizeService; import ru.soune.nocopy.service.file.ImageResizeService;
import ru.soune.nocopy.service.file.ZipService; import ru.soune.nocopy.service.file.ZipService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.util.FileUtil; import ru.soune.nocopy.util.FileUtil;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import java.io.FileNotFoundException; import java.io.*;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -51,19 +56,13 @@ public class FileController {
private ZipService zipService; private ZipService zipService;
private CloudStorageService cloudStorageService;
@GetMapping("/public/{fileId}") @GetMapping("/public/{fileId}")
public ResponseEntity<Resource> getPublicFile(@PathVariable String fileId) { public ResponseEntity<StreamingResponseBody> getPublicFile(@PathVariable String fileId) {
try { try {
FileEntity fileEntity = fileRepository.findById(fileId) FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new FileNotFoundException("Not found: " + fileId)); .orElseThrow(() -> new FileNotFoundException("Not found in DB: " + fileId));
Path filePath = Paths.get(fileEntity.getFilePath());
if (!Files.exists(filePath)) {
log.error("Not on disk: {}", fileEntity.getFilePath());
return ResponseEntity.notFound().build();
}
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath());
MediaType mediaType = getMediaType(fileEntity); MediaType mediaType = getMediaType(fileEntity);
@@ -71,18 +70,28 @@ public class FileController {
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8) .filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build(); .build();
StreamingResponseBody responseBody = outputStream -> {
try (InputStream inputStream = cloudStorageService.readFileFromStorage(fileEntity.getFilePath())) {
ByteStreams.copy(inputStream, outputStream);
} catch (NoSuchKeyException e) {
log.error("File disappeared from S3 during streaming: {}", fileId, e);
throw new IOException("File not found in storage", e);
}
};
return ResponseEntity.ok() return ResponseEntity.ok()
.contentType(mediaType) .contentType(mediaType)
.contentLength(Files.size(filePath))
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString()) .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600") .header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
.body(resource); .body(responseBody);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
log.warn("Not on disk: {}", fileId); log.warn("File not found in DB: {}", fileId);
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} catch (IOException e) { } catch (NoSuchKeyException e) {
log.error("Read file exception: {}", fileId, e); log.warn("File not found in S3: {}", fileId);
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (Exception e) {
log.error("Error reading file: {}", fileId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
} }
} }
@@ -113,9 +122,14 @@ public class FileController {
FileEntity fileEntity = fileRepository.findById(fileId) FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found")); .orElseThrow(() -> new RuntimeException("File not found"));
String path = imageResizeService.getPath(fileEntity, type); String filePath = type.equals("thumbnail") ? fileEntity.getThumbnailPath(): fileEntity.getFilePath();
Resource resource = fileStorageService.loadFileAsResource(path); // InputStream file = cloudStorageService.readFileFromStorage(filePath);
// if (file == null || !file.exists()) {
// log.error("File not found in storage: {}", fileEntity.getFilePath());
// return ResponseEntity.notFound().build();
// }
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED || if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) { fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
@@ -128,6 +142,8 @@ public class FileController {
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8) String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
.replace("+", "%20"); .replace("+", "%20");
InputStreamResource resource = new InputStreamResource(cloudStorageService.readFileFromStorage(filePath));
return ResponseEntity.ok() return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension())) .contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION, .header(HttpHeaders.CONTENT_DISPOSITION,
@@ -117,7 +117,7 @@ public class FileSimilarityService {
} }
public FileEntity findDuplicateByHash(String path, String mimeType, Long userId) throws Exception { public FileEntity findDuplicateByHash(String path, String mimeType, Long userId) throws Exception {
String hash = calculateFileHash(path); String hash = calculateFileHash(path, false);
User user = userRepository.findById(userId).orElseThrow(); User user = userRepository.findById(userId).orElseThrow();
Company company = user.getCompany(); Company company = user.getCompany();
@@ -132,15 +132,15 @@ public class FileSimilarityService {
List<FileEntity> fileEntityList = fileEntityRepository.findByMimeTypeAndUserIds(mimeType, userIds); List<FileEntity> fileEntityList = fileEntityRepository.findByMimeTypeAndUserIds(mimeType, userIds);
for (FileEntity file : fileEntityList) { for (FileEntity file : fileEntityList) {
if (file.getProtectedFilePath() == null) continue; // if (file.getProtectedFilePath() == null) continue;
File existingFile = new File(file.getProtectedFilePath()); // File existingFile = cloudStorageService.readFileFromStorageByPath(file.getFilePath());
File newFile = new File(path); // File newFile = new File(path);
if (existingFile.length() == newFile.length()) { // if (existingFile.length() == newFile.length()) {
if (calculateFileHash(file.getProtectedFilePath()).equals(hash)) { if (calculateFileHash(file.getFilePath(), true).equals(hash)) {
return file; return file;
} }
} // }
} }
return null; return null;
@@ -199,15 +199,19 @@ public class FileSimilarityService {
} }
private String calculateFileHash(String path) throws Exception { private String calculateFileHash(String path, boolean cloud) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256"); MessageDigest digest = MessageDigest.getInstance("SHA-256");
File file = cloudStorageService.readFileFromStorageByPath(path);
try (InputStream is = new FileInputStream(file)) { if (cloud) {
byte[] buffer = new byte[8192]; byte[] data = cloudStorageService.readFileFromStorageBytes(path);
int read; digest.update(data);
while ((read = is.read(buffer)) > 0) { } else {
digest.update(buffer, 0, read); try (InputStream is = new FileInputStream(path)) {
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
} }
} }
@@ -12,6 +12,7 @@ import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Map; import java.util.Map;
@@ -25,20 +26,48 @@ public class ImageHashService {
private final CloudStorageService cloudStorageService; private final CloudStorageService cloudStorageService;
// public Map<String, Long> calculateHash(Path imagePath) throws IOException {
//// File file = cloudStorageService.readFileFromStorageByPath(imagePath.toString());
// File file = imagePath.toFile();
// if (file == null) {
// file = cloudStorageService.readFileFromStorageByPath(imagePath.toString());
// }
//
// PHash pHash = PerceptualHashHelper.INSTANCE.generateDCTPerceptualHash(file);
//
// Long firstPart = pHash.getFirstPart();
// Long secondPart = pHash.getSecondPart();
// Map<String, Long> hash = Map.of(
// "hi", firstPart,
// "low", secondPart);
//
// return hash;
// }
public Map<String, Long> calculateHash(Path imagePath) throws IOException { public Map<String, Long> calculateHash(Path imagePath) throws IOException {
// File file = cloudStorageService.readFileFromStorageByPath(imagePath.toString()); PHash pHash;
File file = imagePath.toFile();
if (imagePath.toFile().exists()) {
pHash = PerceptualHashHelper.INSTANCE.generateDCTPerceptualHash(imagePath.toFile());
} else {
byte[] data = cloudStorageService.readFileFromStorageBytes(imagePath.toString());
PHash pHash = PerceptualHashHelper.INSTANCE.generateDCTPerceptualHash(file); Path tempFile = Files.createTempFile("perceptual_hash_", ".tmp");
try {
Files.write(tempFile, data);
pHash = PerceptualHashHelper.INSTANCE.generateDCTPerceptualHash(tempFile.toFile());
} finally {
Files.deleteIfExists(tempFile);
}
}
Long firstPart = pHash.getFirstPart(); Long firstPart = pHash.getFirstPart();
Long secondPart = pHash.getSecondPart(); Long secondPart = pHash.getSecondPart();
Map<String, Long> hash = Map.of(
"hi", firstPart,
"low", secondPart);
return hash; return Map.of(
"hi", firstPart,
"low", secondPart
);
} }
public void create(FileEntity file, Map<String, Long> stringIntegerMap) { public void create(FileEntity file, Map<String, Long> stringIntegerMap) {
@@ -19,14 +19,15 @@ import ru.soune.nocopy.exception.FileEntityNotFoundException;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.FileMonitoringRepository; import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.FileSimilarityService;
import ru.soune.nocopy.service.ImageHashService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService; import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -240,11 +241,20 @@ public class FileEntityService {
String extension = determineFileExtension(fileExt, fileEntity); String extension = determineFileExtension(fileExt, fileEntity);
Path protectedFilePath = prepareProtectedPath(fileEntity, extension); Path protectedFilePath = prepareProtectedPath(fileEntity, extension);
if (Files.exists(protectedFilePath)) { Files.createDirectories(protectedFilePath.getParent());
Files.delete(protectedFilePath);
}
Files.write(protectedFilePath, data); Files.deleteIfExists(protectedFilePath);
try (FileChannel channel = FileChannel.open(protectedFilePath,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE)) {
ByteBuffer buffer = ByteBuffer.wrap(data);
while (buffer.hasRemaining()) {
channel.write(buffer);
}
channel.force(true);
}
if (imageResizeService.isImage(extension)) { if (imageResizeService.isImage(extension)) {
imageResizeService.generateSizes(fileEntity, data); imageResizeService.generateSizes(fileEntity, data);
@@ -300,7 +310,7 @@ public class FileEntityService {
return protectedPath; return protectedPath;
} }
public void updateSignature(String signature, String fileId) throws IOException { public FileEntity updateSignature(String signature, String fileId) throws IOException {
FileEntity fileEntity = fileEntityRepository.findByFileId(fileId); FileEntity fileEntity = fileEntityRepository.findByFileId(fileId);
fileEntity.setSignature(signature); fileEntity.setSignature(signature);
fileEntity.setProtectionStatus(ProtectionStatus.PROTECTED); fileEntity.setProtectionStatus(ProtectionStatus.PROTECTED);
@@ -308,7 +318,7 @@ public class FileEntityService {
fileEntity.setProtectedAt(LocalDateTime.now()); fileEntity.setProtectedAt(LocalDateTime.now());
fileEntity.setProtectedFilePath(prepareProtectedPath(fileEntity, fileEntity.getFileExtension()).toString()); fileEntity.setProtectedFilePath(prepareProtectedPath(fileEntity, fileEntity.getFileExtension()).toString());
fileEntityRepository.save(fileEntity); return fileEntityRepository.save(fileEntity);
} }
public String formatFileSize(long size) { public String formatFileSize(long size) {
@@ -323,14 +333,14 @@ public class FileEntityService {
} }
} }
private boolean checkFileExistsOnDisk(String filePath) { // private boolean checkFileExistsOnDisk(String filePath) {
try { // try {
return Files.exists(Paths.get(filePath)); // return Files.exists(Paths.get(filePath));
} catch (Exception e) { // } catch (Exception e) {
log.warn("Error checking file existence: {}", filePath, e); // log.warn("Error checking file existence: {}", filePath, e);
return false; // return false;
} // }
} // }
private String determineFileExtension(String fileExt, FileEntity fileEntity) { private String determineFileExtension(String fileExt, FileEntity fileEntity) {
if (fileExt != null) { if (fileExt != null) {
@@ -343,7 +353,7 @@ public class FileEntityService {
private final FileMonitoringRepository fileMonitoringRepository; private final FileMonitoringRepository fileMonitoringRepository;
private FileEntityResponse convertToResponse(FileEntity fileEntity, int version) { private FileEntityResponse convertToResponse(FileEntity fileEntity, int version) {
boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath()); // boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath());
User user = userRepository.findById(fileEntity.getUserId()).get(); User user = userRepository.findById(fileEntity.getUserId()).get();
String fileName = fileEntity.getOriginalFileName(); String fileName = fileEntity.getOriginalFileName();
String name = fileName; String name = fileName;
@@ -375,7 +385,7 @@ public class FileEntityService {
.updatedAt(fileEntity.getUpdatedAt()) .updatedAt(fileEntity.getUpdatedAt())
.formattedSize(formatFileSize(fileEntity.getFileSize())) .formattedSize(formatFileSize(fileEntity.getFileSize()))
.downloadUrl("/api/v" + version + "/files/download/" + fileEntity.getId()) .downloadUrl("/api/v" + version + "/files/download/" + fileEntity.getId())
.existsOnDisk(existsOnDisk) // .existsOnDisk(existsOnDisk)
.supportId(fileEntity.getSupportId()) .supportId(fileEntity.getSupportId())
.protectStatus(fileEntity.getProtectionStatus().toString()) .protectStatus(fileEntity.getProtectionStatus().toString())
.fileName(name + "_nocopy_protected" + "." + fileEntity.getFileExtension()) .fileName(name + "_nocopy_protected" + "." + fileEntity.getFileExtension())
@@ -1,10 +1,12 @@
package ru.soune.nocopy.service.file; package ru.soune.nocopy.service.file;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.*; import java.awt.*;
@@ -18,9 +20,13 @@ import java.util.List;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor
public class ImageResizeService { public class ImageResizeService {
private final CloudStorageService cloudStorageService;
private static final int THUMBNAIL_SIZE = 150; private static final int THUMBNAIL_SIZE = 150;
private static final int MEDIUM_SIZE = 600; private static final int MEDIUM_SIZE = 600;
@Value("${file.storage.base-path}") @Value("${file.storage.base-path}")
@@ -50,11 +56,15 @@ public class ImageResizeService {
if (original.getWidth() > THUMBNAIL_SIZE) { if (original.getWidth() > THUMBNAIL_SIZE) {
String thumbPath = resizeAndSave(original, baseName + "_thumb", THUMBNAIL_SIZE); String thumbPath = resizeAndSave(original, baseName + "_thumb", THUMBNAIL_SIZE);
file.setThumbnailPath(thumbPath); file.setThumbnailPath(thumbPath);
cloudStorageService.saveFilesToStorage(file.getMimeType(), file.getFileExtension(),
thumbPath);
} }
if (original.getWidth() > MEDIUM_SIZE) { if (original.getWidth() > MEDIUM_SIZE) {
String mediumPath = resizeAndSave(original, baseName + "_medium", MEDIUM_SIZE); String mediumPath = resizeAndSave(original, baseName + "_medium", MEDIUM_SIZE);
file.setMediumPath(mediumPath); file.setMediumPath(mediumPath);
cloudStorageService.saveFilesToStorage(file.getMimeType(), file.getFileExtension(),
mediumPath);
} }
} }
} }
@@ -9,18 +9,23 @@ import ru.soune.nocopy.repository.FileEntityRepository;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.UUID;
@Service @Service
@Slf4j @Slf4j
@@ -46,7 +51,6 @@ public class CloudStorageService {
public void saveFilesToStorage(String mimeType, String extension, String sendToCloudFilePath) { public void saveFilesToStorage(String mimeType, String extension, String sendToCloudFilePath) {
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey); AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
S3Client s3Client = S3Client.builder() S3Client s3Client = S3Client.builder()
.httpClient(ApacheHttpClient.create()) .httpClient(ApacheHttpClient.create())
.region(Region.of(region)) .region(Region.of(region))
@@ -63,10 +67,10 @@ public class CloudStorageService {
.key(filePath) .key(filePath)
.contentType(contentType) .contentType(contentType)
.build(); .build();
try { try {
byte[] bytes = Files.readAllBytes(Paths.get(sendToCloudFilePath));
s3Client.putObject(putObjectRequest, s3Client.putObject(putObjectRequest,
RequestBody.fromBytes(Files.readAllBytes(Paths.get(sendToCloudFilePath)))); RequestBody.fromBytes(bytes));
Files.deleteIfExists(Paths.get(sendToCloudFilePath)); Files.deleteIfExists(Paths.get(sendToCloudFilePath));
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to read file: {}", filePath, e); log.error("Failed to read file: {}", filePath, e);
@@ -88,14 +92,61 @@ public class CloudStorageService {
.build(); .build();
String tempDir = System.getProperty("java.io.tmpdir"); String tempDir = System.getProperty("java.io.tmpdir");
String fileName = filePath.replace("/", "_");
String fileName = System.currentTimeMillis() + "_" + UUID.randomUUID() + "_" +
filePath.replace("/", "_");
File tempFile = new File(tempDir, fileName); File tempFile = new File(tempDir, fileName);
s3Client.getObject(objectRequest, tempFile.toPath()); s3Client.getObject(objectRequest, tempFile.toPath());
tempFile.deleteOnExit();
return tempFile; return tempFile;
} }
public byte[] readFileFromStorageBytes(String filePath) {
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
S3Client s3Client = S3Client.builder()
.httpClient(ApacheHttpClient.create())
.region(Region.of(region))
.endpointOverride(URI.create(s3endpoint))
.credentialsProvider(StaticCredentialsProvider.create(credentials))
.build();
GetObjectRequest objectRequest = GetObjectRequest.builder()
.bucket(bucket)
.key("files" + filePath)
.build();
try {
ResponseBytes<GetObjectResponse> responseBytes = s3Client.getObjectAsBytes(objectRequest);
return responseBytes.asByteArray();
} catch (Exception e) {
log.error("Failed to read file from S3: {}", filePath, e);
throw new RuntimeException("Failed to read file from S3", e);
}
}
public InputStream readFileFromStorage(String filePath) {
GetObjectRequest objectRequest = GetObjectRequest.builder()
.bucket(bucket)
.key("files" + filePath)
.build();
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
S3Client s3Client = S3Client.builder()
.httpClient(ApacheHttpClient.create())
.region(Region.of(region))
.endpointOverride(URI.create(s3endpoint))
.credentialsProvider(StaticCredentialsProvider.create(credentials))
.build();
return s3Client.getObject(objectRequest, ResponseTransformer.toInputStream());
}
public void deleteFileFromStorage(String filePath) { public void deleteFileFromStorage(String filePath) {
if (filePath == null || filePath.isEmpty()) { if (filePath == null || filePath.isEmpty()) {
log.warn("Attempted to delete file with empty path"); log.warn("Attempted to delete file with empty path");
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.util.FileUtil; import ru.soune.nocopy.util.FileUtil;
import java.io.IOException; import java.io.IOException;
@@ -22,6 +23,8 @@ public class AudioLocalSearchImpl implements AudioLocalSearch {
private final FileUtil fileUtil; private final FileUtil fileUtil;
private final CloudStorageService cloudStorageService;
@Override @Override
public @Nullable FileProtector.FileInfo findBySignature(@NotNull String signature) { public @Nullable FileProtector.FileInfo findBySignature(@NotNull String signature) {
FileEntity fileEntity = fileEntityService.findBySignature(signature); FileEntity fileEntity = fileEntityService.findBySignature(signature);
@@ -34,7 +37,10 @@ public class AudioLocalSearchImpl implements AudioLocalSearch {
@Override @Override
public void updateSignature(@NotNull String signature, @NotNull FileProtector.FileInfo fileInfo) { public void updateSignature(@NotNull String signature, @NotNull FileProtector.FileInfo fileInfo) {
try { try {
fileEntityService.updateSignature(signature, fileInfo.getId()); FileEntity fileEntity = fileEntityService.updateSignature(signature, fileInfo.getId());
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
fileEntity.getProtectedFilePath());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -25,6 +25,23 @@ public class DocumentLocalSearchImpl implements DocumentLocalSearch {
FileEntity fileEntity = fileEntityRepository.findByIdAndUserId(fileId, Long.valueOf(ownerId)); FileEntity fileEntity = fileEntityRepository.findByIdAndUserId(fileId, Long.valueOf(ownerId));
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, null); FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, null);
log.info("fileInfo: {}", fileInfo); log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
log.info("fileInfo: {}", fileInfo);
return fileInfo; return fileInfo;
} }
@@ -258,15 +258,9 @@ public class FileUploadServiceImpl implements FileUploadService {
} }
cleanupSessionFiles(session); cleanupSessionFiles(session);
if (status != FileStatus.TEMP && status != FileStatus.PRIVATE) {
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, session.getConvertTo());
noCopyFileService.addFile(fileInfo);
}
log.info("File processing completed for session: {}", session.getUploadId()); log.info("File processing completed for session: {}", session.getUploadId());
return saved;
return saved;
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to complete file processing for session {}: {}", log.error("Failed to complete file processing for session {}: {}",
session.getUploadId(), e.getMessage(), e); session.getUploadId(), e.getMessage(), e);
@@ -396,8 +390,8 @@ public class FileUploadServiceImpl implements FileUploadService {
fileSimilarityService.hasDuplicatesByHash(finalFilePath ,session.getFileType(), fileSimilarityService.hasDuplicatesByHash(finalFilePath ,session.getFileType(),
session.getUserId()); session.getUserId());
// File file = new File(finalFilePath); File file = new File(finalFilePath);
File file = cloudStorageService.readFileFromStorageByPath(finalFilePath); // File file = cloudStorageService.readFileFromStorageByPath(finalFilePath);
if (session.getFileType().startsWith("audio") && !isWavFile(file)) { if (session.getFileType().startsWith("audio") && !isWavFile(file)) {
session.setStatus(UploadStatus.FAILED); session.setStatus(UploadStatus.FAILED);
@@ -437,8 +431,6 @@ public class FileUploadServiceImpl implements FileUploadService {
session.setStatus(UploadStatus.COMPLETED); session.setStatus(UploadStatus.COMPLETED);
session.setFilePath(finalFilePath); session.setFilePath(finalFilePath);
notificationService.addNotification(NotificationType.FILE_ADDED_TO_SYSTEM, session.getUserId());
FileStatus status = findSimilar == 0 ? FileStatus.MODERATION: FileStatus.TEMP; FileStatus status = findSimilar == 0 ? FileStatus.MODERATION: FileStatus.TEMP;
if (status == FileStatus.TEMP) { if (status == FileStatus.TEMP) {
@@ -451,15 +443,17 @@ public class FileUploadServiceImpl implements FileUploadService {
fileEntity.getFilePath()); fileEntity.getFilePath());
if (status != FileStatus.TEMP) { if (status != FileStatus.TEMP) {
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, session.getConvertTo()); String fileType = session.getFileType();
noCopyFileService.addFile(fileInfo); tariffInfoService.writeOffTokens(session.getUserId(), costService.protectFileCost(fileType),
OperationType.FILE_UPLOAD);
notificationService.addNotification(NotificationType.FILE_ADDED_TO_SYSTEM, session.getUserId());
} }
if (status != FileStatus.TEMP) { if (status != FileStatus.TEMP) {
String fileType = session.getFileType(); FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, session.getConvertTo());
tariffInfoService.writeOffTokens(session.getUserId(), costService.protectFileCost(fileType), OperationType.FILE_UPLOAD); noCopyFileService.addFile(fileInfo);
} }
} else { } else {
sessionRepository.save(session); sessionRepository.save(session);
@@ -10,10 +10,7 @@ import org.springframework.stereotype.Component;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.FileUploadSessionRepository; import ru.soune.nocopy.repository.FileUploadSessionRepository;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileUploadService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService; import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import java.io.File; import java.io.File;
@@ -118,6 +115,15 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
try { try {
FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, fileExt); FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, fileExt);
Path filePath = Paths.get(fileEntity.getProtectedFilePath());
if (!Files.exists(filePath)) {
log.error("File not found after write: {}", filePath);
return OperationResult.Companion.failure("File not found");
}
log.info("File ready for upload: {} (size: {} bytes)", filePath, Files.size(filePath));
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(), cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
fileEntity.getProtectedFilePath()); fileEntity.getProtectedFilePath());