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);
}
}