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;
}
}
}