Java tutorial
// Copyright 2015-2016 Eivind Vegsundvg // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ninja.eivind.hotsreplayuploader.providers.hotslogs; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import ninja.eivind.hotsreplayuploader.models.ReplayFile; import ninja.eivind.hotsreplayuploader.models.Status; import ninja.eivind.hotsreplayuploader.providers.Provider; import ninja.eivind.hotsreplayuploader.utils.SimpleHttpClient; import ninja.eivind.stormparser.models.Player; import ninja.eivind.stormparser.models.PlayerType; import ninja.eivind.stormparser.models.Replay; import ninja.eivind.stormparser.models.replaycomponents.GameMode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Required; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; /** * Implements a {@link Provider} to upload replays to hotslogs.com.<br> * Uses the Amazon AWS S3 data store to save the file * and an HTTP request to hotslogs.com to submit it to the site. */ @Component public class HotsLogsProvider extends Provider { private static final Logger LOG = LoggerFactory.getLogger(HotsLogsProvider.class); private static final String CLIENT_ID = "HotSLogsUploaderFX"; private static final String BASE_URL = "https://www.hotslogs.com/UploadFile?Source=" + CLIENT_ID; private static long maintenance; private final AmazonS3 s3Client; private SimpleHttpClient httpClient; @Autowired public HotsLogsProvider(SimpleHttpClient httpClient, @Qualifier("hotslogs-s3client") AmazonS3 s3Client) { super("HotSLogs.com"); this.httpClient = httpClient; this.s3Client = s3Client; } public static boolean isMaintenance() { return maintenance + 600000L > System.currentTimeMillis(); } private static UUID getUUIDForString(String concatenatedString) throws NoSuchAlgorithmException { final byte[] hashed = MessageDigest.getInstance("MD5").digest(concatenatedString.getBytes()); final byte[] reArranged = reArrangeForUUID(hashed); return getUUID(reArranged); } private static byte[] reArrangeForUUID(byte[] hashed) { return new byte[] { hashed[3], hashed[2], hashed[1], hashed[0], hashed[5], hashed[4], hashed[7], hashed[6], hashed[8], hashed[9], hashed[10], hashed[11], hashed[12], hashed[13], hashed[14], hashed[15], }; } private static UUID getUUID(byte[] bytes) { long msb = 0; long lsb = 0; assert bytes.length == 16 : "data must be 16 bytes in length"; for (int i = 0; i < 8; i++) { msb = (msb << 8) | (bytes[i] & 0xff); } for (int i = 8; i < 16; i++) { lsb = (lsb << 8) | (bytes[i] & 0xff); } return new UUID(msb, lsb); } @Override public Status upload(final ReplayFile replayFile) { if (isMaintenance()) { return null; } final File file = replayFile.getFile(); if (!(file.exists() && file.canRead())) { return Status.EXCEPTION; } final String fileName = UUID.randomUUID() + ".StormReplay"; LOG.info("Assigning remote file name " + fileName + " to " + replayFile); final String uri = BASE_URL + "&FileName=" + fileName; return uploadFileToHotSLogs(file, fileName, uri); } @Override public Status getPreStatus(final Replay replay) { // Temporary fix for computer players found until the parser supports this if (replayHasComputerPlayers(replay)) { LOG.info("Computer players found for replay, tagging as uploaded."); return Status.UNSUPPORTED_GAME_MODE; } try { final String matchId = getMatchId(replay); LOG.info("Calculated matchId to be" + matchId); final String uri = BASE_URL + "&ReplayHash=" + matchId; final String result = getHttpClient().simpleRequest(uri).toLowerCase(); if (result.equals("duplicate")) { return Status.UPLOADED; } } catch (NoSuchAlgorithmException e) { LOG.warn("Platform does not support MD5; cannot proceed with parsing", e); } catch (IOException e) { return Status.EXCEPTION; } return Status.NEW; } private Status uploadFileToHotSLogs(File file, String fileName, String uri) { try { s3Client.putObject("heroesreplays", fileName, file); LOG.info("File " + fileName + " uploaded to remote storage."); final String result = getHttpClient().simpleRequest(uri).toLowerCase(); switch (result) { case "duplicate": case "success": LOG.info("File registered with HotSLogs.com"); return Status.UPLOADED; case "prealphawipe": case "computerplayerfound": case "trymemode": LOG.warn("File not supported by HotSLogs.com"); return Status.UNSUPPORTED_GAME_MODE; case "maintenance": LOG.error("HotSLogs.com is currently undergoing maintenance."); maintenance = System.currentTimeMillis(); return Status.NEW; default: LOG.error("Could not upload file. Unknown status \"" + result + "\" received."); return Status.EXCEPTION; } } catch (Exception e) { LOG.error("Could not upload file.", e); return Status.EXCEPTION; } } private boolean replayHasComputerPlayers(Replay replay) { return replay.getReplayDetails().getPlayers().stream().map(Player::getPlayerType) .anyMatch(playerType -> playerType == PlayerType.COMPUTER); } protected String getMatchId(Replay replay) throws NoSuchAlgorithmException { final String concatenatedString = getConcatenatedString(replay); return getUUIDForString(concatenatedString).toString(); } private String getConcatenatedString(Replay replay) { final String randomValue = String.valueOf(replay.getInitData().getRandomValue()); final List<String> battleNetIdsSorted = replay.getReplayDetails().getPlayers().stream() .map(Player::getBNetId).map(Long::parseLong).sorted().map(String::valueOf) .collect(Collectors.toList()); final StringBuilder builder = new StringBuilder(); battleNetIdsSorted.forEach(builder::append); builder.append(randomValue); return builder.toString(); } public SimpleHttpClient getHttpClient() { return httpClient; } protected void setHttpClient(SimpleHttpClient httpClient) { this.httpClient = httpClient; } }