Java tutorial
/******************************************************************************* * Copyright (C) 2005-2014 Alfresco Software Limited. * * This file is part of Alfresco Mobile for Android. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.alfresco.mobile.android.application.operations.sync; import java.io.File; import java.io.Serializable; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.alfresco.mobile.android.api.constants.ContentModel; import org.alfresco.mobile.android.api.model.Document; import org.alfresco.mobile.android.api.model.Node; import org.alfresco.mobile.android.api.model.Property; import org.alfresco.mobile.android.api.utils.NodeRefUtils; import org.alfresco.mobile.android.application.ApplicationManager; import org.alfresco.mobile.android.application.accounts.Account; import org.alfresco.mobile.android.application.accounts.AccountManager; import org.alfresco.mobile.android.application.fragments.favorites.SyncScanInfo; import org.alfresco.mobile.android.application.intent.IntentIntegrator; import org.alfresco.mobile.android.application.manager.StorageManager; import org.alfresco.mobile.android.application.operations.Operation; import org.alfresco.mobile.android.application.operations.OperationManager; import org.alfresco.mobile.android.application.operations.OperationRequest; import org.alfresco.mobile.android.application.operations.OperationsGroupRecord; import org.alfresco.mobile.android.application.operations.OperationsRequestGroup; import org.alfresco.mobile.android.application.operations.batch.BatchOperationManager; import org.alfresco.mobile.android.application.operations.batch.BatchOperationSchema; import org.alfresco.mobile.android.application.operations.batch.impl.AbstractBatchOperationRequestImpl; import org.alfresco.mobile.android.application.operations.batch.sync.CleanSyncFavoriteRequest; import org.alfresco.mobile.android.application.operations.batch.sync.SyncPrepareRequest; import org.alfresco.mobile.android.application.operations.batch.utils.MapUtil; import org.alfresco.mobile.android.application.operations.sync.impl.AbstractSyncOperationRequestImpl; import org.alfresco.mobile.android.application.operations.sync.node.delete.SyncDeleteRequest; import org.alfresco.mobile.android.application.operations.sync.node.download.SyncDownloadRequest; import org.alfresco.mobile.android.application.operations.sync.node.update.SyncUpdateRequest; import org.alfresco.mobile.android.application.operations.sync.utils.NodeSyncPlaceHolder; import org.alfresco.mobile.android.application.preferences.GeneralPreferences; import org.alfresco.mobile.android.application.utils.ConnectivityUtils; import org.alfresco.mobile.android.application.utils.CursorUtils; import org.alfresco.mobile.android.application.utils.SessionUtils; import org.apache.chemistry.opencmis.commons.PropertyIds; import android.content.BroadcastReceiver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; public final class SynchroManager extends OperationManager { private static final String TAG = SynchroManager.class.getName(); private static SynchroManager mInstance; // //////////////////////////////////////////////////// // CONSTRUCTORS // //////////////////////////////////////////////////// public static SynchroManager getInstance(Context context) { synchronized (mLock) { if (mInstance == null) { mInstance = new SynchroManager(context.getApplicationContext()); context.startService(new Intent(context, SynchroService.class).putExtra("t", true)); } return (SynchroManager) mInstance; } } private SynchroManager(Context applicationContext) { super(applicationContext); mAppContext.registerReceiver(new NetworkReceiver(), new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); } // //////////////////////////////////////////////////// // PUBLIC METHODS // //////////////////////////////////////////////////// public void enqueue(OperationsRequestGroup group) { OperationsGroupRecord groupRecord = new OperationsGroupRecord(group.getRequests().size()); for (OperationRequest request : group.getRequests()) { indexOperationGroup.put(OperationsGroupRecord.getOperationId(request), groupRecord); } groupRecord.initIndex(group.getRequests()); operationsGroups.add(groupRecord); notifyDataChanged(mAppContext); } public void retry(long[] ids) { Map<String, OperationsRequestGroup> groups = new HashMap<String, OperationsRequestGroup>(ids.length); OperationsRequestGroup group = null; Cursor cursor = null; Uri uri = null; AbstractSyncOperationRequestImpl request = null; for (int i = 0; i < ids.length; i++) { uri = getUri(ids[i]); cursor = mAppContext.getContentResolver().query(uri, SynchroSchema.COLUMN_ALL, null, null, null); cursor.moveToFirst(); int requestId = cursor.getInt(SynchroSchema.COLUMN_REQUEST_TYPE_ID); switch (requestId) { case SyncDeleteRequest.TYPE_ID: request = new SyncDeleteRequest(cursor); break; case SyncDownloadRequest.TYPE_ID: request = new SyncDownloadRequest(cursor); break; case SyncUpdateRequest.TYPE_ID: request = new SyncUpdateRequest(cursor); break; default: break; } if (request != null) { ((AbstractSyncOperationRequestImpl) request).setNotificationUri(uri); mAppContext.getContentResolver().update(uri, ((AbstractSyncOperationRequestImpl) request).createContentValues(Operation.STATUS_PENDING), null, null); if (groups.containsKey(request.getAccountId() + "***" + request.getNetworkId())) { group = groups.get(request.getAccountId() + "***" + request.getNetworkId()); } else { group = new OperationsRequestGroup(mAppContext, request.getAccountId(), request.getNetworkId()); groups.put(request.getAccountId() + "***" + request.getNetworkId(), group); } group.enqueue(request); } else { Log.d(TAG, "Unable to retry for" + ids[i]); } } for (Entry<String, OperationsRequestGroup> groupSet : groups.entrySet()) { enqueue(groupSet.getValue()); } CursorUtils.closeCursor(cursor); } public void retry(long id) { retry(new long[] { id }); } // //////////////////////////////////////////////////// // Utils // //////////////////////////////////////////////////// private static Uri getNotificationUri(OperationRequest operationRequest) { return ((AbstractSyncOperationRequestImpl) operationRequest).getNotificationUri(); } // //////////////////////////////////////////////////// // EVENTS // //////////////////////////////////////////////////// public static final String EXTRA_SYNCHRO_ID = "synchroId"; public static final String EXTRA_SYNCHRO_RESULT = "synchroResult"; public static final String ACTION_START_SYNC = "startSyncService"; private static final String LAST_SYNC_ACTIVATED_AT = "LastSyncDateTime"; private static final String LAST_START_SYNC_PREPARE = "LastSyncPrepareDateTime"; public void notifyCompletion(Context c, String operationId, int status) { Intent i = new Intent(IntentIntegrator.ACTION_SYNCHRO_COMPLETED); i.putExtra(EXTRA_SYNCHRO_ID, operationId); i.putExtra(EXTRA_SYNCHRO_RESULT, status); LocalBroadcastManager.getInstance(c).sendBroadcast(i); } public void notifyDataChanged(Context c) { Intent i = new Intent(ACTION_START_SYNC); LocalBroadcastManager.getInstance(c).sendBroadcast(i); } public void forceStop(Context c, int operationId) { Intent i = new Intent(IntentIntegrator.ACTION_SYNCHRO_STOP); i.putExtra(EXTRA_SYNCHRO_ID, operationId + ""); LocalBroadcastManager.getInstance(c).sendBroadcast(i); } public void forceStop(Context c) { Intent i = new Intent(IntentIntegrator.ACTION_SYNCHROS_STOP); LocalBroadcastManager.getInstance(c).sendBroadcast(i); } public void cancelAll(Context c) { Intent i = new Intent(IntentIntegrator.ACTION_SYNCHROS_CANCEL); LocalBroadcastManager.getInstance(c).sendBroadcast(i); } public void pause(int operationId) { Intent i = new Intent(IntentIntegrator.ACTION_SYNCHRO_PAUSE); i.putExtra(EXTRA_SYNCHRO_ID, operationId + ""); LocalBroadcastManager.getInstance(mAppContext).sendBroadcast(i); } // //////////////////////////////////////////////////// // Broadcast Receiver // //////////////////////////////////////////////////// protected IntentFilter getIntentFilter() { IntentFilter intentFilter = new IntentFilter(IntentIntegrator.ACTION_SYNCHRO_COMPLETED); intentFilter.addAction(IntentIntegrator.ACTION_SYNCHRO_STOP); intentFilter.addAction(IntentIntegrator.ACTION_SYNCHROS_STOP); intentFilter.addAction(IntentIntegrator.ACTION_SYNCHROS_CANCEL); intentFilter.addAction(IntentIntegrator.ACTION_SYNCHRO_PAUSE); intentFilter.addAction(IntentIntegrator.ACTION_UPDATE_COMPLETED); intentFilter.addAction(IntentIntegrator.ACTION_DELETE_COMPLETED); intentFilter.addAction(IntentIntegrator.ACTION_FAVORITE_COMPLETED); return intentFilter; } @Override protected BroadcastReceiver getReceiver() { return new OperationHelperReceiver(); } private class OperationHelperReceiver extends BroadcastReceiver { private OperationRequest request; @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, intent.getAction()); if (intent.getExtras() != null && (IntentIntegrator.ACTION_DELETE_COMPLETED.equals(intent.getAction()) || IntentIntegrator.ACTION_UPDATE_COMPLETED.equals(intent.getAction()) || IntentIntegrator.ACTION_FAVORITE_COMPLETED.equals(intent.getAction()))) { Bundle b = intent.getExtras().getParcelable(IntentIntegrator.EXTRA_DATA); if (b == null) { return; } if (b.getSerializable(IntentIntegrator.EXTRA_FOLDER) instanceof File) { return; } if (intent.getAction().equals(IntentIntegrator.ACTION_FAVORITE_COMPLETED)) { Node node = (Node) b.getParcelable(IntentIntegrator.EXTRA_NODE); Account acc = SessionUtils.getAccount(mAppContext); SyncScanInfo lastScanSyncInfo = SyncScanInfo.getLastSyncScanData(context, acc); // The preceding scan returns an error/warning // We await the next user/automatic scan before redid it if (lastScanSyncInfo != null && lastScanSyncInfo.getScanResponse() == SyncScanInfo.RESPONSE_AWAIT) { return; } if (acc != null && node != null && canSync(acc)) { if (b.containsKey(IntentIntegrator.EXTRA_BATCH_FAVORITE) && b.getBoolean(IntentIntegrator.EXTRA_BATCH_FAVORITE)) { sync(acc); } else { sync(acc, node); } } return; } if (intent.getAction().equals(IntentIntegrator.ACTION_DELETE_COMPLETED)) { Node node = (Node) b.getParcelable(IntentIntegrator.EXTRA_DOCUMENT); if (node == null) { return; } Cursor favoriteCursor = mAppContext.getContentResolver().query( SynchroProvider.CONTENT_URI, SynchroSchema.COLUMN_ALL, SynchroSchema.COLUMN_NODE_ID + " LIKE '" + NodeRefUtils.getCleanIdentifier(node.getIdentifier()) + "%'", null, null); if (favoriteCursor.getCount() == 1 && favoriteCursor.moveToNext()) { Account acc = AccountManager.retrieveAccount(mAppContext, favoriteCursor.getLong(SynchroSchema.COLUMN_ACCOUNT_ID_ID)); if (canSync(acc)) { SynchroManager.getInstance(mAppContext).sync(acc); } } CursorUtils.closeCursor(favoriteCursor); return; } if (intent.getAction().equals(IntentIntegrator.ACTION_UPDATE_COMPLETED)) { Node node = null; if (b.containsKey(IntentIntegrator.EXTRA_DOCUMENT)) { node = (Node) b.getParcelable(IntentIntegrator.EXTRA_DOCUMENT); } else { node = (Node) b.getParcelable(IntentIntegrator.EXTRA_NODE); } Cursor favoriteCursor = mAppContext.getContentResolver().query( SynchroProvider.CONTENT_URI, SynchroSchema.COLUMN_ALL, SynchroSchema.COLUMN_NODE_ID + " LIKE '" + NodeRefUtils.getCleanIdentifier(node.getIdentifier()) + "%'", null, null); if (favoriteCursor.getCount() == 1 && favoriteCursor.moveToNext()) { Account acc = AccountManager.retrieveAccount(mAppContext, favoriteCursor.getLong(SynchroSchema.COLUMN_ACCOUNT_ID_ID)); if (canSync(acc)) { SynchroManager.getInstance(mAppContext).sync(acc, node); } } CursorUtils.closeCursor(favoriteCursor); return; } } if (IntentIntegrator.ACTION_SYNCHROS_CANCEL.equals(intent.getAction())) { OperationsGroupRecord group = null; for (int i = 0; i < operationsGroups.size(); i++) { group = operationsGroups.get(i); for (Entry<String, OperationRequest> requestEntry : group.runningRequest.entrySet()) { try { context.getContentResolver().update(getNotificationUri(requestEntry.getValue()), ((AbstractSyncOperationRequestImpl) requestEntry.getValue()) .createContentValues(Operation.STATUS_CANCEL), null, null); } catch (Exception e) { continue; } group.failedRequest.add(requestEntry.getValue()); } group.runningRequest.clear(); for (Entry<String, OperationRequest> requestEntry : group.index.entrySet()) { try { context.getContentResolver().update(getNotificationUri(requestEntry.getValue()), ((AbstractBatchOperationRequestImpl) requestEntry.getValue()) .createContentValues(Operation.STATUS_CANCEL), null, null); } catch (Exception e) { continue; } group.failedRequest.add(requestEntry.getValue()); } group.index.clear(); } operationsGroups.clear(); return; } if (IntentIntegrator.ACTION_SYNCHROS_STOP.equals(intent.getAction())) { OperationsGroupRecord group = null; for (int i = 0; i < operationsGroups.size(); i++) { group = operationsGroups.get(i); for (Entry<String, OperationRequest> requestEntry : group.runningRequest.entrySet()) { try { context.getContentResolver() .delete(((AbstractBatchOperationRequestImpl) requestEntry.getValue()) .getNotificationUri(), null, null); } catch (Exception e) { continue; } group.failedRequest.add(requestEntry.getValue()); } group.runningRequest.clear(); for (Entry<String, OperationRequest> requestEntry : group.index.entrySet()) { try { context.getContentResolver() .delete(((AbstractSyncOperationRequestImpl) requestEntry.getValue()) .getNotificationUri(), null, null); } catch (Exception e) { continue; } group.failedRequest.add(requestEntry.getValue()); } group.index.clear(); for (OperationRequest operationRequest : group.completeRequest) { context.getContentResolver().delete( ((AbstractSyncOperationRequestImpl) operationRequest).getNotificationUri(), null, null); } } return; } if (intent.getExtras() == null) { return; } String operationId = (String) intent.getExtras().get(EXTRA_SYNCHRO_ID); OperationsGroupRecord currentGroup = getOperationGroup(operationId); // ADD if (operationId != null && IntentIntegrator.ACTION_SYNCHRO_COMPLETED.equals(intent.getAction())) { int operationStatus = (int) intent.getExtras().getInt(EXTRA_SYNCHRO_RESULT); if (currentGroup.runningRequest.containsKey(operationId)) { OperationRequest op = currentGroup.runningRequest.remove(operationId); switch (operationStatus) { case Operation.STATUS_SUCCESSFUL: currentGroup.completeRequest.add(op); break; case Operation.STATUS_PAUSED: case Operation.STATUS_CANCEL: case Operation.STATUS_FAILED: currentGroup.failedRequest.add(op); break; default: break; } } return; } // STOP & DISPATCH if (operationId != null && IntentIntegrator.ACTION_SYNCHRO_STOP.equals(intent.getAction())) { if (currentGroup.runningRequest.containsKey(operationId)) { request = currentGroup.runningRequest.remove(operationId); } else if (currentGroup.index.containsKey(operationId)) { request = currentGroup.index.remove(operationId); } context.getContentResolver().update(getNotificationUri(request), ((AbstractSyncOperationRequestImpl) request).createContentValues(Operation.STATUS_CANCEL), null, null); currentGroup.failedRequest.add(request); } // PAUSE if (operationId != null && IntentIntegrator.ACTION_SYNCHRO_PAUSE.equals(intent.getAction())) { if (currentGroup.runningRequest.containsKey(operationId)) { request = currentGroup.runningRequest.remove(operationId); } else if (currentGroup.index.containsKey(operationId)) { request = currentGroup.index.remove(operationId); } Log.d(TAG, "PAUSED" + getNotificationUri(request)); context.getContentResolver().update(getNotificationUri(request), ((AbstractSyncOperationRequestImpl) request).createContentValues(Operation.STATUS_PAUSED), null, null); currentGroup.index.put(operationId, request); if (!currentGroup.hasRunningRequest()) { operationsGroups.remove(currentGroup); } } } } public class NetworkReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Cursor cursor = null; try { if (ConnectivityUtils.isWifiAvailable(context)) { // SYNC OPERATIONS String[] projection = { SynchroSchema.COLUMN_ID }; String selection = SynchroSchema.COLUMN_STATUS + "=" + Operation.STATUS_PAUSED; cursor = context.getContentResolver().query(SynchroProvider.CONTENT_URI, projection, selection, null, null); if (cursor.getCount() == 0) { return; } long[] ids = new long[cursor.getCount()]; int i = 0; while (cursor.moveToNext()) { ids[i] = cursor.getLong(SynchroSchema.COLUMN_ID_ID); i++; } retry(ids); } } catch (Exception e) { // Nothing special } finally { CursorUtils.closeCursor(cursor); } } } // //////////////////////////////////////////////////// // PUBLIC UTILS METHODS // //////////////////////////////////////////////////// public static ContentValues createContentValues(Context context, Account account, int requestType, Node node, long time) { return createContentValues(context, account, requestType, "", node, time, 0); } public static ContentValues createFavoriteContentValues(Context context, Account account, int requestType, Node node, long time) { ContentValues cValues = createContentValues(context, account, requestType, "", node, time, 0); cValues.put(SynchroSchema.COLUMN_IS_FAVORITE, SynchroProvider.FLAG_FAVORITE); return cValues; } public static ContentValues createContentValues(Context context, Account account, int requestType, String parent, Node node, long time, long folderSize) { ContentValues cValues = new ContentValues(); cValues.put(SynchroSchema.COLUMN_ACCOUNT_ID, account.getId()); cValues.put(SynchroSchema.COLUMN_TENANT_ID, account.getRepositoryId()); cValues.put(SynchroSchema.COLUMN_STATUS, Operation.STATUS_PENDING); cValues.put(SynchroSchema.COLUMN_REASON, -1); cValues.put(SynchroSchema.COLUMN_REQUEST_TYPE, requestType); cValues.put(SynchroSchema.COLUMN_TITLE, node.getName()); cValues.put(SynchroSchema.COLUMN_NOTIFICATION_VISIBILITY, OperationRequest.VISIBILITY_HIDDEN); cValues.put(SynchroSchema.COLUMN_NODE_ID, node.getIdentifier()); cValues.put(SynchroSchema.COLUMN_PARENT_ID, parent); if (node instanceof Document) { cValues.put(SynchroSchema.COLUMN_MIMETYPE, ((Document) node).getContentStreamMimeType()); cValues.put(SynchroSchema.COLUMN_TOTAL_SIZE_BYTES, ((Document) node).getContentStreamLength()); cValues.put(SynchroSchema.COLUMN_DOC_SIZE_BYTES, ((Document) node).getContentStreamLength()); if (node.getProperty(PropertyIds.CONTENT_STREAM_ID) != null) { cValues.put(SynchroSchema.COLUMN_CONTENT_URI, (String) node.getProperty(PropertyIds.CONTENT_STREAM_ID).getValue()); } } else { cValues.put(SynchroSchema.COLUMN_MIMETYPE, ContentModel.TYPE_FOLDER); cValues.put(SynchroSchema.COLUMN_TOTAL_SIZE_BYTES, folderSize); cValues.put(SynchroSchema.COLUMN_DOC_SIZE_BYTES, 0); if (folderSize == 0) { cValues.put(SynchroSchema.COLUMN_STATUS, Operation.STATUS_SUCCESSFUL); } } cValues.put(SynchroSchema.COLUMN_PROPERTIES, serializeProperties(node)); cValues.put(SynchroSchema.COLUMN_BYTES_DOWNLOADED_SO_FAR, 0); cValues.put(SynchroSchema.COLUMN_LOCAL_URI, ""); cValues.put(SynchroSchema.COLUMN_ANALYZE_TIMESTAMP, time); cValues.put(SynchroSchema.COLUMN_SERVER_MODIFICATION_TIMESTAMP, node.getModifiedAt().getTimeInMillis()); cValues.put(SynchroSchema.COLUMN_LOCAL_MODIFICATION_TIMESTAMP, time); cValues.put(SynchroSchema.COLUMN_IS_FAVORITE, 0); return cValues; } public static ContentValues createFavoriteContentValues(Context context, Account account, int requestType, String parent, Node node, long time, long folderSize) { ContentValues cValues = createContentValues(context, account, requestType, parent, node, time, folderSize); cValues.put(SynchroSchema.COLUMN_IS_FAVORITE, SynchroProvider.FLAG_FAVORITE); return cValues; } public static String serializeProperties(Node node) { HashMap<String, Serializable> persistentProperties = new HashMap<String, Serializable>(); Map<String, Property> props = node.getProperties(); for (Entry<String, Property> entry : props.entrySet()) { if (entry.getValue().getValue() instanceof GregorianCalendar) { persistentProperties.put(entry.getKey(), ((GregorianCalendar) entry.getValue().getValue()).getTimeInMillis()); } else { persistentProperties.put(entry.getKey(), (Serializable) entry.getValue().getValue()); } } if (!persistentProperties.isEmpty()) { return MapUtil.mapToString(persistentProperties); } else { return ""; } } public static Uri getUri(long id) { return Uri.parse(SynchroProvider.CONTENT_URI + "/" + id); } public void sync(Account account) { if (account == null) { return; } OperationsRequestGroup group = new OperationsRequestGroup(mAppContext, account); group.enqueue(new SyncPrepareRequest().setNotificationVisibility(OperationRequest.VISIBILITY_HIDDEN)); BatchOperationManager.getInstance(mAppContext).enqueue(group); } public void sync(Account account, Node n) { if (account == null) { return; } OperationsRequestGroup group = new OperationsRequestGroup(mAppContext, account); group.enqueue(new SyncPrepareRequest(SyncPrepareRequest.MODE_NODE, n) .setNotificationVisibility(OperationRequest.VISIBILITY_HIDDEN)); BatchOperationManager.getInstance(mAppContext).enqueue(group); } public void unsync(Account account) { if (account == null) { return; } OperationsRequestGroup group = new OperationsRequestGroup(mAppContext, account); group.enqueue(new CleanSyncFavoriteRequest(account, false) .setNotificationVisibility(OperationRequest.VISIBILITY_HIDDEN)); BatchOperationManager.getInstance(mAppContext).enqueue(group); } public boolean isSynced(Account account, String nodeIdentifier) { if (account == null) { return false; } Cursor favoriteCursor = mAppContext.getContentResolver().query(SynchroProvider.CONTENT_URI, SynchroSchema.COLUMN_ALL, SynchroProvider.getAccountFilter(account) + " AND " + SynchroSchema.COLUMN_NODE_ID + " LIKE '" + NodeRefUtils.getCleanIdentifier(nodeIdentifier) + "%'", null, null); boolean b = (favoriteCursor.getCount() == 1) && GeneralPreferences.hasActivateSync(mAppContext, account); CursorUtils.closeCursor(favoriteCursor); return b; } public boolean isSynced(Account account, Node node) { if (account == null || node == null) { return false; } if (node.isFolder()) { return false; } return isSynced(account, node.getIdentifier()); } public File getSyncFile(Account account, Node node) { if (account == null || node == null) { return null; } if (node.isFolder()) { return null; } if (node instanceof NodeSyncPlaceHolder) { return StorageManager.getSynchroFile(mAppContext, account, node.getName(), node.getIdentifier()); } return StorageManager.getSynchroFile(mAppContext, account, (Document) node); } public static Cursor getCursorForId(Context context, Account acc, String identifier) { if (acc == null) { return null; } return context.getContentResolver().query(SynchroProvider.CONTENT_URI, SynchroSchema.COLUMN_ALL, SynchroProvider.getAccountFilter(acc) + " AND " + SynchroSchema.COLUMN_NODE_ID + " LIKE '" + NodeRefUtils.getCleanIdentifier(identifier) + "%'", null, null); } public Uri getUri(Account account, String nodeIdentifier) { if (account == null) { return null; } Uri b = null; Cursor favoriteCursor = getCursorForId(mAppContext, account, nodeIdentifier); if (favoriteCursor.getCount() == 1 && favoriteCursor.moveToFirst()) { b = Uri.parse(SynchroProvider.CONTENT_URI + "/" + favoriteCursor.getLong(SynchroSchema.COLUMN_ID_ID)); } CursorUtils.closeCursor(favoriteCursor); return b; } public boolean canSync(Account account) { return GeneralPreferences.hasActivateSync(mAppContext, account) && ((GeneralPreferences.hasWifiOnlySync(mAppContext, account) && ConnectivityUtils.isWifiAvailable(mAppContext)) || !GeneralPreferences.hasWifiOnlySync(mAppContext, account)); } public boolean hasActivateSync(Account account) { return GeneralPreferences.hasActivateSync(mAppContext, account); } public boolean canSyncEverything(Account account) { return GeneralPreferences.canSyncEverything(mAppContext, account); } public boolean hasConnectivityToSync(Account account) { return ((GeneralPreferences.hasWifiOnlySync(mAppContext, account) && ConnectivityUtils.isWifiAvailable(mAppContext)) || !GeneralPreferences.hasWifiOnlySync(mAppContext, account)); } /** * Flag the activity time. * * @param context */ public static void saveStartSyncPrepareTimestamp(Context context) { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); Editor editor = sharedPref.edit(); Account account = SessionUtils.getAccount(context); if (account != null) { editor.putLong(LAST_START_SYNC_PREPARE + account.getId(), new Date().getTime()); editor.commit(); } } public static void saveSyncPrepareTimestamp(Context context) { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); Editor editor = sharedPref.edit(); Account account = SessionUtils.getAccount(context); if (account != null) { editor.putLong(LAST_SYNC_ACTIVATED_AT + account.getId(), new Date().getTime()); editor.commit(); } } public static long getSyncPrepareTimestamp(Context context, Account account) { if (account == null) { return -1; } SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); return sharedPref.getLong(LAST_SYNC_ACTIVATED_AT + account.getId(), new Date().getTime()); } public static long getStartSyncPrepareTimestamp(Context context, Account account) { if (account == null) { return -1; } SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); return sharedPref.getLong(LAST_START_SYNC_PREPARE + account.getId(), new Date().getTime()); } /** * Start a sync if the last activity time is greater than 1 hour. */ public void cronSync(Account account) { if (account == null) { return; } long now = new Date().getTime(); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mAppContext); long lastTime = sharedPref.getLong(LAST_SYNC_ACTIVATED_AT + account.getId(), now); if ((lastTime + 3600000) < now && canSync(account)) { sync(account); } } public static boolean isFolder(Cursor cursor) { if (cursor == null) { return false; } return ContentModel.TYPE_FOLDER.equals(cursor.getString(SynchroSchema.COLUMN_MIMETYPE_ID)); } // //////////////////////////////////////////////////// // STORAGE MANAGEMENT // //////////////////////////////////////////////////// private static final String QUERY_SUM = "SELECT SUM(" + SynchroSchema.COLUMN_BYTES_DOWNLOADED_SO_FAR + ") FROM " + SynchroSchema.TABLENAME + " WHERE " + SynchroSchema.COLUMN_PARENT_ID + " = '%s';"; private static final String QUERY_SUM_IN_PENDING = "SELECT SUM(" + SynchroSchema.COLUMN_DOC_SIZE_BYTES + ") FROM " + SynchroSchema.TABLENAME + " WHERE " + SynchroSchema.COLUMN_ACCOUNT_ID + " == %s AND " + SynchroSchema.COLUMN_STATUS + " IN ( " + SyncOperation.STATUS_PENDING + "," + SyncOperation.STATUS_HIDDEN + ");"; private static final String QUERY_SUM_TOTAL_IN_PENDING = "SELECT SUM(" + SynchroSchema.COLUMN_TOTAL_SIZE_BYTES + ") FROM " + SynchroSchema.TABLENAME + " WHERE " + SynchroSchema.COLUMN_ACCOUNT_ID + " == %s AND " + SynchroSchema.COLUMN_STATUS + " = " + SyncOperation.STATUS_PENDING + " AND " + SynchroSchema.COLUMN_MIMETYPE + " NOT IN ('" + ContentModel.TYPE_FOLDER + "');"; private static final String QUERY_TOTAL_STORED = "SELECT SUM(" + SynchroSchema.COLUMN_DOC_SIZE_BYTES + ") FROM " + SynchroSchema.TABLENAME + " WHERE " + SynchroSchema.COLUMN_ACCOUNT_ID + " == %s AND " + SynchroSchema.COLUMN_STATUS + " IN (" + SyncOperation.STATUS_PENDING + ", " + SyncOperation.STATUS_SUCCESSFUL + ");"; private static final String QUERY_SUM_TOTAL = "SELECT SUM(" + SynchroSchema.COLUMN_TOTAL_SIZE_BYTES + ") FROM " + SynchroSchema.TABLENAME + " WHERE " + SynchroSchema.COLUMN_PARENT_ID + " = '%s';"; public synchronized void updateParentFolder(Account account, String identifier) { Long currentValue = null; Long totalSize = null; String parentFolderId = null; Cursor favoriteCursor = null, parentCursor = null, cursorTotal = null, cursor = null; try { // Retrieve Uri & ParentFolder Uri uri = null; favoriteCursor = mAppContext.getContentResolver().query(SynchroProvider.CONTENT_URI, SynchroSchema.COLUMN_ALL, SynchroProvider.getAccountFilter(account) + " AND " + SynchroSchema.COLUMN_NODE_ID + " == '" + NodeRefUtils.getCleanIdentifier(identifier) + "'", null, null); if (favoriteCursor.getCount() == 1 && favoriteCursor.moveToFirst()) { parentFolderId = favoriteCursor.getString(SynchroSchema.COLUMN_PARENT_ID_ID); uri = Uri.parse( SynchroProvider.CONTENT_URI + "/" + favoriteCursor.getLong(SynchroSchema.COLUMN_ID_ID)); parentCursor = mAppContext.getContentResolver() .query(SynchroProvider.CONTENT_URI, SynchroSchema.COLUMN_ALL, SynchroProvider.getAccountFilter(account) + " AND " + SynchroSchema.COLUMN_NODE_ID + " == '" + NodeRefUtils.getCleanIdentifier(parentFolderId) + "'", null, null); } else { return; } if (parentCursor != null && parentCursor.getCount() == 1 && parentCursor.moveToFirst() && SyncOperation.STATUS_HIDDEN == parentCursor.getInt(SynchroSchema.COLUMN_STATUS_ID)) { // Node has been flag to deletion // We don't update return; } // Retrieve the TOTAL sum of children totalSize = retrieveSize(String.format(QUERY_SUM_TOTAL, identifier)); if (totalSize == null) { return; } // REtrieve the sum of children currentValue = retrieveSize(String.format(QUERY_SUM, identifier)); if (currentValue == null) { return; } // Update the parent ContentValues cValues = new ContentValues(); cValues.put(SynchroSchema.COLUMN_BYTES_DOWNLOADED_SO_FAR, currentValue); cValues.put(SynchroSchema.COLUMN_TOTAL_SIZE_BYTES, totalSize); cValues.put(BatchOperationSchema.COLUMN_STATUS, Operation.STATUS_RUNNING); if (totalSize.longValue() == currentValue.longValue()) { cValues.put(BatchOperationSchema.COLUMN_STATUS, Operation.STATUS_SUCCESSFUL); } mAppContext.getContentResolver().update(uri, cValues, null, null); } catch (Exception e) { } finally { CursorUtils.closeCursor(parentCursor); CursorUtils.closeCursor(cursorTotal); CursorUtils.closeCursor(favoriteCursor); CursorUtils.closeCursor(cursor); } // Recursive on grand parent if (parentFolderId != null) { updateParentFolder(account, parentFolderId); } } private Long retrieveSize(String query) { Long totalSize = null; // Retrieve the TOTAL sum of children Cursor cursorTotal = ApplicationManager.getInstance(mAppContext).getDatabaseManager().getWriteDb() .rawQuery(query, null); if (cursorTotal.moveToFirst()) { totalSize = cursorTotal.getLong(0); } return totalSize; } public Long getAmountDataToTransfert(Account acc) { return retrieveSize(String.format(QUERY_SUM_TOTAL_IN_PENDING, acc.getId())); } public Long getAmountDataStored(Account acc) { return retrieveSize(String.format(QUERY_TOTAL_STORED, acc.getId())); } public Long getPreviousAmountDataStored(Account acc) { return retrieveSize(String.format(QUERY_SUM_IN_PENDING, acc.getId())); } // //////////////////////////////////////////////////// // SYNC POLICIES // //////////////////////////////////////////////////// public SyncScanInfo getScanInfo(Account acc) { // IF sync is disabled scanInfo is success by default. if (!hasActivateSync(acc)) { return new SyncScanInfo(0, 0, SyncScanInfo.RESULT_SUCCESS); } long dataFinalStored = getAmountDataStored(acc); long deltaStorage = getPreviousAmountDataStored(acc); float totalBytes = StorageManager.getTotalBytes(mAppContext); float availableBytes = StorageManager.getAvailableBytes(mAppContext); long dataToTransfer = getAmountDataToTransfert(acc); Log.d(TAG, "Data Transfer : " + dataToTransfer); Log.d(TAG, "Data Final : " + dataFinalStored); Log.d(TAG, "Data Delta : " + deltaStorage); Log.d(TAG, "Data AvailableBytes : " + availableBytes); Log.d(TAG, "Data TotalBytes : " + totalBytes); boolean respectMobileTransferPolicy = respectMobileTransferPolicy(acc, dataToTransfer); boolean respectEnoughStorageSpace = respectEnoughStorageSpace(availableBytes, deltaStorage); boolean respectLimitStorageSpace = respectLimitStorageSpace(acc, availableBytes, deltaStorage, totalBytes); Log.d(TAG, "Transfert Policy : " + respectMobileTransferPolicy); Log.d(TAG, "Enough Space : " + respectEnoughStorageSpace); Log.d(TAG, "Limit Space : " + respectLimitStorageSpace); int scanResult = SyncScanInfo.RESULT_SUCCESS; if (!respectEnoughStorageSpace) { scanResult = SyncScanInfo.RESULT_ERROR_NOT_ENOUGH_STORAGE; } else if (!respectLimitStorageSpace) { scanResult = SyncScanInfo.RESULT_WARNING_LOW_STORAGE; } else if (!respectMobileTransferPolicy) { scanResult = SyncScanInfo.RESULT_WARNING_MOBILE_DATA; } return new SyncScanInfo(deltaStorage, dataToTransfer, scanResult); } private boolean respectMobileTransferPolicy(Account acc, long dataToTransfer) { if (!ConnectivityUtils.hasMobileConnectivity(mAppContext)) { return true; } // Check Data transfert only if on Mobile Network if (ConnectivityUtils.isMobileNetworkAvailable(mAppContext) && !ConnectivityUtils.isWifiAvailable(mAppContext)) { long maxDataTransfer = GeneralPreferences.getDataSyncTransferAlert(mAppContext, acc); if (maxDataTransfer < dataToTransfer) { // Warning : Data transfer ! // Request user Info return false; } } return true; } private boolean respectEnoughStorageSpace(float availableBytes, long deltaStorage) { // In case we remove data or no data if (deltaStorage <= 0) { return true; } // POLICY 1 : Enough Storage if ((availableBytes - deltaStorage) <= 0) { // ERROR : Not Enough Storage space // Sync canceled ! return false; } return true; } private boolean respectLimitStorageSpace(Account acc, float availableBytes, long deltaStorage, float totalBytes) { // In case we remove data or no data if (deltaStorage <= 0) { return true; } float percentTotalSpace = GeneralPreferences.getDataSyncPercentFreeSpace(mAppContext, acc); // Check Delta data storage after sync if ((availableBytes - deltaStorage) < (percentTotalSpace * totalBytes)) { // Warning : Storage space low // Request user Info return false; } return true; } // //////////////////////////////////////////////////// // PENDING OPERATIONS // //////////////////////////////////////////////////// private OperationsRequestGroup pendingOperationGroup; public void saveOperationGroup(OperationsRequestGroup group) { pendingOperationGroup = group; } public void runPendingOperationGroup() { if (pendingOperationGroup != null) { enqueue(pendingOperationGroup); } SyncScanInfo.getLastSyncScanData(mAppContext, SessionUtils.getAccount(mAppContext)).forceScan(mAppContext, SessionUtils.getAccount(mAppContext)); pendingOperationGroup = null; } }