/*
Copyright 2012-2013, Polyvi Inc. (http://polyvi.github.io/openxface)
This program is distributed under the terms of the GNU General Public License.
This file is part of xFace.
xFace is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
xFace is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with xFace. If not, see <http://www.gnu.org/licenses/>.
*/
package com.polyvi.xface.core;
import java.io.File;
import java.util.Iterator;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import com.polyvi.xface.XSecurityPolicy;
import com.polyvi.xface.app.XApplication;
import com.polyvi.xface.util.XConstant;
import com.polyvi.xface.util.XFileUtils;
import com.polyvi.xface.util.XFileVisitor;
import com.polyvi.xface.util.XLog;
import com.polyvi.xface.util.XStringUtils;
/**
* 在线模式,应对HTML5的offline功能(application cache) 应用的src文件夹(所有源码文件)放在在服务器上
* app.xml和图片放在在本地
*
*/
public class XOnlineMode extends XAppRunningMode implements XAppCheckListener {
private static final String CLASS_NAME = XOnlineMode.class.getName();
/** HTML5离线应用缓存的数据库名称 */
public static final String OFFLINE_DATABASE_NAME = "ApplicationCache.db";
/** HTML5离线应用缓存的数据库绝对路径 */
private static String mDataBasePath;
/** app_database名称 */
private static final String APP_DATABASE_PATH = "app_database";
/** localStorage名称 */
private static final String LOCALSTORAGE = "localstorage";
@Override
public String getAppUrl(XApplication app) {
if (null == app) {
XLog.e(CLASS_NAME, "Application object is null!");
return null;
}
// 配置的是app的启动路径 http://...
String entry = app.getAppInfo().getEntry();
if (entry.contains(XConstant.HTTP_SCHEME)
|| entry.contains(XConstant.HTTPS_SCHEME)) {
return addPlatformTag(entry);
}
app.getSystemContext().toast("app.xml: tag entry config error");
XLog.e(CLASS_NAME, "app.xml: tag entry config error");
return null;
}
@Override
public void loadApp(XApplication app, XSecurityPolicy policy) {
String appUrl = getAppUrl(app);
if (null == appUrl) {
XLog.d(CLASS_NAME, "appUrl is null");
return;
}
if (hasAppCache(appUrl, app)) {
policy.checkAppStart(app, this);
} else {
app.loadAppIntoView(getAppUrl(app), true);
}
}
@Override
public RUNNING_MODE getRunningMode() {
return RUNNING_MODE.ONLINE;
}
@Override
public void clearAppData(XApplication app, Context context) {
clearOfflineAppCache(getAppUrl(app));
clearAppLocalStorage(getAppUrl(app), app.getAppId(), context
.getFilesDir().getParent());
}
@Override
public Iterator<byte[]> createResourceIterator(XApplication app,
XIResourceFilter filter) {
return new XOnlineResourceIterator(getAppUrl(app), mDataBasePath, filter);
}
/**
* 判断当前应用是否存在缓存
*
* @param appUrl
* [in] 当前应用url
* @param app
* @return
*/
private boolean hasAppCache(String appUrl, XApplication app) {
mDataBasePath = app.getSystemContext().getCordovaInterface()
.getActivity().getApplicationContext()
.getDir("database", Context.MODE_PRIVATE).getPath()
+ File.separator + OFFLINE_DATABASE_NAME;
File file = new File(mDataBasePath);
boolean hasCache = false;
if (!file.exists()) {
XLog.w(CLASS_NAME,
"hasAppCache: ApplicationCache.db is not exists!");
return hasCache;
}
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
mDataBasePath, null);
try {
String sqlCommand = "select * from CacheResources where id in ( select resource from "
+ "CacheEntries where cache in( select cache from CacheEntries where "
+ "resource in ( select id from CacheResources where url = \'"
+ appUrl + "\')))";
Cursor cursor = database.rawQuery(sqlCommand, null);
if (cursor.getCount() > 0) {
hasCache = true;
}
cursor.close();
} catch (SQLiteException e) {
XLog.d(CLASS_NAME, "SQLite operate exception!");
e.printStackTrace();
return hasCache;
} finally {
database.close();
database = null;
}
return hasCache;
}
/**
* 清理当前应用的缓存
*
* @param appUrl
* [in] 当前应用Url
*/
public void clearOfflineAppCache(String appUrl) {
if (null == mDataBasePath) {
XLog.w(CLASS_NAME,
"clearAppCache: ApplicationCache.db is not exists!");
return;
}
File file = new File(mDataBasePath);
if (!file.exists()) {
XLog.w(CLASS_NAME,
"clearAppCache: ApplicationCache.db is not exists!");
return;
}
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
mDataBasePath, null);
try {
String sqlCommand = "select cache from CacheEntries where "
+ "resource in ( select id from CacheResources where url = \'"
+ appUrl + "\')";
Cursor cursor = database.rawQuery(sqlCommand, null);
if (!cursor.moveToFirst()) {
XLog.e(CLASS_NAME, "clearAppCache: moveToFirst error!");
cursor.close();
return;
}
int cacheIndex = cursor.getColumnIndex("cache");
if (-1 == cacheIndex) {
XLog.e(CLASS_NAME, "Clear AppCache failed!");
cursor.close();
return;
}
String cache = cursor.getString(cacheIndex);
cursor.close();
// 删除Caches表中当前应用的对应项
database.delete("Caches", "cacheGroup = \'" + cache + "\'", null);
// 删除CacheAllowsAllNetworkRequests表中当前应用的对应项
database.delete("CacheAllowsAllNetworkRequests", "cache = \'"
+ cache + "\'", null);
// 删除CacheResources表中当前应用的对应项
database.delete("CacheResources", "id in ( select resource from "
+ "CacheEntries where cache = \'" + cache + "\')", null);
// 删除CacheResourceData表中当前应用的对应项
database.delete("CacheResourceData",
"id in ( select resource from "
+ "CacheEntries where cache = \'" + cache + "\')",
null);
// 删除CacheEntries表中当前应用的对应项
database.delete("CacheEntries", "cache = \'" + cache + "\'", null);
// 删除CacheGroups表中当前应用的对应项
database.delete("CacheGroups", "newestCache = \'" + cache + "\'",
null);
// 删除CacheWhiltelistURLs表中当前应用的对应项
database.delete("CacheWhitelistURLs", "cache = \'" + cache + "\'",
null);
} catch (SQLiteException e) {
XLog.d(CLASS_NAME, "SQLite operate exception!");
e.printStackTrace();
} finally {
database.close();
database = null;
}
}
/**
* 清理应用对应的localStorage的键值对
*
* @param appUrl
* [in] 当前应用Url
* @param appId
* [in] 当前应用的appId
* @param appPackageDir
* [in] 当前应用的appPackageDir 例如:/data/data/com.paas.xface <br/>
*/
private void clearAppLocalStorage(String appUrl, String appId,
String appPackageDir) {
if (XStringUtils.isEmptyString(appUrl)
|| XStringUtils.isEmptyString(appId)
|| XStringUtils.isEmptyString(appPackageDir)) {
return;
}
// 得到数据库的名字
String localStorageName = getLocalStorageName(appUrl);
if (XStringUtils.isEmptyString(localStorageName)) {
return;
}
// 清理数据
LocalStorageClear localStorageClear = new LocalStorageClear(
localStorageName, appId);
// 得到app_database路径
String databasePath = appPackageDir + File.separator
+ APP_DATABASE_PATH + File.separator;
XFileUtils.walkDirectory(databasePath, localStorageClear);
}
/**
* 清理应用的localstorage信息 内部使用
*/
class LocalStorageClear implements XFileVisitor {
private boolean mContinueTraverse = true;
private String mFileName;
private String mAppId;
LocalStorageClear(String fileName, String appId) {
mFileName = fileName;
mAppId = appId;
}
@Override
public void visit(String filePath) {
File file = new File(filePath);
if (file.getName().equals(mFileName)) {
mContinueTraverse = false;
if (!file.exists()) {
XLog.d(CLASS_NAME,
"clearAppLocalStorage: " + file.getAbsolutePath()
+ " is not exists!");
return;
}
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
file, null);
try {
database.delete("ItemTable", "key in ( select key from "
+ "ItemTable where key like \'" + mAppId + "%\')",
null);
} catch (SQLiteException e) {
XLog.e(CLASS_NAME, "SQLite operate exception!");
e.printStackTrace();
} finally {
if (null != database) {
database.close();
database = null;
}
}
}
}
@Override
public boolean isContinueTraverse() {
return mContinueTraverse;
}
}
@Override
public void onCheckSuccess(XApplication app, XISystemContext ctx) {
app.loadAppIntoView(getAppUrl(app));
}
@Override
public void onCheckError(XApplication app, XISystemContext ctx) {
String appUrl = getAppUrl(app);
clearOfflineAppCache(appUrl);
app.loadAppIntoView(appUrl);
}
@Override
public void onCheckStart(XApplication app, XISystemContext ctx) {
}
/**
* 获取appUrl对应的localStorage数据库名字
*
* @param appUrl
* 在线应用的URL地址
* 例如:http://www.polyvi.net:8012/offlineApp/yecx/index.html
* @return 在线应用所在localstorage数据库 例如:"http_com.polyvi.net_8012.localstorage"
*/
private String getLocalStorageName(String appUrl) {
appUrl = appUrl.replaceAll("://", "_");
appUrl = appUrl.replaceAll(":", "_");
return appUrl.substring(0, appUrl.indexOf("/")) + "." + LOCALSTORAGE;
}
/**
* 给Url加上平台标示符 example:
* http://polyvi.com会变成http://polyvi.com?platform=android
* http://polyvi.com?data
* =test会变成http://polyvi.com?data=test&platform=android
*/
private String addPlatformTag(String url) {
if (null == url) {
return url;
}
String append = url.indexOf("?") > 0 ? "&platform=android"
: "?platform=android";
return url + append;
}
}