package net.classicube.launcher; import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParserException; import com.grack.nanojson.JsonStringWriter; import com.grack.nanojson.JsonWriter; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import net.classicube.launcher.gui.ErrorScreen; import net.classicube.shared.SharedUpdaterCode; import org.apache.commons.lang3.StringUtils; public class DiagnosticInfoUploader { public static final String GIST_API_URL = "https://api.github.com/gists"; public static String uploadToGist() { // gather files for uploading final String sysData = getSystemProperties(); final String dirData = gatherDirStructure(); final String clientLogData = readLogFile(PathUtil.getClientDir(), PathUtil.CLIENT_LOG_FILE_NAME); final String clientOldLogData = readLogFile(PathUtil.getClientDir(), PathUtil.CLIENT_LOG_OLD_FILE_NAME); final String selfUpdaterLogData = readLogFile(PathUtil.getClientDir(), PathUtil.SELF_UPDATER_LOG_FILE_NAME); final String optionsData = readLogFile(PathUtil.getClientDir(), PathUtil.OPTIONS_FILE_NAME); String launcherLogData = null, launcherOldLogData = null, crashLogData = null; try { launcherLogData = readLogFile(SharedUpdaterCode.getLauncherDir(), PathUtil.LOG_FILE_NAME); launcherOldLogData = readLogFile(SharedUpdaterCode.getLauncherDir(), PathUtil.LOG_OLD_FILE_NAME); } catch (final IOException ex) { // Theoretically this should never happen. LogUtil.getLogger().log(Level.SEVERE, "Could not find launcher directory!", ex); } final String crashLogName = findLastCrashLogFile(PathUtil.getClientDir()); if (crashLogName != null) { crashLogData = readLogFile(PathUtil.getClientDir(), crashLogName); } // construct a Gist API request (JSON) JsonStringWriter writer = JsonWriter.string() .object() .value("description", "ClassiCube debug information") .value("public", false) .object("files"); // append system information if (sysData != null && !sysData.isEmpty()) { writer = writer.object("_system") .value("content", sysData) .end(); } // append directory information if (dirData != null && !dirData.isEmpty()) { writer = writer.object("_dir") .value("content", dirData) .end(); } // append log files if (clientLogData != null && !clientLogData.isEmpty()) { writer = writer.object(PathUtil.CLIENT_LOG_FILE_NAME) .value("content", clientLogData) .end(); } if (clientOldLogData != null && !clientOldLogData.isEmpty()) { writer = writer.object(PathUtil.CLIENT_LOG_OLD_FILE_NAME) .value("content", clientOldLogData) .end(); } if (launcherLogData != null && !launcherLogData.isEmpty()) { writer = writer.object(PathUtil.LOG_FILE_NAME) .value("content", launcherLogData) .end(); } if (launcherOldLogData != null && !launcherOldLogData.isEmpty()) { writer = writer.object(PathUtil.LOG_OLD_FILE_NAME) .value("content", launcherOldLogData) .end(); } if (selfUpdaterLogData != null && !selfUpdaterLogData.isEmpty()) { writer = writer.object(PathUtil.SELF_UPDATER_LOG_FILE_NAME) .value("content", selfUpdaterLogData) .end(); } if (optionsData != null && !optionsData.isEmpty()) { writer = writer.object(PathUtil.OPTIONS_FILE_NAME) .value("content", optionsData) .end(); } if (crashLogData != null && !crashLogData.isEmpty()) { writer = writer.object(crashLogName) .value("content", crashLogData) .end(); } // finalize JSON final String json = writer.end().end().done(); // DEBUG: Log request string LogUtil.getLogger().log(Level.INFO, json); // post data to Gist final String gistResponse = HttpUtil.uploadString(GIST_API_URL, json, HttpUtil.JSON); // get URL of newly-created Gist try { return JsonParser.object().from(gistResponse).getString("html_url"); } catch (final JsonParserException ex) { ErrorScreen.show("Error uploading debug information", "Debug information was gathered, but could not be uploaded.", ex); LogUtil.getLogger().log(Level.SEVERE, "Error parsing Gist response", ex); return null; } } // Prints all system properties to string, one per line // Based on HashTable.toString(), but different formatting. private static String getSystemProperties() { final Properties props = System.getProperties(); final int max = props.size(); final StringBuilder sb = new StringBuilder(); final Iterator<Map.Entry<Object, Object>> it = props.entrySet().iterator(); for (int i = 0; i < max; i++) { final Map.Entry<Object, Object> e = it.next(); final Object key = e.getKey(); final Object value = e.getValue(); sb.append(key == props ? "(this)" : key.toString()); sb.append('='); sb.append(value == props ? "(this)" : value.toString()); sb.append('\n'); } return sb.toString(); } // List files in client's and launcher's directories private static String gatherDirStructure() { try { final StringBuilder sb = new StringBuilder(); final String absClientDir = PathUtil.getClientDir().getAbsolutePath(); sb.append("Client directory structure:\n"); walkDir(Paths.get(absClientDir), sb); final String absLauncherDir = SharedUpdaterCode.getLauncherDir().getAbsolutePath(); sb.append("\nLauncher directory structure:\n"); walkDir(Paths.get(absLauncherDir), sb); return sb.toString(); } catch (final IOException ex) { LogUtil.getLogger().log(Level.SEVERE, "Error gathering directory structure.", ex); return null; } } // List all files in client's directory, except screenshots and server logs private static void walkDir(final Path basePath, final StringBuilder sb) throws IOException { Files.walkFileTree(basePath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { final String relativePathName = basePath.relativize(file).toString(); if (!relativePathName.startsWith("Screenshots") && !relativePathName.startsWith("logs")) { sb.append(String.format("%1$7s %2$s\n", file.toFile().length(), relativePathName)); } return FileVisitResult.CONTINUE; } }); } // Reads contents of given file into a string, if the file exists. Returns null otherwise. private static String readLogFile(final File dir, final String fileName) { final Path path = Paths.get(dir.getAbsolutePath(), fileName); if (path.toFile().exists()) { try { final List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8); return StringUtils.join(lines, '\n'); } catch (final IOException ex) { LogUtil.getLogger().log(Level.SEVERE, "Could not read " + fileName, ex); } } return null; } private static String findLastCrashLogFile(final File dir) { // your directory File[] matchingFiles = dir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith("hs_err_pid"); } }); if (matchingFiles.length == 0) { // No JVM crash logs found return null; } else if (matchingFiles.length > 1) { // Multiple JVM crash logs found -- find the most recent one Arrays.sort(matchingFiles, new Comparator<File>() { @Override public int compare(File f1, File f2) { // Sort by date-modified in descending order return Long.compare(f2.lastModified(), f1.lastModified()); } }); } return matchingFiles[0].getName(); } private DiagnosticInfoUploader() { } }