/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2008 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.client.source;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import com.funambol.client.configuration.Configuration;
import com.funambol.client.customization.Customization;
import com.funambol.sapisync.source.FileSyncSource;
import com.funambol.sync.SourceConfig;
import com.funambol.sync.SyncSource;
import com.funambol.sync.SyncListener;
import com.funambol.syncml.protocol.SyncML;
import com.funambol.util.Log;
/**
* This class represents the configuration of an AppSyncSource. In particular it
* holds all the values that are kept across application resets. These values
* are loaded/saved using a Configuration object.
*/
public class AppSyncSourceConfig {
private static final String TAG_LOG = "AppSyncSourceConfig";
protected static final String CONF_KEY_CONFIG_VERSION = "SYNC_SOURCE_CONFIG_VERSION";
protected static final String CONF_KEY_SYNC_URI = "SYNC_SOURCE_URI";
/** It should be called SYNC_MODE but it is SYNC_TYPE for historical reasons */
protected static final String CONF_KEY_SYNC_TYPE = "SYNC_TYPE";
protected static final String CONF_KEY_SOURCE_FULL = "SYNC_SOURCE_FULL";
protected static final String CONF_KEY_SOURCE_SYNCED = "SYNC_SOURCE_SYNCED";
protected static final String CONF_KEY_SOURCE_ACTIVE = "SYNC_SOURCE_ACTIVE";
protected static final String CONF_KEY_SOURCE_ENABLED = "SYNC_SOURCE_ENABLED";
protected static final String CONF_KEY_SYNC_STATUS = "SOURCE_STATUS";
protected static final String CONF_KEY_SOURCE_CONFIG = "SOURCE_CONFIG";
protected static final String CONF_KEY_UPLOAD_CONTENT_VIA_HTTP= "UPLOAD_CONTENT_VIA_HTTP";
protected static final String CONF_KEY_PENDING_SYNC_TYPE = "PENDING_SYNC_TYPE";
protected static final String CONF_KEY_PENDING_SYNC_MODE = "PENDING_SYNC_MODE";
protected static final String CONF_KEY_SYNC_TIMESTAMP = "SYNC_SOURCE_TIMESTAMP";
protected static final String CONF_KEY_MAX_ITEM_SIZE = "MAX_ITEM_SIZE";
protected static final String VERSION_1 = "1";
protected static final String VERSION_2 = "2";
protected static final String VERSION = "3";
protected String uri;
protected boolean enabled = true;
protected boolean active;
protected AppSyncSource appSource;
public static final int UNDEFINED_SYNC_MODE = -1;
/** It should be called syncMode but it is syncType for historical reasons */
protected int syncType = UNDEFINED_SYNC_MODE;
protected boolean deviceFullShown = false;
protected boolean sourceSynced;
protected int lastSyncStatus = SyncListener.SUCCESS;
protected long lastSyncTimestamp = 0;
protected boolean dirty = false;
protected boolean uploadContentViaHttp = false;
protected String pendingSyncType = "";
protected int pendingSyncMode = -1;
protected long maxItemSize = FileSyncSource.NO_LIMIT_ON_ITEM_SIZE;
protected String version;
protected boolean loaded = false;
protected Configuration configuration;
protected Customization customization;
public AppSyncSourceConfig(AppSyncSource appSource, Customization customization, Configuration configuration) {
this.appSource = appSource;
this.configuration = configuration;
this.customization = customization;
appSource.setConfig(this);
}
public boolean getEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
dirty = true;
}
public boolean getActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
dirty = true;
}
public int getLastSyncStatus() {
return lastSyncStatus;
}
public void setLastSyncStatus(int lastSyncStatus) {
this.lastSyncStatus = lastSyncStatus;
dirty = true;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
dirty = true;
}
/**
* @deprecated Used only in Blackberry for retro-compatibility. In Android
* this property haven't be used. As techdebt, will be removed soon also in
* blackberry
* @return
*/
public boolean getDeviceFullShown() {
return deviceFullShown;
}
/**
* @deprecated Used only in Blackberry for retro-compatibility. In Android
* this property haven't be used. As techdebt, will be removed soon also in
* blackberry
*/
public void setDeviceFullShown(boolean value) {
this.deviceFullShown = value;
dirty = true;
}
public void setSynced(boolean sourceSynced) {
this.sourceSynced = sourceSynced;
dirty = true;
}
public boolean getSynced() {
return sourceSynced;
}
/**
* @deprecated use {@link #setSyncMode} instead
*/
public int getSyncType() {
return getSyncMode();
}
/**
* @return the sync mode (direction)
*/
public int getSyncMode() {
return syncType;
}
/**
* @deprecated use {@link #setSyncMode} instead
*/
public void setSyncType(int syncType) {
setSyncMode(syncType);
}
/**
* @param syncType the sync mode (direction)
*/
public void setSyncMode(int syncMode) {
this.syncType = syncMode;
dirty = true;
}
public boolean getUploadContentViaHttp() {
return uploadContentViaHttp;
}
public void setUploadContentViaHttp(boolean value) {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Setting upload content via http to " + value);
}
uploadContentViaHttp = value;
dirty = true;
}
public long getLastSyncTimestamp() {
return lastSyncTimestamp;
}
public void setLastSyncTimestamp(long ts) {
this.lastSyncTimestamp = ts;
dirty = true;
}
/**
* Returns max allowed size for sync source items.
* {@link BasicMediaSyncSource#NO_LIMIT_ON_ITEM_SIZE} means no limit on size
*/
public long getMaxItemSize() {
return maxItemSize;
}
/**
* Sets max allowed size for sync source items
* {@link BasicMediaSyncSource#NO_LIMIT_ON_ITEM_SIZE} means no limit on size
* @param value
*/
public void setMaxItemSize(long value) {
this.maxItemSize = value;
dirty = true;
}
/**
* Returns a value indicating if there is a pending sync. A pending sync is
* a synchronization that could be performed for any reason. The value that
* is stored here, is the sync type (MANUAL, SCHEDULED or PUSH).
*
* @return null if there is no pending sync, a value otherwise (can be
* MANUAL, SCHEDULED or PUSH)
*/
public String getPendingSyncType() {
if (pendingSyncType != null && pendingSyncType.length() == 0) {
return null;
} else {
return pendingSyncType;
}
}
/**
* Returns a value indicating if there is a pending sync. A pending sync is
* a synchronization that could be performed for any reason. The value that
* is stored here, is the sync mode (SyncML alert sync mode).
*
* @return -1 if there is no pending sync, a value otherwise (can be any
* value according to SyncML alert codes)
*/
public int getPendingSyncMode() {
return pendingSyncMode;
}
/**
* Sets the last (only one) pending sync. A pending sync is a
* synchronization that could not be performed for any reason. Two info are
* stored for this sync:
*
* <ul>
* <li> how it was triggered (its sync type)</li>
* <li> its SyncML sync mode </li>
* </ul>
*
* @param pendingSyncType can be null to cancel any pending sync, or one of
* the values in MANUAL, SCHEDULED, PUSH
* @param syncType the SyncML alert code, or -1 to cancel any pending sync
*/
public void setPendingSync(String pendingSyncType, int pendingSyncMode) {
if (pendingSyncType == null) {
pendingSyncType = "";
}
if(!pendingSyncType.equals(this.pendingSyncType)) {
this.pendingSyncType = pendingSyncType;
dirty = true;
}
if(this.pendingSyncMode != pendingSyncMode) {
this.pendingSyncMode = pendingSyncMode;
dirty = true;
}
}
public void saveSourceSyncConfig() {
int sourceId = appSource.getId();
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Storing SourceConfig for " + appSource.getName());
}
SyncSource source = appSource.getSyncSource();
if (source != null) {
SourceConfig config = source.getConfig();
try {
String storageKey = CONF_KEY_SOURCE_CONFIG + sourceId;
ByteArrayOutputStream buff = new ByteArrayOutputStream(512);
DataOutputStream temp = new DataOutputStream(buff);
config.serialize(temp);
configuration.saveByteArrayKey(storageKey, buff.toByteArray());
temp.close();
} catch (final Exception e) {
Log.error(TAG_LOG, "Exception while storing SourceConfig [" + config.getName() + "] ", e);
}
}
}
/**
* Saves a syncsource config to storage. Classes that extend the
* AppSyncSourceConfig shall always save their custom data before invoking
* this method, because the method before returning notifies the
* configuration that a source has changed.
*/
public synchronized void save() {
int sourceId = appSource.getId();
// Save the low level sync config
saveSourceSyncConfig();
if (!dirty) {
return;
}
// Now save all the high level source parameters
StringBuffer key = new StringBuffer();
// Save the version
key.append(CONF_KEY_CONFIG_VERSION).append("-").append(sourceId);
configuration.saveStringKey(key.toString(), version);
// Save the remote URI
key = new StringBuffer();
key.append(CONF_KEY_SYNC_URI).append("-").append(sourceId)
.append("-").append("URI");
configuration.saveStringKey(key.toString(), getUri());
// Save the last sync status
key = new StringBuffer();
key.append(CONF_KEY_SYNC_STATUS).append("-").append(sourceId);
configuration.saveIntKey(key.toString(), getLastSyncStatus());
// Save the last sync timestamp
key = new StringBuffer();
key.append(CONF_KEY_SYNC_TIMESTAMP).append("-").append(sourceId);
configuration.saveLongKey(key.toString(), lastSyncTimestamp);
// Save the active flag
key = new StringBuffer();
key.append(CONF_KEY_SOURCE_ACTIVE).append("-").append(sourceId);
configuration.saveBooleanKey(key.toString(), getActive());
// Save the sync type
key = new StringBuffer();
key.append(CONF_KEY_SYNC_TYPE).append("-").append(sourceId);
configuration.saveIntKey(key.toString(), syncType);
// Save the enabled/flag
key = new StringBuffer();
key.append(CONF_KEY_SOURCE_ENABLED).append("-").append(sourceId);
configuration.saveBooleanKey(key.toString(), enabled);
// Save if the source showed device full already
key = new StringBuffer();
key.append(CONF_KEY_SOURCE_FULL).append("-").append(sourceId);
configuration.saveBooleanKey(key.toString(), getDeviceFullShown());
// Save if the source got synced at least once
key = new StringBuffer();
key.append(CONF_KEY_SOURCE_SYNCED).append("-").append(sourceId);
configuration.saveBooleanKey(key.toString(), getSynced());
// Save if content shall be sent via http
key = new StringBuffer();
key.append(CONF_KEY_UPLOAD_CONTENT_VIA_HTTP).append("-").append(sourceId);
configuration.saveBooleanKey(key.toString(), getUploadContentViaHttp());
// Save the pending sync type
key = new StringBuffer();
key.append(CONF_KEY_PENDING_SYNC_TYPE).append("-").append(sourceId);
configuration.saveStringKey(key.toString(), getPendingSyncType());
// Save the pending sync type
key = new StringBuffer();
key.append(CONF_KEY_PENDING_SYNC_MODE).append("-").append(sourceId);
configuration.saveIntKey(key.toString(), getPendingSyncMode());
// Save the max item size
key = new StringBuffer();
key.append(CONF_KEY_MAX_ITEM_SIZE).append("-").append(sourceId);
configuration.saveLongKey(key.toString(), getMaxItemSize());
// Clear the dirty flag
dirty = false;
configuration.notifySourceConfigChanged(appSource);
}
public void load(SourceConfig config) {
// We load the config from the storage only once
if (loaded) {
return;
}
int sourceId = appSource.getId();
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Loading config for " + appSource.getName());
}
// Load the version number
StringBuffer key = new StringBuffer();
key.append(CONF_KEY_CONFIG_VERSION).append("-").append(sourceId);
version = configuration.loadStringKey(key.toString(), null);
// Load the source config
if (config != null) {
try {
String storageKey = CONF_KEY_SOURCE_CONFIG + sourceId;
byte[] byteArray = configuration.loadByteArrayKey(storageKey, null);
if (byteArray != null) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Data Found");
}
DataInputStream temp = new DataInputStream(new ByteArrayInputStream(byteArray));
config.deserialize(temp);
temp.close();
}
} catch (final Exception e) {
Log.error(TAG_LOG, "Exception while initializating (reading) of SourceConfig ["
+ config.getName() + "] " + e.toString());
}
}
// Now load all the high level source parameters
// Load the last sync status
key = new StringBuffer();
key.append(CONF_KEY_SYNC_STATUS).append("-").append(sourceId);
lastSyncStatus = configuration.loadIntKey(key.toString(), SyncListener.SUCCESS);
// Load the last sync timestamp
key = new StringBuffer();
key.append(CONF_KEY_SYNC_TIMESTAMP).append("-").append(sourceId);
lastSyncTimestamp = configuration.loadLongKey(key.toString(), 0);
// Load the sync type
key = new StringBuffer();
key.append(CONF_KEY_SYNC_TYPE).append("-").append(sourceId);
syncType = configuration.loadIntKey(key.toString(),
customization.getDefaultSourceSyncMode(sourceId));
// Load the remote URI
key = new StringBuffer();
key.append(CONF_KEY_SYNC_URI).append("-").append(sourceId)
.append("-").append("URI");
uri = configuration.loadStringKey(key.toString(),
customization.getDefaultSourceUri(sourceId));
// Update the source config
if (config != null) {
config.setRemoteUri(uri);
config.setSyncMode(syncType);
}
// Load the enable property
key = new StringBuffer();
key.append(CONF_KEY_SOURCE_ENABLED).append("-").append(sourceId);
enabled = configuration.loadBooleanKey(key.toString(), enabled);
// Load if the source is active
key = new StringBuffer();
key.append(CONF_KEY_SOURCE_ACTIVE).append("-").append(sourceId);
active = configuration.loadBooleanKey(key.toString(), customization.isSourceActive(sourceId));
// Load if the source showed device full warning already
key = new StringBuffer();
key.append(CONF_KEY_SOURCE_FULL).append("-").append(sourceId);
deviceFullShown = configuration.loadBooleanKey(key.toString(), deviceFullShown);
// Load if the source got synced at least once
key = new StringBuffer();
key.append(CONF_KEY_SOURCE_SYNCED).append("-").append(sourceId);
sourceSynced = configuration.loadBooleanKey(key.toString(), sourceSynced);
// Load if the source shall use http based upload
key = new StringBuffer();
key.append(CONF_KEY_UPLOAD_CONTENT_VIA_HTTP).append("-").append(sourceId);
uploadContentViaHttp = configuration.loadBooleanKey(key.toString(), uploadContentViaHttp);
// Load the pending sync type
key = new StringBuffer();
key.append(CONF_KEY_PENDING_SYNC_TYPE).append("-").append(sourceId);
pendingSyncType = configuration.loadStringKey(key.toString(), pendingSyncType);
// Load the pending sync mode
key = new StringBuffer();
key.append(CONF_KEY_PENDING_SYNC_MODE).append("-").append(sourceId);
pendingSyncMode = configuration.loadIntKey(key.toString(), pendingSyncMode);
// Load the pending sync mode
key = new StringBuffer();
key.append(CONF_KEY_MAX_ITEM_SIZE).append("-").append(sourceId);
maxItemSize = configuration.loadLongKey(key.toString(), maxItemSize);
// If needed, we migrate the configuration
if (!VERSION.equals(version)) {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Migrating source config for " + appSource.getName());
}
// we need to migrate the config
migrateConfig(version, VERSION, config);
}
loaded = true;
}
public void commit() {
// We commit the whole configuration
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Committing config for: " + appSource.getName());
}
if (dirty) {
save();
}
configuration.commit();
}
public boolean isDirty() {
return dirty;
}
protected void migrateConfig(String from, String to, SourceConfig config) {
// the version in the app sync source config was introduced in v9. In
// this case the from is null because not available in previous version
if (from == null) {
// Migrates from v8.7 to v9.0
// The enabled flag is now properly handled, before it was based on the
// syncType
if (syncType == SyncML.ALERT_CODE_NONE) {
enabled = false;
}
// In v9 we also introduced the lastSyncTimestamp property in the
// configuration. We shall read the value stored in the last anchor
// and copy it over
if (config != null) {
// TODO: handle the upgrade here?
//lastSyncTimestamp = config.getLastAnchor();
}
// In v9 we removed 1way syncs from the product, so it is safe to
// execute this piece of code here
migrateSupportedSyncModes(config);
} else if(VERSION_1.equals(from)) {
// ALERT_CODE_ONE_WAY_FROM_CLIENT_NO_SLOW has been deprecated
if(syncType == SyncML.ALERT_CODE_ONE_WAY_FROM_CLIENT_NO_SLOW) {
syncType = SyncSource.INCREMENTAL_UPLOAD;
} if(pendingSyncMode == SyncML.ALERT_CODE_ONE_WAY_FROM_CLIENT_NO_SLOW) {
pendingSyncMode = SyncSource.INCREMENTAL_UPLOAD;
}
} else if(VERSION_2.equals(from)) {
// Nothing to do
}
// Finally we update the version to its current value
version = VERSION;
// Now persist everything
dirty = true;
commit();
}
/**
* This method checks if the sync type currently set for this source is
* still supported. If not, then the source is disabled and its anchor
* reset. This method shall be invoked any time a client removes a sync
* type for a source.
*/
protected void migrateSupportedSyncModes(SourceConfig config) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Migrating supported sync modes for " + appSource.getName());
}
// Check if the current sync type is supported
int sourceModes[] = customization.getDefaultSourceSyncModes(appSource.getId());
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "sourceModes = " + sourceModes);
}
if (sourceModes == null) {
return;
}
boolean found = false;
for(int i=0;i<sourceModes.length;++i) {
if (sourceModes[i] == syncType) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Found sourceModes[i]=" + sourceModes[i] + ",syncType=" + syncType);
}
found = true;
}
}
if (!found) {
// The sync type currently set is no longer supported for this
// source
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Sync mode: " + syncType + " is no longer supported for: " + appSource.getName());
}
setEnabled(false);
// Resets the anchors in order to force a slow sync at the next sync
if (config != null) {
config.getSyncAnchor().reset();
}
// Revert to the default sync mode
syncType = customization.getDefaultSourceSyncMode(appSource.getId());
save();
// Persist the fact that we changed the configuration so the UI can
// show appropriate messages
configuration.setPimSourceSyncTypeChanged(true);
configuration.save();
}
}
}