Java tutorial
/* * Copyright 2016 Mikhail Antonov * * 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 net.cyllene.hackerrank.downloader; import org.apache.commons.cli.*; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedList; import java.util.List; import java.util.Map; public class HackerrankDownloader { static final String SECRET_KEY = getSecretFromConfig(); static { DownloaderSettings.cliOptions = createCliOptions(); } public static void main(String[] args) { // Parse arguments and set up the defaults DownloaderSettings.cmd = parseArguments(args); if (DownloaderSettings.cmd.hasOption("help")) { printHelp(); System.exit(0); } if (DownloaderSettings.cmd.hasOption("verbose")) { DownloaderSettings.beVerbose = true; } /** * Output directory logic: * 1) if directory exists, ask for -f option to overwrite, quit with message * 2) if -f flag is set, check if user has access to a parent directory * 3) if no access, quit with error * 4) if everything is OK, remember the path */ String sDesiredPath = DownloaderSettings.outputDir; if (DownloaderSettings.cmd.hasOption("directory")) { sDesiredPath = DownloaderSettings.cmd.getOptionValue("d", DownloaderSettings.outputDir); } if (DownloaderSettings.beVerbose) { System.out.println("Checking output dir: " + sDesiredPath); } Path desiredPath = Paths.get(sDesiredPath); if (Files.exists(desiredPath) && Files.isDirectory(desiredPath)) { if (!DownloaderSettings.cmd.hasOption("f")) { System.out.println("I wouldn't like to overwrite existing directory: " + sDesiredPath + ", set the --force flag if you are sure. May lead to data loss, be careful."); System.exit(0); } else { System.out.println( "WARNING!" + System.lineSeparator() + "--force flag is set. Overwriting directory: " + sDesiredPath + System.lineSeparator() + "WARNING!"); } } if ((Files.exists(desiredPath) && !Files.isWritable(desiredPath)) || !Files.isWritable(desiredPath.getParent())) { System.err .println("Fatal error: " + sDesiredPath + " cannot be created or modified. Check permissions."); // TODO: use Exceptions instead of system.exit System.exit(1); } DownloaderSettings.outputDir = sDesiredPath; Integer limit = DownloaderSettings.ITEMS_TO_DOWNLOAD; if (DownloaderSettings.cmd.hasOption("limit")) { try { limit = ((Number) DownloaderSettings.cmd.getParsedOptionValue("l")).intValue(); } catch (ParseException e) { System.out.println("Incorrect limit: " + e.getMessage() + System.lineSeparator() + "Using default value: " + limit); } } Integer offset = DownloaderSettings.ITEMS_TO_SKIP; if (DownloaderSettings.cmd.hasOption("offset")) { try { offset = ((Number) DownloaderSettings.cmd.getParsedOptionValue("o")).intValue(); } catch (ParseException e) { System.out.println("Incorrect offset: " + e.getMessage() + " Using default value: " + offset); } } DownloaderCore dc = DownloaderCore.INSTANCE; List<HRChallenge> challenges = new LinkedList<>(); // Download everything first Map<String, List<Integer>> structure = null; try { structure = dc.getStructure(offset, limit); } catch (IOException e) { System.err.println("Fatal Error: could not get data structure."); e.printStackTrace(); System.exit(1); } challengesLoop: for (Map.Entry<String, List<Integer>> entry : structure.entrySet()) { String challengeSlug = entry.getKey(); HRChallenge currentChallenge = null; try { currentChallenge = dc.getChallengeDetails(challengeSlug); } catch (IOException e) { System.err.println("Error: could not get challenge info for: " + challengeSlug); if (DownloaderSettings.beVerbose) { e.printStackTrace(); } continue challengesLoop; } submissionsLoop: for (Integer submissionId : entry.getValue()) { HRSubmission submission = null; try { submission = dc.getSubmissionDetails(submissionId); } catch (IOException e) { System.err.println("Error: could not get submission info for: " + submissionId); if (DownloaderSettings.beVerbose) { e.printStackTrace(); } continue submissionsLoop; } // TODO: probably should move filtering logic elsewhere(getStructure, maybe) if (submission.getStatus().equalsIgnoreCase("Accepted")) { currentChallenge.getSubmissions().add(submission); } } challenges.add(currentChallenge); } // Now dump all data to disk try { for (HRChallenge currentChallenge : challenges) { if (currentChallenge.getSubmissions().isEmpty()) continue; final String sChallengePath = DownloaderSettings.outputDir + "/" + currentChallenge.getSlug(); final String sSolutionPath = sChallengePath + "/accepted_solutions"; final String sDescriptionPath = sChallengePath + "/problem_description"; Files.createDirectories(Paths.get(sDescriptionPath)); Files.createDirectories(Paths.get(sSolutionPath)); // FIXME: this should be done the other way String plainBody = currentChallenge.getDescriptions().get(0).getBody(); String sFname; if (!plainBody.equals("null")) { sFname = sDescriptionPath + "/english.txt"; if (DownloaderSettings.beVerbose) { System.out.println("Writing to: " + sFname); } Files.write(Paths.get(sFname), plainBody.getBytes(StandardCharsets.UTF_8.name())); } String htmlBody = currentChallenge.getDescriptions().get(0).getBodyHTML(); String temporaryHtmlTemplate = "<html></body>" + htmlBody + "</body></html>"; sFname = sDescriptionPath + "/english.html"; if (DownloaderSettings.beVerbose) { System.out.println("Writing to: " + sFname); } Files.write(Paths.get(sFname), temporaryHtmlTemplate.getBytes(StandardCharsets.UTF_8.name())); for (HRSubmission submission : currentChallenge.getSubmissions()) { sFname = String.format("%s/%d.%s", sSolutionPath, submission.getId(), submission.getLanguage()); if (DownloaderSettings.beVerbose) { System.out.println("Writing to: " + sFname); } Files.write(Paths.get(sFname), submission.getSourceCode().getBytes(StandardCharsets.UTF_8.name())); } } } catch (IOException e) { System.err.println("Fatal Error: couldn't dump data to disk."); System.exit(1); } } /** * @return {@link Options} object containing all valid options for this program */ private static Options createCliOptions() { final Options options = new Options(); options.addOption(Option.builder("h").longOpt("help").desc("display this help and exit").build()); options.addOption(Option.builder("d").longOpt("directory").hasArg(true).argName("PATH") .desc("path to output directory. Default: current working directory").build()); options.addOption(Option.builder("f").longOpt("force-overwrite") .desc("Force overwrite if output directory exists. May lead to data loss.").build()); options.addOption(Option.builder("l").longOpt("limit").hasArg(true).argName("NUMBER").type(Number.class) .desc("number of solved challenges to download. Default is " + DownloaderSettings.ITEMS_TO_DOWNLOAD) .build()); options.addOption(Option.builder("o").longOpt("offset").required(false).hasArg(true).argName("NUMBER") .type(Number.class).desc("number of items to skip. Default is " + DownloaderSettings.ITEMS_TO_SKIP) .build()); options.addOption( Option.builder("v").longOpt("verbose").required(false).desc("run in verbose mode").build()); return options; } static CommandLine parseArguments(String[] args) { final CommandLineParser parser = new DefaultParser(); try { return parser.parse(DownloaderSettings.cliOptions, args); } catch (ParseException e) { System.err.println("Fatal Error: " + e.getMessage()); System.exit(1); } return null; } /** * Gets a secret key from configuration file in user.home. * The secret key is a _hackerrank_session variable stored in cookies by server. * To simplify things, no login logic is present in this program, it means * you should login somewhere else and then provide this value in the config. * * @return String representing a _hackerrank_session id, about 430 characters long. */ private static String getSecretFromConfig() { final String confPathStr = System.getProperty("user.home") + File.separator + DownloaderSettings.KEYFILE_NAME; final Path confPath = Paths.get(confPathStr); String result = null; try { result = Files.readAllLines(confPath, StandardCharsets.US_ASCII).get(0); } catch (IOException e) { System.err.println("Fatal Error: Unable to open configuration file " + confPathStr + System.lineSeparator() + "File might be missing, empty or inaccessible by user." + System.lineSeparator() + "It must contain a single ASCII line, a value of \"" + DownloaderSettings.SECRET_COOKIE_ID + "\" cookie variable," + System.lineSeparator() + "which length is about 430 symbols."); System.exit(1); } return result; } private static void printHelp() { HelpFormatter formatter = new HelpFormatter(); String sUsage = "java -jar "; try { sUsage += new File( HackerrankDownloader.class.getProtectionDomain().getCodeSource().getLocation().toURI()) .getName(); } catch (URISyntaxException e) { sUsage += "hackerrank-downloader.jar"; } String header = ""; String footer = System.lineSeparator() + "If you are experiencing problems with JSON parser, " + "try to run program with \"-Dfile.encoding=UTF-8\" option" + System.lineSeparator() + System.lineSeparator() + "Application expects a file " + DownloaderSettings.KEYFILE_NAME + " to be created in your home directory. " + "It must contain a single ASCII line, a value of \"" + DownloaderSettings.SECRET_COOKIE_ID + "\" cookie variable, " + "which length is about 430 symbols."; formatter.printHelp(sUsage, header, DownloaderSettings.cliOptions, footer, true); } }