Newer
Older
screenshot-app / src / main / java / com / screenshot / service / UploadService.java
package com.screenshot.service;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

import java.io.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.*;

/**
 * Сервис для отправки файлов на удалённый сервер
 */
public class UploadService {

    private final HttpClient httpClient;
    private final Gson gson;
    private final String boundary;

    public UploadService() {
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(30))
                .build();
        this.gson = new Gson();
        this.boundary = "----ScreenshotUploadBoundary" + System.currentTimeMillis();
    }

    /**
     * Результат загрузки
     */
    public record UploadResult(
            boolean success,
            String message,
            int statusCode,
            String serverResponse
    ) {}

    /**
     * Загрузить файл на сервер с повторными попытками
     * @param filePath путь к файлу
     * @param serverUrl URL сервера
     * @param retryCount количество попыток
     * @param retryDelaySeconds задержка между попытками
     * @return результат загрузки
     */
    public UploadResult uploadWithRetry(
            Path filePath,
            String serverUrl,
            int retryCount,
            int retryDelaySeconds) {

        int attempt = 0;
        UploadResult lastResult = null;

        while (attempt < retryCount) {
            attempt++;
            System.out.println("Попытка " + attempt + "/" + retryCount +
                    ": отправка " + filePath.getFileName());

            lastResult = uploadFile(filePath, serverUrl);

            if (lastResult.success) {
                System.out.println("✓ Успешно отправлен: " + filePath.getFileName());
                return lastResult;
            }

            System.err.println("✗ Ошибка: " + lastResult.message);

            if (attempt < retryCount) {
                System.out.println("Повтор через " + retryDelaySeconds + " сек...");
                try {
                    Thread.sleep(retryDelaySeconds * 1000L);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return new UploadResult(false, "Прервано", -1, null);
                }
            }
        }

        return lastResult != null ? lastResult :
                new UploadResult(false, "Все попытки исчерпаны", -1, null);
    }

    /**
     * Загрузить файл на сервер (одна попытка)
     */
    public UploadResult uploadFile(Path filePath, String serverUrl) {
        try {
            if (!Files.exists(filePath)) {
                return new UploadResult(false, "Файл не найден: " + filePath, -1, null);
            }

            // Формируем multipart/form-data запрос
            byte[] requestBody = createMultipartBody(filePath);

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(serverUrl))
                    .timeout(Duration.ofMinutes(10))
                    .header("Content-Type", "multipart/form-data; boundary=" + boundary)
                    .POST(HttpRequest.BodyPublishers.ofByteArray(requestBody))
                    .build();

            HttpResponse<String> response = httpClient.send(
                    request,
                    HttpResponse.BodyHandlers.ofString()
            );

            int statusCode = response.statusCode();
            String responseBody = response.body();

            if (statusCode >= 200 && statusCode < 300) {
                return new UploadResult(true, "OK", statusCode, responseBody);
            } else {
                String errorMsg = parseErrorMessage(responseBody);
                return new UploadResult(false,
                        "HTTP " + statusCode + ": " + errorMsg,
                        statusCode,
                        responseBody);
            }

        } catch (IOException e) {
            return new UploadResult(false, "IO ошибка: " + e.getMessage(), -1, null);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return new UploadResult(false, "Прервано", -1, null);
        } catch (Exception e) {
            return new UploadResult(false, "Ошибка: " + e.getMessage(), -1, null);
        }
    }

    /**
     * Создать тело multipart/form-data запроса
     */
    private byte[] createMultipartBody(Path filePath) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintWriter writer = new PrintWriter(
                new OutputStreamWriter(baos, StandardCharsets.UTF_8), true);

        String fileName = filePath.getFileName().toString();
        String contentType = getContentType(fileName);

        // Начало части
        writer.append("--").append(boundary).append("\r\n");
        writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"")
                .append(fileName).append("\"").append("\r\n");
        writer.append("Content-Type: ").append(contentType).append("\r\n");
        writer.append("\r\n");
        writer.flush();

        // Содержимое файла
        Files.copy(filePath, baos);

        // Конец
        writer.append("\r\n");
        writer.append("--").append(boundary).append("--").append("\r\n");
        writer.flush();

        return baos.toByteArray();
    }

    /**
     * Определить Content-Type по расширению
     */
    private String getContentType(String fileName) {
        String lower = fileName.toLowerCase();
        if (lower.endsWith(".zip")) return "application/zip";
        if (lower.endsWith(".png")) return "image/png";
        if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
        return "application/octet-stream";
    }

    /**
     * Извлечь сообщение об ошибке из JSON ответа
     */
    private String parseErrorMessage(String responseBody) {
        try {
            JsonObject json = gson.fromJson(responseBody, JsonObject.class);
            if (json.has("message")) {
                return json.get("message").getAsString();
            }
            if (json.has("error")) {
                return json.get("error").getAsString();
            }
        } catch (Exception ignored) {}
        return responseBody;
    }

    /**
     * Проверить доступность сервера
     */
    public boolean isServerAvailable(String serverUrl) {
        try {
            // Отправляем HEAD или GET запрос для проверки
            URI baseUri = URI.create(serverUrl);
            URI healthUri = baseUri.resolve("/api/health");

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(healthUri)
                    .timeout(Duration.ofSeconds(10))
                    .GET()
                    .build();

            HttpResponse<Void> response = httpClient.send(
                    request,
                    HttpResponse.BodyHandlers.discarding()
            );

            return response.statusCode() < 500;

        } catch (Exception e) {
            return false;
        }
    }
}