package de.onyxbits.raccoon; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import com.akdeniz.googleplaycrawler.GooglePlay.BulkDetailsEntry; import com.akdeniz.googleplaycrawler.GooglePlay.BulkDetailsResponse; import com.akdeniz.googleplaycrawler.GooglePlay.DocV2; import com.akdeniz.googleplaycrawler.GooglePlayAPI; import de.onyxbits.raccoon.io.Archive; import de.onyxbits.raccoon.io.DownloadLogger; import de.onyxbits.raccoon.io.FetchListener; import de.onyxbits.raccoon.io.FetchService; /** * Runs the app in command line interface mode. * * @author patrick * */ public class CliService implements FetchListener, Runnable { private String[] cmdLine; private DownloadLogger logger; private Archive destination; public CliService(String[] cmdLine) { this.cmdLine = cmdLine; } public boolean onChunk(FetchService src, long numBytes) { System.out.print('.'); return false; } public void onFailure(FetchService src, Exception e) { System.err.println("Failure: " + e.getMessage()); // e.printStackTrace(); } public void onAborted(FetchService src) { } public void run() { Option help = new Option("h", false, "Show commandline usage"); Option version = new Option("v", false, "Show version and exit"); Option update = new Option("u", false, "Update archive (requires -a)."); Option archive = new Option("a", true, "Archive to work on"); Option importer = new Option( "i", true, "Import apps. A text file, containing one market url per line must be given (requires -a and -u to actually download)."); Option paid = new Option("p", false, "Use with -f to download paid apps (the app must already have been bought)."); archive.setArgName("directory"); Option fetch = new Option("f", true, "Fetch an app (requires -a)."); fetch.setArgName("packId,versionCode,offerType"); importer.setArgName("file"); Options opts = new Options(); opts.addOption(version); opts.addOption(archive); opts.addOption(help); opts.addOption(update); opts.addOption(importer); opts.addOption(fetch); opts.addOption(paid); CommandLine cmd = null; try { cmd = new BasicParser().parse(opts, cmdLine); } catch (ParseException e1) { System.err.println(e1.getMessage()); System.exit(1); } if (cmd.hasOption('h')) { new HelpFormatter().printHelp("Raccoon", opts); System.exit(0); } if (cmd.hasOption('v')) { System.out.println(App.VERSIONSTRING); System.exit(0); } if (cmd.hasOption('a')) { destination = new Archive(new File(cmd.getOptionValue('a'))); logger = new DownloadLogger(destination); logger.clear(); } if (cmd.hasOption("i")) { requireArchive(); try { doImport(new File(cmd.getOptionValue('i'))); } catch (IOException e) { System.err.println(e.getMessage()); } } if (cmd.hasOption("u")) { requireArchive(); doUpdate(); } if (cmd.hasOption("f")) { requireArchive(); String[] tmp = cmd.getOptionValues('f')[0].split(","); try { String appId = tmp[0]; int vc = Integer.parseInt(tmp[1]); int ot = Integer.parseInt(tmp[2]); new FetchService(destination, appId, vc, ot, cmd.hasOption('p'), this).run(); } catch (Exception e) { System.err.println("Format: packagename,versioncode,offertype"); System.exit(1); } } } private void doUpdate() { BulkDetailsResponse response = null; GooglePlayAPI service = null; try { service = App.createConnection(destination); response = service.bulkDetails(destination.list()); } catch (Exception e) { e.printStackTrace(); return; } for (BulkDetailsEntry bulkDetailsEntry : response.getEntryList()) { DocV2 doc = bulkDetailsEntry.getDoc(); String pn = doc.getBackendDocid(); int vc = -1; int ot = -1; boolean paid = false; try { vc = doc.getDetails().getAppDetails().getVersionCode(); ot = doc.getOffer(0).getOfferType(); paid = doc.getOffer(0).getCheckoutFlowRequired(); } catch (Exception e) { // Something in the apk storage did not resolve. This could be an app // that was pulled from Google Play or a directory s/he created. Design // decision: ignore silently. In the first case the user doesn't want // to bother in the second, s/he does not need to. continue; } File target = destination.fileUnder(pn, vc); if (!target.exists()) { FetchService fs = new FetchService(destination, pn, vc, ot, paid, this); fs.run(); } } } private void doImport(File file) throws IOException { BufferedReader br = new BufferedReader(new FileReader(file)); String line; String prefix = "market://details?id="; while ((line = br.readLine()) != null) { if (line.startsWith(prefix) && line.length() > prefix.length()) { // Let's keep it simple. String id = line.substring(prefix.length(), line.length()); destination.fileUnder(id, 0).getParentFile().mkdirs(); System.out.println("Adding: " + id); } } br.close(); } /** * Check if an archive is specified, bail out if not. */ private void requireArchive() { if (destination == null) { System.err.println("No archive specified!"); System.exit(1); } } @Override public void onBeginFile(FetchService src, File file) { System.err.println("Starting: " + file.getName()); } @Override public void onFinishFile(FetchService src, File file) { try { // NOTE: Not correct to do that here, but since the user can't cancel, // we can get away with it. logger.addEntry(file); System.out.println("\nSuccess"); } catch (IOException e) { e.printStackTrace(); } } @Override public void onComplete(FetchService src) { } }