/** * */ package ma.car.tishadow.bundle.update; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.concurrent.atomic.AtomicReference; import ma.car.tishadow.bundle.update.util.ManifestParseException; import ma.car.tishadow.bundle.update.util.ManifestUtil; import ma.car.tishadow.bundle.update.util.TiAppUtil; import ma.car.tishadow.bundle.update.util.TiAppUtil.PropertyKey; import ma.car.tishadow.bundle.update.util.AsyncTiProperties; import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.KrollObject; import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.annotations.Kroll; import org.appcelerator.titanium.TiApplication; import org.appcelerator.titanium.TiContext; import android.content.Context; /** * The bundle update request's context. * @author wei.ding */ @Kroll.proxy(creatableInModule = TishadowBundleUpdateModule.class) public class RequestProxy extends KrollProxy { private static final String TEMPORARY_DIRECTORY_NAME = "tmp"; private AtomicReference<ApplicationState> stateRef = new AtomicReference<ApplicationState>(ApplicationState.UNKNOWN); private AtomicReference<BundleUpdateState> updateStateRef = new AtomicReference<BundleUpdateState>(BundleUpdateState.STARTING); private KrollObject javascriptContext; private Context applicationContext; private AsyncTiProperties applicationProperties; private HashMap<String, Object> properties; private File backupDirectory; private File patchDirectory; private File applicationResourcesDirectory; private File applicationDataDirectory; private File externalApplicationBaseDirectory; private File externalApplicationTemporaryDirectory; private BundleUpdateStateListener onBundleUpdateStateChangedListener; private ForceUpdateRequiredListener forceUpdateRequiredListener; public RequestProxy(TiContext tiContext) { this(tiContext.getActivity()); } public RequestProxy(Context context) { this(new AsyncTiProperties(context, TiApplication.APPLICATION_PREFERENCES_NAME, false)); this.setApplicationContext(context); } /** * @param properties */ public RequestProxy(AsyncTiProperties properties) { super(); this.applicationProperties = properties; } /* * (non-Javadoc) * @see org.appcelerator.kroll.KrollProxy#handleCreationDict(org.appcelerator.kroll.KrollDict) */ @Override public void handleCreationDict(KrollDict dict) { super.handleCreationDict(dict); this.setRequestProperties(dict); } /** * Changes the application's bundle state. * @param state */ public void setApplicationState(ApplicationState state) { this.stateRef.set(state); } /** * @return the javascriptContext */ public KrollObject getJavascriptContext() { return javascriptContext; } /** * @param javascriptContext the javascriptContext to set */ public void setJavascriptContext(KrollObject javascriptContext) { this.javascriptContext = javascriptContext; } /** * @return the applicationContext */ public Context getApplicationContext() { return applicationContext; } /** * @param applicationContext the applicationContext to set */ public void setApplicationContext(Context applicationContext) { this.applicationContext = applicationContext; } /** * @return the properties */ public AsyncTiProperties getApplicationProperties() { return applicationProperties; } /** * @return the contextProperties */ public HashMap<String, Object> getRequestProperties() { return properties; } /** * Gets context property by key. * @param key * @return */ public Object getRequestProperty(String key) { return properties.get(key); } /** * Gets context callback by specific key. * @param key * @return */ public KrollFunction getRequestCallback(String key) { Object function = this.properties.get(key); return function == null || !(function instanceof KrollFunction) ? null : (KrollFunction) function; } /** * @param properties the contextProperties to set */ public void setRequestProperties(HashMap<String, Object> properties) { this.properties = properties; } /** * Marks current bundle update process to currentState. * @param currentState current state. */ public void markedBundleUpdateStateTo(BundleUpdateState currentState) { BundleUpdateState previousState = this.updateStateRef.getAndSet(currentState); if (this.onBundleUpdateStateChangedListener != null) { this.onBundleUpdateStateChangedListener.onBundleUpdateStateChanged(previousState, currentState, this); } } /** * Gets current bundle update process. * @return */ public BundleUpdateState getCurrentBundleUpdateProcess() { return this.updateStateRef.get(); } /** * Gets current bundle update process. * @return */ @Kroll.getProperty @Kroll.method public String getUpdateState() { return this.getCurrentBundleUpdateProcess().toString(); } /** * Gets backup directory, also called 'standby' directory, which is used to backup current application resources, and will be updated later whenever the * bundle is ready to apply. This backup directory on android looks like: "/data/data/[appID]/app_appData/[standby_dir]" * @return {@link File} point to backup directory on external storage. */ public File getBackupDirectory() { if (this.backupDirectory == null) { String backupDirectoryName = (String) this.getRequestProperty(RequestProxy.Key.BACKUP_DIRECTORY); this.backupDirectory = new File(this.getApplicationDataDirectory(), backupDirectoryName); } return this.backupDirectory; } private File getExternalApplicationBaseDirectory() { if (this.externalApplicationBaseDirectory == null) { File externalCacheDirectory = this.getApplicationContext().getExternalCacheDir(); this.externalApplicationBaseDirectory = externalCacheDirectory.getParentFile(); } return this.externalApplicationBaseDirectory; } /** * Gets current application's external temporary directory, which is used to temporary caches, or hard drive manipulation(e.g bundle update). * @return */ public File getExternalApplicationTemporaryDirectory() { if (this.externalApplicationTemporaryDirectory == null) { File baseDir = this.getExternalApplicationBaseDirectory(); if (baseDir == null) { return null; } File tempDir = new File(baseDir, TEMPORARY_DIRECTORY_NAME); tempDir.mkdirs(); this.externalApplicationTemporaryDirectory = tempDir.exists() ? tempDir : null; } return this.externalApplicationTemporaryDirectory; } /** * Gets patch directory, also called 'bundle-decompress' directory, which is used to extract new bundle resources, and will be updated to backup directory. * This directory on android looks like: "/mnt/sdcard/Android/data/[appID]/tmp/[bundle_decompress_dir]" * @return */ public File getPatchDirectory() { if (this.patchDirectory == null) { String directorySeting = (String) this.getRequestProperties().get(RequestProxy.Key.BUNDLE_DECOMPRESS_DIRECTORY); this.patchDirectory = new File(getExternalApplicationTemporaryDirectory(), directorySeting); } return this.patchDirectory; } /** * Gets application resources directory, where you can find all javascript resources to execute in current application. This directory on android looks * like: "/data/data/[appID]/appData/[app_name]/" * @return */ public File getApplicationResourcesDirectory() { if (this.applicationResourcesDirectory == null) { String directorySeting = (String) this.getRequestProperties().get(RequestProxy.Key.APP_NAME); this.applicationResourcesDirectory = new File(this.getApplicationDataDirectory(), directorySeting); } return this.applicationResourcesDirectory; } /** * Gets application resources' base directory name. * @return */ public String getApplicationResourcesDirectoryName() { return (String) this.getRequestProperties().get(RequestProxy.Key.APP_NAME); } /** * Gets current application's internal root data directory, where you can find all resources(javascript, caches, databases, and etc.). This directory on * android looks like: "/data/data/[appID]/appData/" * @return */ public File getApplicationDataDirectory() { if (this.applicationDataDirectory == null) { this.applicationDataDirectory = this.getApplicationContext().getDir(TiAppUtil.APPLICATION_DATA_DIRECTORY_KEY, Context.MODE_PRIVATE); } return this.applicationDataDirectory; } /** * @return the onBundleUpdateStateChangedListener */ public BundleUpdateStateListener getOnBundleUpdateStateChangedListener() { return onBundleUpdateStateChangedListener; } /** * @param onBundleUpdateStateChangedListener the onBundleUpdateStateChangedListener to set */ public void setOnBundleUpdateStateChangedListener(BundleUpdateStateListener onBundleUpdateStateChangedListener) { this.onBundleUpdateStateChangedListener = onBundleUpdateStateChangedListener; } /** * @return the forceUpdateRequiredListener */ public ForceUpdateRequiredListener getForceUpdateRequiredListener() { return forceUpdateRequiredListener; } /** * @param forceUpdateRequiredListener the forceUpdateRequiredListener to set */ public void setForceUpdateRequiredListener(ForceUpdateRequiredListener forceUpdateRequiredListener) { this.forceUpdateRequiredListener = forceUpdateRequiredListener; } /** * Gets latest bundle version from request parameter. * @return the value of 'latest_bundle_version' in request parameters, -1 if no such a parameter. * @throws NumberFormatException - if the value of 'latest_bundle_version' is not a number. */ public long getLatestBundleVerion() { Object latestBundleVersion = this.getRequestProperty(RequestProxy.Key.LATEST_BUNDLE_VERSION); return latestBundleVersion == null ? -1 : Long.parseLong(latestBundleVersion.toString()); } /** * Clear bundle update information. */ public void clearBundleUpdateInfo() { getApplicationProperties().setBool(PropertyKey.UPDATE_READY_SIGN_KEY, false); getApplicationProperties().removeProperty(PropertyKey.UPDATE_VERSION_KEY); } /** * Set bundle update information from the manifest of standby directory. * @throws IOException * @throws ManifestParseException */ public void setBundleUpdateInfo() throws IOException, ManifestParseException { getApplicationProperties().setBool(PropertyKey.UPDATE_READY_SIGN_KEY, true); int updateVersion = (int) ManifestUtil.readBundleVersion(ManifestUtil.getBundleManifest(getBackupDirectory())); getApplicationProperties().setInt(PropertyKey.UPDATE_VERSION_KEY, updateVersion); } /** * Gets ready-for-apply bundle version. * @return the version which is ready for apply to application resources, -1 if don't have any ready-for-apply directory. */ public long getReadyForApplyVersion() { return getApplicationProperties().getInt(PropertyKey.UPDATE_VERSION_KEY, -1); } /** * Checks if the application is in the ready-for-apply state. * @return */ public boolean isReadyForApply() { return getApplicationProperties().getBool(PropertyKey.UPDATE_READY_SIGN_KEY, false); } public static class Key { /** * The key to get app_name directory, where all current effective javascript files are. */ public static final String APP_NAME = "app_name"; /** * The key to get backup directory, which is used to backup current application, in case to recovery current version because any reason causes the * bundle update fails later. */ public static final String BACKUP_DIRECTORY = "standby_dir"; /** * The key to a directory which is used to decompress latest bundle download from specific server. */ public static final String BUNDLE_DECOMPRESS_DIRECTORY = "bundle_decompress_dir"; /** * Update type: must be either 'feature_toggle' or 'dev_update'. */ public static final String UPDATE_TYPE = "update_type"; /** * The key to get latest bundle version from server */ public static final String LATEST_BUNDLE_VERSION = "latest_bundle_version"; /** * The key to a remote URL to download the latest bundle */ public static final String BUNDLE_DOWNLOAD_URL = "bundle_download_url"; /** * The key to start downloading bundle callback. */ public static final String ON_BUNDLE_DOWNLOADING_CALLBACK = "onBundleDownloading"; /** * The key to the callback KrollFunction, where javascript side can handle their own logic(change bundle update progressing bar UI e.g). */ public static final String ON_BUNDLE_DOWNLOAD_CALLBACK = "onBundleDownload"; /** * The key to the callback KrollFunction when the download bundle has been extracted. */ public static final String ON_BUNDLE_EXTRACTED_CALLBACK = "onBundleExtracted"; /** * The key to the callback KrollFunction when the bundle is ready for apply. */ public static final String ON_BUNDLE_READY_FOR_APPLY_CALLBACK = "onBundleReadyForApply"; /** * The key to the callback KrollFunction when the extracted bundle is applying to backup/standby directory. */ public static final String ON_BUNDLE_APPLYING_CALLBACK = "onBundleApplying"; /** * The key to the callback krollFunction when the bundle has been applied completely. */ public static final String ON_BUNDLE_APPLIED_CALLBACK = "onBundleApplied"; /** * The key to the callback KrollFunction whenever the bundle process has been changed. */ public static final String ON_STATE_CHANGED_CALLBACK = "onStateChanged"; // Internal Generated keys /** * Internal key for downloading bundle's reference id, which is retrieved from android DownlaodManager, and can be used later after equeue method is * returned. */ public static final String DOWNLOADING_BUNDLE_REFID = "__downloading_bundle_refid"; /** * Internal key to get filename for the bundle which will be download shortly. */ public static final String DOWNLOAD_DESTINATION_FILENAME = "__download_destination_filename"; } public interface ForceUpdateRequiredListener { public void onForceUpdateRequired(); } }