package com.faforever.client.replay; import com.faforever.client.remote.AbstractServerAccessor; import com.faforever.client.remote.ClientMessageSerializer; import com.faforever.client.remote.ServerWriter; import com.faforever.client.remote.StringSerializer; import com.faforever.client.remote.domain.ClientMessage; import com.faforever.client.remote.domain.ServerCommand; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; 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.net.Socket; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; import static com.faforever.client.util.ConcurrentUtil.executeInBackground; public class ReplayServerAccessorImpl extends AbstractServerAccessor implements ReplayServerAccessor { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private final Gson gson; @Resource Environment environment; private Task<Void> connectionTask; private ServerWriter serverWriter; private CompletableFuture<List<ReplayInfoBean>> replayListCallback; public ReplayServerAccessorImpl() { gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create(); } @Override public CompletionStage<List<ReplayInfoBean>> requestOnlineReplays() { replayListCallback = new CompletableFuture<>(); writeToServer(new ListReplaysMessage()); return replayListCallback; } private void writeToServer(ClientMessage clientMessage) { connectionTask = new Task<Void>() { Socket serverSocket; @Override protected Void call() throws Exception { String replayHost = environment.getProperty("replay.host"); Integer replayPort = environment.getProperty("replay.port", int.class); logger.info("Trying to connect to replay server at {}:{}", replayHost, replayPort); try (Socket replayServerSocket = new Socket(replayHost, replayPort); OutputStream outputStream = replayServerSocket.getOutputStream()) { this.serverSocket = replayServerSocket; logger.info("Replay server connection established"); serverWriter = createServerWriter(outputStream); serverWriter.write(clientMessage); blockingReadServer(replayServerSocket); } catch (IOException e) { logger.warn("Lost connection to replay server", e); } return null; } @Override protected void cancelled() { IOUtils.closeQuietly(serverSocket); IOUtils.closeQuietly(serverSocket); logger.debug("Closed connection to statistics server"); } }; executeInBackground(connectionTask); } protected ServerWriter createServerWriter(OutputStream outputStream) throws IOException { ServerWriter serverWriter = new ServerWriter(outputStream); serverWriter.registerMessageSerializer(new ClientMessageSerializer(), ClientMessage.class); serverWriter.registerMessageSerializer(new StringSerializer(), String.class); return serverWriter; } @Override public void onServerMessage(String message) { ServerCommand serverCommand = ServerCommand.fromString(message); if (serverCommand != null) { throw new IllegalStateException("Didn't expect an unknown server message from the statistics server"); } try { // Who knows why, but these messages do not have a "command" like all other messages but an "action" ReplayServerObject replayServerObject = gson.fromJson(message, ReplayServerObject.class); if (replayListCallback != null) { replayListCallback.complete(replayInfoBeans(replayServerObject.getReplays())); } } catch (JsonSyntaxException e) { logger.warn("Could not deserialize message: " + message, e); } } private List<ReplayInfoBean> replayInfoBeans(List<ServerReplayInfo> replayInfos) { return replayInfos.stream() .map(ReplayInfoBean::new) .collect(Collectors.toList()); } @PreDestroy void disconnect() { if (connectionTask != null) { connectionTask.cancel(true); } } }