package de.onyxbits.raccoon.io; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Properties; import java.util.Vector; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.apache.http.impl.conn.SchemeRegistryFactory; import com.akdeniz.googleplaycrawler.Utils; /** * An archive is a container for managing downloaded apk files and credentials. * The idea here is that the user should be able to keep around different apk * versions of an app and should also be able to maintain different download * folders (e.g. one for anonymously downloading apps and one for downloading * bought apps or one downloadfolder per device/account). The later means that * credentials have to be kept within the archive. * * @author patrick * */ public class Archive { /** * Relative path to where we keep APK files. This directory contains one * subdirectory per app, which in turn holds the apks. */ private static final String APKDIR = "apk_storage"; /** * Name of the logdir (relative to the root dir). */ public static final String LOGDIR = "logs"; /** * Name of the file containing the credentials for connecting to GPlay. */ private static final String CREDCFG = "credentials.cfg"; /** * Name of the file containing the network config */ public static final String NETCFG = "network.cfg"; public static final String PASSWORD = "password"; public static final String USERID = "userid"; public static final String ANDROIDID = "androidid"; public static final String PROXYHOST = "proxyhost"; public static final String PROXYPORT = "proxyport"; public static final String USERAGENT = "useragent"; public static final String PROXYUSER = "proxyuser"; public static final String PROXYPASS = "proxypass"; private File root; private Properties credentials; /** * Cache of the auth token. This is not persisted. This may be null. */ private String authToken; /** * Query the cached auth cookie. * * @return the authToken */ public String getAuthToken() { return authToken; } /** * Cache an auth cookie. WARNING: In a multithread environment, make sure that * you don't perform logins simultaneously, so you don't race for cookies. * * @param authToken * the authToken to set */ public void setAuthToken(String authToken) { this.authToken = authToken; } /** * @return the androidId */ public String getAndroidId() { return credentials.getProperty(ANDROIDID, ""); } /** * @param androidId * the androidId to set */ public void setAndroidId(String androidId) { credentials.setProperty(ANDROIDID, androidId); } /** * @return the password */ public String getPassword() { return credentials.getProperty(PASSWORD, ""); } /** * @param password * the password to set */ public void setPassword(String password) { credentials.setProperty(PASSWORD, password); } /** * @return the userId */ public String getUserId() { return credentials.getProperty(USERID, ""); } /** * @param userId * the userId to set */ public void setUserId(String userId) { credentials.setProperty(USERID, userId); } /** * Create a new archive in the designated place * * @param root * directory in which to generate the archive */ public Archive(File root) { this.root = root; try { credentials = new Properties(); credentials.load(new FileInputStream(new File(root, CREDCFG))); } catch (Exception e) { // e.printStackTrace(); } } /** * Get a proxy client, if it is configured. * * @return either a client or null * @throws IOException * if reading the config file fails * @throws KeyManagementException * @throws NumberFormatException * if that port could not be parsed. * @throws NoSuchAlgorithmException */ public HttpClient getProxyClient() throws IOException, KeyManagementException, NoSuchAlgorithmException, NumberFormatException { File cfgfile = new File(root, NETCFG); if (cfgfile.exists()) { Properties cfg = new Properties(); cfg.load(new FileInputStream(cfgfile)); String ph = cfg.getProperty(PROXYHOST, null); String pp = cfg.getProperty(PROXYPORT, null); String pu = cfg.getProperty(PROXYUSER, null); String pw = cfg.getProperty(PROXYPASS, null); if (ph == null || pp == null) { return null; } PoolingClientConnectionManager connManager = new PoolingClientConnectionManager( SchemeRegistryFactory.createDefault()); connManager.setMaxTotal(100); connManager.setDefaultMaxPerRoute(30); DefaultHttpClient client = new DefaultHttpClient(connManager); client.getConnectionManager().getSchemeRegistry().register(Utils.getMockedScheme()); HttpHost proxy = new HttpHost(ph, Integer.parseInt(pp)); client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); if (pu != null && pw != null) { client.getCredentialsProvider().setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials(pu, pw)); } return client; } return null; } /** * Query the location of the archive * * @return the directory in which the archive is kept. */ public File getRoot() { return root; } /** * Persist credentials to the CREDS file. * * @throws IOException * if saving goes wrong. */ public void saveCredentials() throws IOException { root.mkdirs(); credentials.store(new FileOutputStream(new File(root, CREDCFG)), ""); } /** * Figure out where to save an app * * @param packName * packagename of the app * @param vc * versioncode of the app * @return the file where to save this app. */ public File fileUnder(String packName, int vc) { File appRoot = new File(new File(root, APKDIR), packName.replace('.', '-')); return new File(appRoot, packName.replace('.', '_') + "-" + vc + ".apk"); } /** * Figure our where to save an expansion file for an app * * @param type * this should either be "main" or "patch", depending on which * expansion to file * @param packName * package name of the app. * @param vc * version code of the expansion * @return where to save. */ public File fileExpansionUnder(String type, String packName, int vc) { File appRoot = new File(new File(root, APKDIR), packName.replace('.', '-')); return new File(appRoot, type + "." + vc + "." + packName + ".obb"); } /** * List all apps in the archive * * @return a list of packagenames. */ public List<String> list() { File storage = new File(root, APKDIR); storage.mkdirs(); File[] lst = storage.listFiles(); Vector<String> tmp = new Vector<String>(); for (File f : lst) { if (f.isDirectory()) { tmp.add(f.getName().replace('-', '.')); } } return tmp; } /** * Statistics: count apps in this archive * * @return the number of subdirectories in the APK_STORAGE */ public int countApps() { int ret = 0; try { File[] lst = new File(root, APKDIR).listFiles(); for (File f : lst) { if (f.isDirectory()) { ret++; } } } catch (Exception e) { // Obviously no apps. } return ret; } /** * Turn a raw filesize into a human readable one * * @param bytes * raw size * @param si * use SI units * @return formated string. */ public static String humanReadableByteCount(long bytes, boolean si) { int unit = si ? 1000 : 1024; if (bytes < unit) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(unit)); String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); } public String getUserAgent() { return credentials.getProperty(USERAGENT,null); } }