/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2003 - 2007 Funambol, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program 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 Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*
* You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
* 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by Funambol" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by Funambol".
*/
package com.funambol.updater;
import java.util.Calendar;
import java.util.Date;
import com.funambol.util.TransportAgent;
import com.funambol.util.HttpTransportAgent;
import com.funambol.util.Log;
import com.funambol.util.StringUtil;
public class Updater {
private static final String TAG_LOG = "Updater";
public static final String DEFAULT_UPDATER_REQUEST_URI = "/sapi/profile/client?action=get-update-info";
/** Updater url parameters */
private final String UPDATER_VERSION_PARAM = "version";
private final String UPDATER_COMPONENT_PARAM = "component";
private final String UPDATER_OS_PARAM = "os";
private final String UPDATER_MODEL_PARAM = "model";
private final String UPDATER_MANUFACTURER_PARAM = "manufacturer";
private final String UPDATER_APPINFO_PARAM = "appinfo";
private final String UPDATER_CARRIER_PARAM = "carrier";
private final String UPDATER_FORMAT_PARAM = "format";
/** if empty the server sends back the upd info in JSON format */
private final String UPDATER_PROPERTIES_FORMAT = "properties";
/** upd_info file properties */
private final String VERSION_TAG = "version=";
private final String TYPE_TAG = "type=";
private final String URL_TAG = "url=";
private final String ACTIVATION_DATE_TAG = "activation-date=";
private UpdaterConfig config;
private String currentVersion;
private String component;
private String os = null;
private String model = null;
private String manufacturer = null;
private String appinfo = null;
private String carrier = null;
private TransportAgent userTA = null;
private UpdaterListener listener = null;
/** The updater url */
private String requestUri = DEFAULT_UPDATER_REQUEST_URI;
public Updater(UpdaterConfig config, String currentVersion, String component) {
this.config = config;
this.currentVersion = currentVersion;
this.component = component;
}
public Updater(UpdaterConfig config, String currentVersion, String component,
String os) {
this.config = config;
this.currentVersion = currentVersion;
this.component = component;
this.os = os;
}
public Updater(UpdaterConfig config, String currentVersion, String component,
String os, String model, String manufacturer, String appinfo, String carrier) {
this.config = config;
this.currentVersion = currentVersion;
this.component = component;
this.os = os;
this.model = model;
this.manufacturer = manufacturer;
this.appinfo = appinfo;
this.carrier = carrier;
}
public Updater(UpdaterConfig config, String currentVersion, TransportAgent userTA) {
this.config = config;
this.currentVersion = currentVersion;
this.userTA = userTA;
}
public void setListener(UpdaterListener listener) {
this.listener = listener;
}
public void setTransportAgent(TransportAgent ta) {
this.userTA = ta;
}
public boolean check() {
// TODO: check if network is available in a platform neutral way
// Check if updates are available from server
checkUpdateFromServer();
if (updateIsReportable() && isNewVersionAvailable()) {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Update available ");
}
if(listener != null) {
if(config.isOptional()) {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Optional update is available");
}
listener.optionalUpdateAvailable(config.getAvailableVersion());
} else if(config.isMandatory()) {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Mandatory update is available");
}
listener.mandatoryUpdateAvailable(config.getAvailableVersion());
} else {
if(!config.isRecommended()) {
Log.error(TAG_LOG, "Unknown update type: " +
config.getType() + ", assume it is recommended");
}
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Recommended update is available");
}
listener.recommendedUpdateAvailable(config.getAvailableVersion());
}
}
return true;
}
return false;
}
public boolean isNewVersionAvailable() {
final String VER_SEP = ".";
boolean possibleUpdate = false;
String version = currentVersion;
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Current version : " + currentVersion);
}
String fversion = config.getAvailableVersion();
if (fversion == null || " ".equals(fversion)) {
fversion = currentVersion;
}
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Available version : " + fversion);
}
int vpos = 0;
int vfpos = 0;
do {
vpos = version.indexOf(VER_SEP, 0);
vfpos = fversion.indexOf(VER_SEP, 0);
if (vpos < 0 && version.length() > 0) {
vpos = version.length();
}
if (vfpos < 0 && fversion.length() > 0) {
vfpos = fversion.length();
}
if (vpos > 0 && vfpos > 0) {
int val = Integer.parseInt(version.substring(0, vpos));
int fval = Integer.parseInt(fversion.substring(0, vfpos));
if (val < fval) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Current version is old");
}
possibleUpdate = true;
break;
} else if (val > fval) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Current version isn't old");
}
break;
}
if (vpos < version.length()) {
version = version.substring(vpos + 1);
} else {
vpos = -1;
}
if (vfpos < fversion.length()) {
fversion = fversion.substring(vfpos + 1);
} else {
vfpos = -1;
}
}
} while (vpos > 0 && vfpos > 0);
return possibleUpdate;
}
private String getValueTag(String string, String tag) {
int index = string.indexOf(tag);
if (index < 0) {
return null;
}
String tmp = string.substring(index);
int end = tmp.indexOf("\n");
if (end < 0) {
end = tmp.indexOf("\r");
if (end < 0) {
return null;
}
}
String value = tmp.substring(tag.length(), end);
return value;
}
private boolean isTimeToRefresh() {
long now = System.currentTimeMillis();
boolean refresh = false;
long lastCheck = config.getLastCheck();
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "isTimeToRefresh - Now Date is: " + new Date(now) +
" Last Check Date was " + new Date(lastCheck));
}
if (((now - lastCheck) >= config.getCheckInterval())) {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "isTimeToRefresh - Update info need to be refreshed. " +
"Last Check was " + new Date(lastCheck));
}
refresh = true;
config.save();
}
return refresh;
}
private void checkUpdateFromServer() {
if (isTimeToRefresh()) {
String url = getUpdaterUrl();
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "checkUpdateFromServer - update url: " + url);
}
TransportAgent ta;
if (userTA != null) {
ta = userTA;
} else {
ta = new HttpTransportAgent(url, false, false);
}
try {
String updateProperties = ta.sendMessage("");
String version = getValueTag(updateProperties, VERSION_TAG);
if (version != null) {
if (!version.equals(config.getAvailableVersion())) {
// There is a new version on the server, even if the
// user decided to skip the current version, we must
// inform about the new one. Superseed user decision
// in this case and change the config
config.setSkip(false);
}
config.setAvailableVersion(version);
}
String type = getValueTag(updateProperties, TYPE_TAG);
if (type != null) {
config.setType(type);
}
String downloadUrl = getValueTag(updateProperties, URL_TAG);
if (downloadUrl != null) {
config.setDownloadUrl(downloadUrl);
}
String activationDate = getValueTag(updateProperties,
ACTIVATION_DATE_TAG);
if (activationDate != null) {
config.setActivationDate(parseDate(activationDate));
}
config.setLastCheck(System.currentTimeMillis());
config.save();
// Log updater infos
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "availableVersion: " + config.getAvailableVersion());
}
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "updateType: " + config.getType());
}
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "updateURL: " + config.getUrl());
}
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "activationDate: " + config.getActivationDate());
}
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "lastUpdateCheck: " + config.getLastCheck());
}
} catch (Throwable t) {
Log.error(TAG_LOG, "checkUpdateFromServer - " + t.toString());
}
} else {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "No refresh update info from server needs");
}
}
}
/**
* Get time (a {@code long} value that holds the number of milliseconds
* since midnight GMT, January 1, 1970) from date in "yyyyMMdd" String
* format
* @param field Date in "yyyyMMdd" String format
* @return the Date timestamp
*/
private static long parseDate(String field) {
int day = 0;
int month = 0;
int year = 0;
Calendar date = Calendar.getInstance();
year = Integer.parseInt(field.substring(0, 4));
month = Integer.parseInt(field.substring(4, 6));
day = Integer.parseInt(field.substring(6, 8));
date.set(Calendar.DAY_OF_MONTH, day);
date.set(Calendar.MONTH, month - 1);
date.set(Calendar.YEAR, year);
date.set(Calendar.HOUR_OF_DAY, 0);
date.set(Calendar.MINUTE, 0);
date.set(Calendar.SECOND, 0);
date.set(Calendar.MILLISECOND, 0);
return date.getTime().getTime();
}
private String getUpdaterUrl() {
StringBuffer urlParams = new StringBuffer();
// Set the url path and the component
urlParams.append(requestUri);
// Append component param
urlParams.append("&" + UPDATER_COMPONENT_PARAM + "=" + component);
// Append version param
urlParams.append("&" + UPDATER_VERSION_PARAM + "=" + currentVersion);
// Append os param if needed
if(!StringUtil.isNullOrEmpty(os)) {
urlParams.append("&" + UPDATER_OS_PARAM + "=" + os);
}
// Append model param if needed
if(!StringUtil.isNullOrEmpty(model)) {
urlParams.append("&" + UPDATER_MODEL_PARAM + "=" + model);
}
// Append manufacturer param if needed
if(!StringUtil.isNullOrEmpty(manufacturer)) {
urlParams.append("&" + UPDATER_MANUFACTURER_PARAM + "=" + manufacturer);
}
// Append appinfo param if needed
if(!StringUtil.isNullOrEmpty(appinfo)) {
urlParams.append("&" + UPDATER_APPINFO_PARAM + "=" + appinfo);
}
// Append carrier param if needed
if(!StringUtil.isNullOrEmpty(carrier)) {
urlParams.append("&" + UPDATER_CARRIER_PARAM + "=" + carrier);
}
// Append format param if needed
if(!StringUtil.isNullOrEmpty(UPDATER_PROPERTIES_FORMAT)) {
urlParams.append("&" + UPDATER_FORMAT_PARAM + "=" + UPDATER_PROPERTIES_FORMAT);
}
return config.getUrl() + urlParams.toString();
}
private boolean updateIsReportable() {
if (config.getSkip()) {
return false;
}
long now = System.currentTimeMillis();
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Now: " + new Date(now));
}
Date next = new Date(config.getLastReminder() + config.getReminderInterval());
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Next update remind: " + next);
}
if ((config.getLastReminder() + config.getReminderInterval()) > now) {
return false;
}
return true;
}
public void setLastReminder(long time) {
config.setLastReminder(time);
config.save();
}
public void setSkip() {
config.setSkip(true);
config.save();
}
public boolean isUpdateAvailable() {
try {
String availableVersion = config.getAvailableVersion();
return (availableVersion != null && isNewVersionAvailable());
} catch (Exception e) {
Log.error(TAG_LOG, "Cannot detect if a new version is available", e);
return false;
}
}
/**
* @param uri the requestUri to set (null will reset to default)
*/
public void setRequestUri(String uri) {
if (uri != null) {
this.requestUri = uri;
} else {
this.requestUri = DEFAULT_UPDATER_REQUEST_URI;
}
}
}