package ru.soune.nocopy.service.file; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.soune.nocopy.dto.file.FileEntityResponse; 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.exception.FileEntityNotFoundException; import ru.soune.nocopy.repository.FileEntityRepository; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor public class FileEntityService { private final FileEntityRepository fileEntityRepository; @Transactional public FileEntity createFromUploadSession(FileUploadSession session, String checksum) { log.info("Creating FileEntity for upload session: {}", session.getUploadId()); try { Path filePath = Paths.get(session.getFilePath()); if (!Files.exists(filePath)) { throw new IOException("File not found on disk: " + filePath); } long fileSize = Files.size(filePath); String originalName = session.getFileName(); String storedName = filePath.getFileName().toString(); FileEntity fileEntity = FileEntity.builder() .userId(session.getUserId()) .originalFileName(originalName) .storedFileName(storedName) .filePath(session.getFilePath()) .fileSize(fileSize) .mimeType(session.getFileType()) .fileExtension(session.getExtension()) .checksum(checksum) .uploadSessionId(session.getUploadId()) .status(FileStatus.ACTIVE) .build(); FileEntity saved = fileEntityRepository.save(fileEntity); log.info("FileEntity created successfully: {} (size: {} bytes)", saved.getId(), fileSize); return saved; } catch (IOException e) { log.error("Failed to create FileEntity for session {}: {}", session.getUploadId(), e.getMessage(), e); throw new RuntimeException("Failed to create file entity: " + e.getMessage(), e); } } private String extractFileExtension(String fileName) { int dotIndex = fileName.lastIndexOf('.'); if (dotIndex > 0 && dotIndex < fileName.length() - 1) { return fileName.substring(dotIndex + 1).toLowerCase(); } return ""; } @Transactional(readOnly = true) public FileEntityResponse getById(String fileId, int version) { FileEntity fileEntity = fileEntityRepository.findById(fileId) .orElseThrow(() -> new FileEntityNotFoundException(fileId)); return convertToResponse(fileEntity, version); } @Transactional(readOnly = true) public FileEntityResponse getByUploadSessionId(String uploadSessionId, int version) { FileEntity fileEntity = fileEntityRepository.findByUploadSessionId(uploadSessionId) .orElseThrow(() -> new FileEntityNotFoundException( "Not found for upload session: " + uploadSessionId)); 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( userId, FileStatus.ACTIVE); int start = (page - 1) * pageSize; int end = Math.min(start + pageSize, allFiles.size()); if (start >= allFiles.size()) { return FileResponse.builder() .files(List.of()) .totalCount(allFiles.size()) .totalSize(0) .formattedTotalSize("0 B") .page(page) .pageSize(pageSize) .build(); } List pageFiles = allFiles.subList(start, end); List files = pageFiles.stream() .map(file -> convertToResponse(file, version)) .collect(Collectors.toList()); long totalSize = allFiles.stream() .mapToLong(FileEntity::getFileSize) .sum(); return FileResponse.builder() .files(files) .totalCount(allFiles.size()) .totalSize(totalSize) .formattedTotalSize(formatFileSize(totalSize)) .page(page) .pageSize(pageSize) .build(); } @Transactional public void markAsDeleted(FileEntity fileEntity) { fileEntity.setStatus(FileStatus.DELETED); fileEntity.setUpdatedAt(LocalDateTime.now()); fileEntityRepository.save(fileEntity); } @Transactional public boolean deleteFromDisk(FileEntity fileEntity) throws IOException { Path path = Paths.get(fileEntity.getFilePath()); if (!Files.exists(path)) { return true; } Files.delete(path); return true; } @Transactional(readOnly = true) public long getUserStorageUsed(Long userId) { Long totalSize = fileEntityRepository.getTotalSizeByUserId(userId); return totalSize != null ? totalSize : 0L; } private boolean checkFileExistsOnDisk(String filePath) { try { return Files.exists(Paths.get(filePath)); } catch (Exception e) { log.warn("Error checking file existence: {}", filePath, e); return false; } } private FileEntityResponse convertToResponse(FileEntity fileEntity, int version) { boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath()); return FileEntityResponse.builder() .id(fileEntity.getId()) .userId(fileEntity.getUserId()) .originalFileName(fileEntity.getOriginalFileName()) .storedFileName(fileEntity.getStoredFileName()) .filePath(fileEntity.getFilePath()) .fileSize(fileEntity.getFileSize()) .mimeType(fileEntity.getMimeType()) .fileExtension(fileEntity.getFileExtension()) .checksum(fileEntity.getChecksum()) .uploadSessionId(fileEntity.getUploadSessionId()) .status(fileEntity.getStatus()) .createdAt(fileEntity.getCreatedAt()) .updatedAt(fileEntity.getUpdatedAt()) .formattedSize(formatFileSize(fileEntity.getFileSize())) .downloadUrl("/api/v" + version + "/files/download/" + fileEntity.getId()) .existsOnDisk(existsOnDisk) .build(); } public String formatFileSize(long size) { if (size < 1024) { return size + " B"; } else if (size < 1024 * 1024) { return String.format("%.1f KB", size / 1024.0); } else if (size < 1024 * 1024 * 1024) { return String.format("%.1f MB", size / (1024.0 * 1024.0)); } else { return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0)); } } }