X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/blobdiff_plain/aabdaee86861832d15558f91f040479a1906beee..d24ba158d24c161a0db484ea551a40851a9d60e8:/src/com/owncloud/android/syncadapter/FileSyncAdapter.java diff --git a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java index 0d4e4c5a..407ce1a2 100644 --- a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -26,68 +26,114 @@ import java.util.Map; import org.apache.jackrabbit.webdav.DavException; -import com.owncloud.android.Log_OC; -import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AuthenticatorActivity; -import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.operations.SynchronizeFolderOperation; import com.owncloud.android.operations.UpdateOCVersionOperation; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity; +import com.owncloud.android.utils.Log_OC; import android.accounts.Account; import android.accounts.AccountsException; -import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SyncResult; import android.os.Bundle; +import android.support.v4.app.NotificationCompat; /** - * SyncAdapter implementation for syncing sample SyncAdapter contacts to the - * platform ContactOperations provider. + * Implementation of {@link AbstractThreadedSyncAdapter} responsible for synchronizing + * ownCloud files. + * + * Performs a full synchronization of the account recieved in {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)}. * * @author Bartek Przybylski * @author David A. Velasco */ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { - private final static String TAG = "FileSyncAdapter"; + private final static String TAG = FileSyncAdapter.class.getSimpleName(); - /** - * Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation - */ + /** Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation */ private static final int MAX_FAILED_RESULTS = 3; + + public static final String EVENT_FULL_SYNC_START = FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_START"; + public static final String EVENT_FULL_SYNC_END = FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_END"; + public static final String EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED = FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED"; + //public static final String EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED = FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED"; + + public static final String EXTRA_ACCOUNT_NAME = FileSyncAdapter.class.getName() + ".EXTRA_ACCOUNT_NAME"; + public static final String EXTRA_FOLDER_PATH = FileSyncAdapter.class.getName() + ".EXTRA_FOLDER_PATH"; + public static final String EXTRA_RESULT = FileSyncAdapter.class.getName() + ".EXTRA_RESULT"; + + + /** Time stamp for the current synchronization process, used to distinguish fresh data */ private long mCurrentSyncTime; + + /** Flag made 'true' when a request to cancel the synchronization is received */ private boolean mCancellation; + + /** When 'true' the process was requested by the user through the user interface; when 'false', it was requested automatically by the system */ private boolean mIsManualSync; - private int mFailedResultsCounter; + + /** Counter for failed operations in the synchronization process */ + private int mFailedResultsCounter; + + /** Result of the last failed operation */ private RemoteOperationResult mLastFailedResult; - private SyncResult mSyncResult; + + /** Counter of conflicts found between local and remote files */ private int mConflictsFound; + + /** Counter of failed operations in synchronization of kept-in-sync files */ private int mFailsInFavouritesFound; + + /** Map of remote and local paths to files that where locally stored in a location out of the ownCloud folder and couldn't be copied automatically into it */ private Map mForgottenLocalFiles; + /** {@link SyncResult} instance to return to the system when the synchronization finish */ + private SyncResult mSyncResult; + + /** 'True' means that the server supports the share API */ + private boolean mIsShareSupported; + + /** + * Creates a {@link FileSyncAdapter} + * + * {@inheritDoc} + */ public FileSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); } + + /** + * Creates a {@link FileSyncAdapter} + * + * {@inheritDoc} + */ + public FileSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { + super(context, autoInitialize, allowParallelSyncs); + } + + /** * {@inheritDoc} */ @Override public synchronized void onPerformSync(Account account, Bundle extras, - String authority, ContentProviderClient provider, + String authority, ContentProviderClient providerClient, SyncResult syncResult) { mCancellation = false; @@ -99,36 +145,37 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { mForgottenLocalFiles = new HashMap(); mSyncResult = syncResult; mSyncResult.fullSyncRequested = false; - mSyncResult.delayUntil = 60*60*24; // sync after 24h + mSyncResult.delayUntil = 60*60*24; // avoid too many automatic synchronizations this.setAccount(account); - this.setContentProvider(provider); - this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); + this.setContentProviderClient(providerClient); + this.setStorageManager(new FileDataStorageManager(account, providerClient)); + try { this.initClientForCurrentAccount(); } catch (IOException e) { - /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again + /// the account is unknown for the Synchronization Manager, unreachable this context, or can not be authenticated; don't try this again mSyncResult.tooManyRetries = true; notifyFailedSynchronization(); return; } catch (AccountsException e) { - /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again + /// the account is unknown for the Synchronization Manager, unreachable this context, or can not be authenticated; don't try this again mSyncResult.tooManyRetries = true; notifyFailedSynchronization(); return; } Log_OC.d(TAG, "Synchronization of ownCloud account " + account.name + " starting"); - sendStickyBroadcast(true, null, null); // message to signal the start of the synchronization to the UI + sendLocalBroadcast(EVENT_FULL_SYNC_START, null, null); // message to signal the start of the synchronization to the UI try { updateOCVersion(); mCurrentSyncTime = System.currentTimeMillis(); if (!mCancellation) { - fetchData(OCFile.PATH_SEPARATOR, DataStorageManager.ROOT_PARENT_ID); + synchronizeFolder(getStorageManager().getFileByPath(OCFile.ROOT_PATH)); } else { - Log_OC.d(TAG, "Leaving synchronization before any remote request due to cancellation was requested"); + Log_OC.d(TAG, "Leaving synchronization before synchronizing the root folder because cancelation request"); } @@ -142,16 +189,14 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { /// notify the user about the failure of MANUAL synchronization notifyFailedSynchronization(); - } if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { notifyFailsInFavourites(); } if (mForgottenLocalFiles.size() > 0) { notifyForgottenLocalFiles(); - } - sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI + sendLocalBroadcast(EVENT_FULL_SYNC_END, null, mLastFailedResult); // message to signal the end to the UI } } @@ -159,8 +204,12 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { /** * Called by system SyncManager when a synchronization is required to be cancelled. * - * Sets the mCancellation flag to 'true'. THe synchronization will be stopped when before a new folder is fetched. Data of the last folder - * fetched will be still saved in the database. See onPerformSync implementation. + * Sets the mCancellation flag to 'true'. THe synchronization will be stopped later, + * before a new folder is fetched. Data of the last folder synchronized will be still + * locally saved. + * + * See {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)} + * and {@link #synchronizeFolder(String, long)}. */ @Override public void onSyncCanceled() { @@ -178,25 +227,43 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { RemoteOperationResult result = update.execute(getClient()); if (!result.isSuccess()) { mLastFailedResult = result; + } else { + mIsShareSupported = update.getOCVersion().isSharedSupported(); } } /** - * Synchronize the properties of files and folders contained in a remote folder given by remotePath. + * Synchronizes the list of files contained in a folder identified with its remote path. + * + * Fetches the list and properties of the files contained in the given folder, including their + * properties, and updates the local database with them. + * + * Enters in the child folders to synchronize their contents also, following a recursive + * depth first strategy. * - * @param remotePath Remote path to the folder to synchronize. - * @param parentId Database Id of the folder to synchronize. + * @param folder Folder to synchronize. */ - private void fetchData(String remotePath, long parentId) { + private void synchronizeFolder(OCFile folder) { if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult)) return; - // perform folder synchronization - SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( remotePath, + /* + OCFile folder, + long currentSyncTime, + boolean updateFolderProperties, + boolean syncFullAccount, + DataStorageManager dataStorageManager, + Account account, + Context context ) { + } + */ + // folder synchronization + SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( folder, mCurrentSyncTime, - parentId, + true, + mIsShareSupported, getStorageManager(), getAccount(), getContext() @@ -205,8 +272,9 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess - sendStickyBroadcast(true, remotePath, null); + sendLocalBroadcast(EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED, folder.getRemotePath(), result); + // check the result of synchronizing the folder if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) { if (result.getCode() == ResultCode.SYNC_CONFLICT) { @@ -216,17 +284,18 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { if (synchFolderOp.getForgottenLocalFiles().size() > 0) { mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles()); } - // synchronize children folders - List children = synchFolderOp.getChildren(); - fetchChildren(children); // beware of the 'hidden' recursion here! - - sendStickyBroadcast(true, remotePath, null); + if (result.isSuccess()) { + // synchronize children folders + List children = synchFolderOp.getChildren(); + fetchChildren(folder, children, synchFolderOp.getRemoteFolderChanged()); // beware of the 'hidden' recursion here! + } } else { + // in failures, the statistics for the global result are updated if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED || - // (result.isTemporalRedirection() && result.isIdPRedirection() && - ( result.isIdPRedirection() && - MainApp.getAuthTokenTypeSamlSessionCookie().equals(getClient().getAuthTokenType()))) { + ( result.isIdPRedirection() && + getClient().getCredentials() == null )) { + //MainApp.getAuthTokenTypeSamlSessionCookie().equals(getClient().getAuthTokenType()))) { mSyncResult.stats.numAuthExceptions++; } else if (result.getException() instanceof DavException) { @@ -260,45 +329,51 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { } /** - * Synchronize data of folders in the list of received files + * Triggers the synchronization of any folder contained in the list of received files. * - * @param files Files to recursively fetch + * @param files Files to recursively synchronize. */ - private void fetchChildren(List files) { + private void fetchChildren(OCFile parent, List files, boolean parentEtagChanged) { int i; + OCFile newFile = null; + //String etag = null; + //boolean syncDown = false; for (i=0; i < files.size() && !mCancellation; i++) { - OCFile newFile = files.get(i); - if (newFile.isDirectory()) { - fetchData(newFile.getRemotePath(), newFile.getFileId()); - - // Update folder size on DB - getStorageManager().calculateFolderSize(newFile.getFileId()); + newFile = files.get(i); + if (newFile.isFolder()) { + /* + etag = newFile.getEtag(); + syncDown = (parentEtagChanged || etag == null || etag.length() == 0); + if(syncDown) { */ + synchronizeFolder(newFile); + //sendLocalBroadcast(EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED, parent.getRemotePath(), null); + //} } } - if (mCancellation && i 0) { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; + NotificationCompat.Builder notificationBuilder = createNotificationBuilder(); + notificationBuilder.setTicker(i18n(R.string.sync_fail_in_favourites_ticker)); + // TODO put something smart in the contentIntent below - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_fail_in_favourites_ticker), - String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification); + notificationBuilder + .setContentIntent(PendingIntent.getActivity( + getContext(), (int) System.currentTimeMillis(), new Intent(), 0 + )) + .setContentTitle(i18n(R.string.sync_fail_in_favourites_ticker)) + .setContentText(i18n(R.string.sync_fail_in_favourites_content, mFailedResultsCounter + mConflictsFound, mConflictsFound)); + showNotification(R.string.sync_fail_in_favourites_ticker, notificationBuilder); } else { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; + NotificationCompat.Builder notificationBuilder = createNotificationBuilder(); + notificationBuilder.setTicker(i18n(R.string.sync_conflicts_in_favourites_ticker)); + // TODO put something smart in the contentIntent below - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_conflicts_in_favourites_ticker), - String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification); + notificationBuilder + .setContentIntent(PendingIntent.getActivity( + getContext(), (int) System.currentTimeMillis(), new Intent(), 0 + )) + .setContentTitle(i18n(R.string.sync_conflicts_in_favourites_ticker)) + .setContentText(i18n(R.string.sync_conflicts_in_favourites_ticker, mConflictsFound)); + + showNotification(R.string.sync_conflicts_in_favourites_ticker, notificationBuilder); } } @@ -382,9 +460,9 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory. */ private void notifyForgottenLocalFiles() { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; - + NotificationCompat.Builder notificationBuilder = createNotificationBuilder(); + notificationBuilder.setTicker(i18n(R.string.sync_foreign_files_forgotten_ticker)); + /// includes a pending intent in the notification showing a more detailed explanation Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class); explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount()); @@ -396,14 +474,45 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths); explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_foreign_files_forgotten_ticker), - String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size(), getContext().getString(R.string.app_name)), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification); + notificationBuilder + .setContentIntent(PendingIntent.getActivity( + getContext(), (int) System.currentTimeMillis(), explanationIntent, 0 + )) + .setContentTitle(i18n(R.string.sync_foreign_files_forgotten_ticker)) + .setContentText(i18n(R.string.sync_foreign_files_forgotten_content, mForgottenLocalFiles.size(), i18n(R.string.app_name))); + showNotification(R.string.sync_foreign_files_forgotten_ticker, notificationBuilder); } + /** + * Creates a notification builder with some commonly used settings + * + * @return + */ + private NotificationCompat.Builder createNotificationBuilder() { + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getContext()); + notificationBuilder.setSmallIcon(R.drawable.notification_icon).setAutoCancel(true); + return notificationBuilder; + } + /** + * Builds and shows the notification + * + * @param id + * @param builder + */ + private void showNotification(int id, NotificationCompat.Builder builder) { + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)) + .notify(id, builder.build()); + } + /** + * Shorthand translation + * + * @param key + * @param args + * @return + */ + private String i18n(int key, Object... args) { + return getContext().getString(key, args); + } }