/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2010 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 de.chbosync.android.syncmlclient.source.pim; import java.io.File; import java.io.IOException; import java.util.Enumeration; import android.content.ContentResolver; import android.content.Context; import android.os.Environment; import android.os.StatFs; import com.funambol.client.configuration.Configuration; import com.funambol.client.source.AppSyncSource; import com.funambol.sync.ResumableSource; import com.funambol.sync.SourceConfig; import com.funambol.sync.SyncAnchor; import com.funambol.sync.SyncException; import com.funambol.sync.SyncItem; import com.funambol.sync.SyncSource; import com.funambol.sync.client.ChangesTracker; import com.funambol.sync.client.TrackableSyncSource; import com.funambol.util.Log; import de.chbosync.android.syncmlclient.source.AbstractDataManager; public abstract class PIMSyncSource<E> extends TrackableSyncSource implements ResumableSource { private static final String TAG = "PIMSyncSource"; protected Context context; protected ContentResolver resolver; protected AbstractDataManager<E> dm = null; protected Configuration configuration; protected AppSyncSource appSource; protected long totalMemory; // This is the percentage of the total memory before the sync is aborted. // When the available memory is below this threshold, then the sync is // aborted protected long lowSpaceThreshold = 0; /** * PIMSyncSource constructor: initialize source config. * * @param config * @param tracker * @param context * @param configuration * @param appSource */ public PIMSyncSource(SourceConfig config, ChangesTracker tracker, Context context, Configuration configuration, AppSyncSource appSource, AbstractDataManager dm) { super(config, tracker); this.context = context; this.configuration = configuration; this.appSource = appSource; this.resolver = context.getContentResolver(); this.dm = dm; } /** * Sets the low space threshold. When the available memory goes below this * threshold (percentage) then the sync is aborted. If this threshold is set * to 5 then the sync is aborted as soon as the available memory goes below * 5%. Setting this value to 0 disable the feature. */ public void setLowSpaceThreshold(long threshold) { this.lowSpaceThreshold = threshold; } /** Delete a SyncItem stored on the related Items list */ @Override protected int deleteItem(String key) { if (Log.isLoggable(Log.INFO)) { Log.info(TAG, "Delete from server for item " + key); } if (syncMode == SyncSource.FULL_UPLOAD || syncMode == SyncSource.INCREMENTAL_UPLOAD) { Log.error(TAG, "Server is trying to delete items for a one way sync! " + "(syncMode: " + syncMode + ")"); return 500; } try { dm.delete(key); SyncItem tmpItem = new SyncItem(key); tmpItem.setState(SyncItem.STATE_DELETED); // Call super method super.deleteItem(key); return SyncSource.SUCCESS_STATUS; } catch (Exception e) { Log.error(TAG, "Cannot delete item", e); return SyncSource.ERROR_STATUS; } } @Override public void beginSync(int syncMode, boolean resume) throws SyncException { // Init the data manager account at each sync as it may change at any // time dm.initAccount(); super.beginSync(syncMode, resume); // For any refresh we reset the anchors so that if this sync does not // terminate properly, the next sync will be a slow one if(syncMode == SyncSource.FULL_DOWNLOAD || syncMode == SyncSource.FULL_UPLOAD) { SyncAnchor anchor = getConfig().getSyncAnchor(); anchor.reset(); } // Initialize the total memory on the device totalMemory = getTotalInternalMemorySize(); } @Override public void endSync() throws SyncException { super.endSync(); // Save the source configuration. We save the configuration here because // this piece of code is always executed on successfull sync, no matter // if they are triggered from the native app or our client. appSource.getConfig().saveSourceSyncConfig(); appSource.getConfig().commit(); } @Override public Enumeration getAllItemsKeys() throws SyncException { if (Log.isLoggable(Log.INFO)) { Log.info(TAG, "getAllItemsKeys"); } try { Enumeration keys = dm.getAllKeys(); return keys; } catch (IOException ioe) { Log.error(TAG, "Cannot get all keys", ioe); throw new SyncException(SyncException.CLIENT_ERROR, "Cannot get all keys"); } } @Override public int getAllItemsCount() throws SyncException { try { return dm.getAllCount(); } catch (IOException ioe) { Log.error(TAG, "Cannot get all count", ioe); throw new SyncException(SyncException.CLIENT_ERROR, "Cannot get all keys count"); } } @Override protected void deleteAllItems() throws SyncException { super.deleteAllItems(); try { dm.deleteAll(); } catch (IOException ioe) { throw new SyncException(SyncException.STORAGE_ERROR, ioe.getMessage()); } } /** * This method has two purposes: * * 1) check if there is enough space to add the item (if the source is * configured) * * 2) allow test case recording */ @Override protected int addItem(SyncItem item) throws SyncException { checkAvailableSpace(); int res; // Depending on the actual sync source, an item key can be set or not. // In particular sources that apply all changes in one shot do not have the key set at this point. // If this is the case we avoid invoking the super addItem which requires the key to be set. if (item.getKey() != null) { res = super.addItem(item); } else { res = SyncSource.SUCCESS_STATUS; } return res; } @Override protected int updateItem(SyncItem item) throws SyncException { checkAvailableSpace(); return super.updateItem(item); } /** * Indicates if the source is ready to resume. */ public boolean readyToResume() { return tracker.supportsResume(); } public boolean exists(String key) throws SyncException { try { return dm.exists(key); } catch (Exception e) { Log.error(TAG, "Cannot check item existence", e); throw new SyncException(SyncException.CLIENT_ERROR, e.toString()); } } public boolean hasChangedSinceLastSync(String key, long ts) { // Check if the given item has changed since the given timestamp return true; } /** * No support for single item resume. */ public long getPartiallyReceivedItemSize(String key) { return -1; } public String getLuid(SyncItem item) { return null; } protected void checkAvailableSpace() throws SyncException { if (lowSpaceThreshold > 0) { // check if we have enough space for new items long availableSpace = getAvailableInternalMemorySize(); if (availableSpace < ((lowSpaceThreshold * totalMemory) / 100)) { // We reached the low limit, abort the sync with a proper error // message lowSpaceReached(); } } } protected void lowSpaceReached() throws SyncException { throw new SyncException(SyncException.LOCAL_DEVICE_FULL, "Insufficient space available on device"); } protected long getAvailableInternalMemorySize() { File path = Environment.getDataDirectory(); StatFs stat = new StatFs(path.getPath()); long blockSize = stat.getBlockSize(); long availableBlocks = stat.getAvailableBlocks(); return availableBlocks * blockSize; } protected long getTotalInternalMemorySize() { File path = Environment.getDataDirectory(); StatFs stat = new StatFs(path.getPath()); long blockSize = stat.getBlockSize(); long totalBlocks = stat.getBlockCount(); return totalBlocks * blockSize; } }