package ru.soune.nocopy.handler; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import ru.soune.nocopy.dto.*; import ru.soune.nocopy.dto.file.*; 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.exception.FileEntityNotFoundException; import ru.soune.nocopy.exception.NotFoundAuthToken; import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.service.register.AuthService; import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileStatsService; import java.time.LocalDate; import java.util.*; import java.util.stream.Collectors; @Slf4j @Component @RequiredArgsConstructor public class FileEntityHandler implements RequestHandler { private final FileEntityService fileEntityService; private final AuthService authService; private final FileStatsService fileStatsService; private final ObjectMapper objectMapper; private final FileEntityRepository fileEntityRepository; @Override public BaseResponse handle(BaseRequest request) { try { FileEntityRequest fileRequest = objectMapper.convertValue( request.getMessageBody(), FileEntityRequest.class); String action = fileRequest.getAction(); switch (action) { case "file_info": return handleGetFileInfo(request, fileRequest); case "user_files_info": return handleGetFilesUserInfo(request, fileRequest); case "file_by_session": return handleGetFileBySession(request, fileRequest); case "user_files": return handleGetUserFiles(request, fileRequest); case "search_files": return handleSearchFiles(request, fileRequest); case "storage_usage": return handleGetStorageUsage(request, fileRequest); case "check_protected": return handleProtectFile(request, fileRequest); case "delete_file": return handleDeleteFile(request, fileRequest); default: ActionResponse response = ActionResponse.builder() .action(action) .availableActions(Arrays.asList( "file_info", "file_by_session", "user_files", "search_files", "storage_usage", "delete_file", "user_files_info")) .build(); return new BaseResponse(request.getMsgId(), MessageCode.INVALID_ACTION.getCode(), "Invalid action: " + action, response); } } catch (Exception e) { log.error("Error in FileEntityHandler", e); return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), "Handler error: " + e.getMessage(), null); } } private BaseResponse handleGetFilesUserInfo(BaseRequest request, FileEntityRequest fileRequest) { Long userId = authService.useUserAuthToken(fileRequest.getToken()); FileInfoUserResponse userFileStats = fileStatsService.getUserFileStats(userId); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), userFileStats); } private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = authService.useUserAuthToken(fileRequest.getToken()); FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion()); if (!fileInfo.getUserId().equals(userId)) { return new BaseResponse(request.getMsgId(), MessageCode.ACCESS_DENIED.getCode(), "Access denied", null); } return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), fileInfo); } catch (NotFoundAuthToken e) { return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(), "Authentication required", null); } catch (Exception e) { log.error("Error getting file info", e); return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), "Failed to get file info: " + e.getMessage(), null); } } private BaseResponse handleGetFileBySession(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = authService.useUserAuthToken(fileRequest.getToken()); FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId(), request.getVersion()); if (!fileInfo.getUserId().equals(userId)) { return new BaseResponse(request.getMsgId(), MessageCode.ACCESS_DENIED.getCode(), "Access denied", null); } return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), fileInfo); } catch (NotFoundAuthToken e) { return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(), "Authentication required", null); } catch (Exception e) { log.error("Error getting file by session", e); return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), "Failed to get file: " + e.getMessage(), null); } } private BaseResponse handleGetUserFiles(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = authService.useUserAuthToken(fileRequest.getToken()); int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1; int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; FileResponse files = fileEntityService.getUserFiles(userId, page, pageSize, request.getVersion()); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), files); } catch (NotFoundAuthToken e) { return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(), "Authentication required", null); } catch (Exception e) { log.error("Error getting user files", e); return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), "Failed to get files: " + e.getMessage(), null); } } private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = authService.useUserAuthToken(fileRequest.getToken()); int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1; int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; String sortBy = fileRequest.getSortBy() != null ? fileRequest.getSortBy() : "fileName"; SortOrder sortOrder = SortOrder.fromString(fileRequest.getSortOrder()); FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000, request.getVersion()); String searchQuery = fileRequest.getQuery() != null ? fileRequest.getQuery().toLowerCase().trim() : ""; String[] searchTerms = searchQuery.split("\\s+"); String filterType = fileRequest.getType(); String dateFilter = fileRequest.getDateFilter(); List filteredFiles = allFiles.getFiles().stream() .filter(f -> matchesSearch(f, searchTerms, searchQuery, filterType)) .filter(f -> matchesDateFilter(f, dateFilter)) .collect(Collectors.toList()); Comparator comparator = getComparator(sortBy, sortOrder); filteredFiles.sort(comparator); int start = (page - 1) * pageSize; int end = Math.min(start + pageSize, filteredFiles.size()); if (start >= filteredFiles.size()) { return createEmptyResponse(request, page, pageSize, sortBy, sortOrder.getValue()); } List paginatedFiles = filteredFiles.subList(start, end); long totalSize = paginatedFiles.stream() .mapToLong(FileEntityResponse::getFileSize) .sum(); FileListResponse response = FileListResponse.builder() .files(paginatedFiles) .totalCount(filteredFiles.size()) .totalSize(totalSize) .formattedTotalSize(fileEntityService.formatFileSize(totalSize)) .page(page) .pageSize(pageSize) .sortBy(sortBy) .sortOrder(sortOrder.getValue()) .build(); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), response); } catch (NotFoundAuthToken e) { return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(), "Authentication required", null); } catch (Exception e) { log.error("Error searching files", e); return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), "Failed to search files: " + e.getMessage(), null); } } private boolean matchesDateFilter(FileEntityResponse file, String dateFilter) { if (dateFilter == null || dateFilter.isEmpty()) { return true; } LocalDate fileDate = file.getCreatedAt().toLocalDate(); LocalDate now = LocalDate.now(); switch (dateFilter.toLowerCase()) { case "today": return fileDate.equals(now); case "week": return fileDate.isAfter(now.minusWeeks(1)) && !fileDate.isAfter(now); case "month": return fileDate.isAfter(now.minusMonths(1)) && !fileDate.isAfter(now); default: return true; } } private boolean matchesSearch(FileEntityResponse file, String[] searchTerms, String fullQuery, String type) { String fileName = getSafeString(file.getFileName()); String originalFileName = getSafeString(file.getOriginalFileName()); String mimeType = getSafeString(file.getMimeType()); String fileFormat = getSafeString(file.getFileFormat()); String fullFileName = fileName + " " + originalFileName; boolean matchesType = true; if (type != null && !type.isEmpty()) { matchesType = matchesMimeType(mimeType, type); } if (!matchesType) { return false; } if (fullQuery != null && !fullQuery.isEmpty()) { if (fullFileName.toLowerCase().contains(fullQuery)) { return true; } for (String term : searchTerms) { if (term.length() < 2) continue; if (fullFileName.toLowerCase().contains(term)) { return true; } } if (fileFormat.toLowerCase().contains(fullQuery)) { return true; } if (mimeType.toLowerCase().contains(fullQuery)) { return true; } if (!fileFormat.isEmpty() && !fileName.toLowerCase().endsWith("." + fileFormat.toLowerCase())) { String fileNameWithExtension = fileName + "." + fileFormat; if (fileNameWithExtension.toLowerCase().contains(fullQuery)) { return true; } } return false; } return true; } private boolean matchesMimeType(String mimeType, String filterType) { if (filterType == null || filterType.isEmpty()) return true; String mimeTypeLower = mimeType.toLowerCase(); String filterTypeLower = filterType.toLowerCase().trim(); return mimeTypeLower.equals(filterTypeLower); } private String getSafeString(String value) { return value != null ? value : ""; } private Comparator getComparator(String sortBy, SortOrder sortOrder) { Comparator comparator; switch (sortBy) { case "fileSize": comparator = Comparator.comparingLong( FileEntityResponse::getFileSize ); break; case "fileType": comparator = Comparator.comparing( FileEntityResponse::getMimeType); break; case "createdAt": comparator = Comparator.comparing( FileEntityResponse::getCreatedAt, Comparator.nullsLast(Comparator.naturalOrder()) ); break; case "updatedAt": comparator = Comparator.comparing( FileEntityResponse::getUpdatedAt, Comparator.nullsLast(Comparator.naturalOrder()) ); break; case "fileName": comparator = Comparator.comparing( f -> getSafeString(f.getFileName()).toLowerCase(), Comparator.nullsLast(Comparator.naturalOrder()) ); break; case "protectStatus": comparator = Comparator.comparing( f -> getSafeString(f.getProtectStatus()).toLowerCase(), Comparator.nullsLast(Comparator.naturalOrder()) ); break; case "supportId": comparator = Comparator.comparing( FileEntityResponse::getSupportId, Comparator.nullsLast(Comparator.naturalOrder()) ); break; default: comparator = Comparator.comparing( f -> getSafeString(f.getFileName()).toLowerCase(), Comparator.nullsLast(Comparator.naturalOrder()) ); break; } return sortOrder == SortOrder.DESC ? comparator.reversed() : comparator; } private BaseResponse createEmptyResponse(BaseRequest request, int page, int pageSize, String sortBy, String sortOrder) { FileListResponse emptyResponse = FileListResponse.builder() .files(Collections.emptyList()) .totalCount(0) .totalSize(0L) .formattedTotalSize("0 B") .page(page) .pageSize(pageSize) .sortBy(sortBy) .sortOrder(sortOrder) .build(); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), emptyResponse); } private BaseResponse handleGetUserFilesWithSort(BaseRequest request, FileEntityRequest fileRequest, String sortBy, SortOrder sortOrder) { try { Long userId = authService.useUserAuthToken(fileRequest.getToken()); int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1; int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000, request.getVersion()); List files = new ArrayList<>(allFiles.getFiles()); Comparator comparator = getComparator(sortBy, sortOrder); files.sort(comparator); int start = (page - 1) * pageSize; int end = Math.min(start + pageSize, files.size()); if (start >= files.size()) { return createEmptyResponse(request, page, pageSize, sortBy, sortOrder.getValue()); } List paginatedFiles = files.subList(start, end); long totalSize = paginatedFiles.stream() .mapToLong(FileEntityResponse::getFileSize) .sum(); FileListResponse response = FileListResponse.builder() .files(paginatedFiles) .totalCount(files.size()) .totalSize(totalSize) .formattedTotalSize(fileEntityService.formatFileSize(totalSize)) .page(page) .pageSize(pageSize) .sortBy(sortBy) .sortOrder(sortOrder.getValue()) .build(); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), response); } catch (NotFoundAuthToken e) { return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(), "Authentication required", null); } catch (Exception e) { log.error("Error getting user files with sort", e); return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), "Failed to get files: " + e.getMessage(), null); } } private BaseResponse handleGetStorageUsage(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = authService.useUserAuthToken(fileRequest.getToken()); long usage = fileEntityService.getUserStorageUsed(userId); StorageUsageResponse response = StorageUsageResponse.builder() .userId(userId) .storageUsed(usage) .formattedStorageUsed(formatFileSize(usage)) .build(); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), response); } catch (NotFoundAuthToken e) { return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(), "Authentication required", null); } catch (Exception e) { log.error("Error getting storage usage", e); return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), "Failed to get storage usage: " + e.getMessage(), null); } } private BaseResponse handleProtectFile(BaseRequest request, FileEntityRequest fileRequest) { String fileId = fileRequest.getFileId(); Optional optionalFileEntity = fileEntityRepository.findById(fileId); if (optionalFileEntity.isEmpty()) { return new BaseResponse(request.getMsgId(), MessageCode.FILE_NOT_FOUND.getCode(), "File not found exception: " + fileRequest.getFileId(), null); } FileEntity fileEntity = optionalFileEntity.get(); ProtectionStatus protectionStatus = fileEntity.getProtectionStatus(); Integer code = protectionStatus == ProtectionStatus.PROTECTED ? MessageCode.FILE_IS_PROTECTED.getCode(): MessageCode.FILE_IS_NOT_PROTECTED.getCode(); String message = protectionStatus == ProtectionStatus.PROTECTED ? MessageCode.FILE_IS_PROTECTED.getDescription(): MessageCode.FILE_IS_NOT_PROTECTED.getDescription(); return new BaseResponse(request.getMsgId(), code, message, fileEntityService.getById(fileId, request.getVersion())); } private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = authService.useUserAuthToken(fileRequest.getToken()); String fileId = fileRequest.getFileId(); FileEntityResponse fileInfo = fileEntityService.getById(fileId, request.getVersion()); DeleteFileResponse response; if (!fileInfo.getUserId().equals(userId)) { return new BaseResponse(request.getMsgId(), MessageCode.ACCESS_DENIED.getCode(), "Access denied", null); } FileEntity fileEntity = fileEntityRepository.findById(fileId) .orElseThrow(() -> new FileEntityNotFoundException(fileId)); int fullDelete = fileRequest.getFullDelete() == null ? 0 : fileRequest.getFullDelete(); if (fileEntity.getStatus().equals(FileStatus.DELETED) || fullDelete == 1) { fileEntityService.deleteFromDisk(fileEntity); response = DeleteFileResponse.builder() .fileId(fileRequest.getFileId()) .message("File deleted from disk") .build(); } else { fileEntityService.softDeleteFileWithHash(fileEntity); response = DeleteFileResponse.builder() .fileId(fileRequest.getFileId()) .message("File marked as deleted") .build(); } return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), "File marked as deleted", response); } catch (NotFoundAuthToken e) { return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(), "Authentication required", null); } catch (Exception e) { log.error("Error deleting file", e); return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), "Failed to delete file: " + e.getMessage(), null); } } private String formatFileSize(long size) { if (size < 1024) return size + " B"; int exp = (int) (Math.log(size) / Math.log(1024)); String pre = "KMGTPE".charAt(exp-1) + ""; return String.format("%.1f %sB", size / Math.pow(1024, exp), pre); } }