package com.faforever.client.patch; import com.faforever.client.i18n.I18n; import com.faforever.client.notification.NotificationService; import com.faforever.client.notification.PersistentNotification; import com.faforever.client.notification.Severity; import com.faforever.client.patch.domain.IncrementModDownloadCountRequest; import com.faforever.client.patch.domain.ModPatchRequest; import com.faforever.client.patch.domain.ModVersionRequest; import com.faforever.client.patch.domain.PatchRequest; import com.faforever.client.patch.domain.PathRequest; import com.faforever.client.patch.domain.RequestRequest; import com.faforever.client.patch.domain.SimPathRequest; import com.faforever.client.patch.domain.UpdateFileRequest; import com.faforever.client.patch.domain.UpdateServerRequest; import com.faforever.client.patch.domain.VersionRequest; import com.faforever.client.remote.AbstractServerAccessor; import com.faforever.client.remote.ServerWriter; import com.faforever.client.util.ConcurrentUtil; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import javafx.concurrent.Task; import org.apache.commons.compress.utils.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import javax.annotation.PreDestroy; import javax.annotation.Resource; import java.io.IOException; import java.io.OutputStream; import java.lang.invoke.MethodHandles; import java.lang.reflect.Type; import java.net.Socket; import java.net.SocketException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; public class UpdateServerAccessorImpl extends AbstractServerAccessor implements UpdateServerAccessor { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private final Gson gson; @Resource Environment environment; @Resource I18n i18n; @Resource NotificationService notificationService; private Socket socket; private ServerWriter serverWriter; private CompletableFuture<List<String>> filesToUpdateFuture; private CompletableFuture<String> requestSimPathFuture; private UpdateServerResponseListener updateServerResponseListener; private boolean disconnectedGracefully; public UpdateServerAccessorImpl() { gson = new GsonBuilder().create(); } @Override protected void onServerMessage(String message) throws IOException { UpdateServerMessageType type = UpdateServerMessageType.valueOf(message); switch (type) { case PATH_TO_SIM_MOD: requestSimPathFuture.complete(readNextString()); break; case SIM_MOD_NOT_FOUND: requestSimPathFuture.completeExceptionally(new Exception(readNextString())); break; case LIST_FILES_TO_UP: Type listType = new TypeToken<List<String>>() { }.getType(); ArrayList<String> filesToUpdate = gson.fromJson(readNextString(), listType); filesToUpdateFuture.complete(filesToUpdate); break; case UP_TO_DATE: updateServerResponseListener.onFileUpToDate(readNextString()); break; case SEND_FILE_PATH: String path = readNextString(); String fileToCopy = readNextString(); String url = readNextString(); updateServerResponseListener.onFileUrl(path, fileToCopy, url); break; case SEND_PATCH_URL: String destination = readNextString(); String fileToUpdate = readNextString(); url = readNextString(); updateServerResponseListener.onPatchUrl(destination, fileToUpdate, url); break; case VERSION_PATCH_NOT_FOUND: String response = readNextString(); logger.warn("Patch version not found for '{}'", response); updateServerResponseListener.onVersionPatchNotFound(response); break; case VERSION_MOD_PATCH_NOT_FOUND: response = readNextString(); logger.warn("Mod Patch version not found for '{}'", response); updateServerResponseListener.onVersionModPatchNotFound(response); break; case PATCH_NOT_FOUND: response = readNextString(); logger.warn("Patch not found for '{}'", response); updateServerResponseListener.onPatchNotFound(response); break; case UNKNOWN_APP: case ERROR_FILE: logger.warn("{}: {}", type, readNextString()); break; } } @Override public void connect(UpdateServerResponseListener updateServerResponseListener) { this.updateServerResponseListener = updateServerResponseListener; disconnectedGracefully = false; String host = environment.getProperty("update.host"); int port = environment.getProperty("update.port", int.class); try { logger.debug("Connecting to update server {}:{}", host, port); this.socket = new Socket(host, port); logger.debug("Connection to update server established"); serverWriter = createServerWriter(socket.getOutputStream()); readInBackground(); } catch (IOException e) { notificationService.addNotification(new PersistentNotification(i18n.get("update.error.connectionFailed"), Severity.WARN)); } } @Override @PreDestroy public void disconnect() { disconnectedGracefully = true; updateServerResponseListener = null; IOUtils.closeQuietly(socket); logger.info("Disconnected from update server"); } @Override public CompletionStage<List<String>> requestFilesToUpdate(String fileGroup) { filesToUpdateFuture = new CompletableFuture<>(); writeToServer(new GetFilesToUpdateMessage(fileGroup)); return filesToUpdateFuture; } private void writeToServer(UpdateServerRequest updateServerRequest) { serverWriter.write(updateServerRequest); } @Override public void requestVersion(String targetDirectoryName, String filename, String gameVersion) { writeToServer(new VersionRequest(targetDirectoryName, filename, gameVersion)); } @Override public void requestModVersion(String targetDirectoryName, String filename, Map<String, Integer> modVersions) { String modVersionsJson = gson.toJson(modVersions); writeToServer(new ModVersionRequest(targetDirectoryName, filename, modVersionsJson)); } @Override public void requestPath(String targetDirectoryName, String filename) { writeToServer(new PathRequest(targetDirectoryName, filename)); } @Override public void patchTo(String targetDirectoryName, String filename, String currentMd5, String gameVersion) { writeToServer(new PatchRequest(targetDirectoryName, filename, currentMd5, gameVersion)); } @Override public void modPatchTo(String targetDirectoryName, String filename, String currentMd5, Map<String, Integer> modVersions) { String modVersionsJson = gson.toJson(modVersions); writeToServer(new ModPatchRequest(targetDirectoryName, filename, currentMd5, modVersionsJson)); } @Override public void update(String targetDirectoryName, String filename, String actualMd5) { writeToServer(new UpdateFileRequest(targetDirectoryName, filename, actualMd5)); } @Override public CompletionStage<String> requestSimPath(String uid) { requestSimPathFuture = new CompletableFuture<>(); writeToServer(new SimPathRequest(uid)); return requestSimPathFuture; } @Override public void incrementModDownloadCount(String uid) { writeToServer(new IncrementModDownloadCountRequest(uid)); } @Override public void request(String targetDirectoryName, String response) { writeToServer(new RequestRequest(targetDirectoryName, response)); } private ServerWriter createServerWriter(OutputStream outputStream) { ServerWriter serverWriter = new ServerWriter(outputStream); serverWriter.registerMessageSerializer(UpdateServerRequestSerializer.INSTANCE, UpdateServerRequest.class); return serverWriter; } private void readInBackground() { ConcurrentUtil.executeInBackground(new Task<Void>() { @Override protected Void call() throws Exception { try { blockingReadServer(socket); } catch (SocketException e) { if (!disconnectedGracefully) { throw e; } } return null; } }); } }