FileSyncAdapter.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /* ownCloud Android client application
  2. * Copyright (C) 2011 Bartek Przybylski
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. *
  17. */
  18. package com.owncloud.android.syncadapter;
  19. import java.io.IOException;
  20. import java.net.UnknownHostException;
  21. import java.util.List;
  22. import org.apache.jackrabbit.webdav.DavException;
  23. import com.owncloud.android.R;
  24. import com.owncloud.android.datamodel.DataStorageManager;
  25. import com.owncloud.android.datamodel.FileDataStorageManager;
  26. import com.owncloud.android.datamodel.OCFile;
  27. import com.owncloud.android.operations.RemoteOperationResult;
  28. import com.owncloud.android.operations.SynchronizeFolderOperation;
  29. import com.owncloud.android.operations.UpdateOCVersionOperation;
  30. import android.accounts.Account;
  31. import android.app.Notification;
  32. import android.app.NotificationManager;
  33. import android.app.PendingIntent;
  34. import android.content.ContentProviderClient;
  35. import android.content.ContentResolver;
  36. import android.content.Context;
  37. import android.content.Intent;
  38. import android.content.SyncResult;
  39. import android.os.Bundle;
  40. import android.util.Log;
  41. /**
  42. * SyncAdapter implementation for syncing sample SyncAdapter contacts to the
  43. * platform ContactOperations provider.
  44. *
  45. * @author Bartek Przybylski
  46. */
  47. public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
  48. private final static String TAG = "FileSyncAdapter";
  49. /**
  50. * Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation
  51. */
  52. private static final int MAX_FAILED_RESULTS = 3;
  53. private long mCurrentSyncTime;
  54. private boolean mCancellation;
  55. private boolean mIsManualSync;
  56. private int mFailedResultsCounter;
  57. private RemoteOperationResult mLastFailedResult;
  58. private SyncResult mSyncResult;
  59. public FileSyncAdapter(Context context, boolean autoInitialize) {
  60. super(context, autoInitialize);
  61. }
  62. /**
  63. * {@inheritDoc}
  64. */
  65. @Override
  66. public synchronized void onPerformSync(Account account, Bundle extras,
  67. String authority, ContentProviderClient provider,
  68. SyncResult syncResult) {
  69. mCancellation = false;
  70. mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
  71. mFailedResultsCounter = 0;
  72. mLastFailedResult = null;
  73. mSyncResult = syncResult;
  74. this.setAccount(account);
  75. this.setContentProvider(provider);
  76. this.setStorageManager(new FileDataStorageManager(account, getContentProvider()));
  77. try {
  78. this.initClientForCurrentAccount();
  79. } catch (UnknownHostException e) {
  80. /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again
  81. mSyncResult.tooManyRetries = true;
  82. notifyFailedSynchronization();
  83. return;
  84. }
  85. Log.d(TAG, "Synchronization of ownCloud account " + account.name + " starting");
  86. sendStickyBroadcast(true, null, null); // message to signal the start of the synchronization to the UI
  87. try {
  88. updateOCVersion();
  89. mCurrentSyncTime = System.currentTimeMillis();
  90. if (!mCancellation) {
  91. fetchData(OCFile.PATH_SEPARATOR, DataStorageManager.ROOT_PARENT_ID);
  92. } else {
  93. Log.d(TAG, "Leaving synchronization before any remote request due to cancellation was requested");
  94. }
  95. } finally {
  96. // it's important making this although very unexpected errors occur; that's the reason for the finally
  97. if (mFailedResultsCounter > 0 && mIsManualSync) {
  98. /// don't let the system synchronization manager retries MANUAL synchronizations
  99. // (be careful: "MANUAL" currently includes the synchronization requested when a new account is created and when the user changes the current account)
  100. mSyncResult.tooManyRetries = true;
  101. /// notify the user about the failure of MANUAL synchronization
  102. notifyFailedSynchronization();
  103. }
  104. sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI
  105. }
  106. }
  107. /**
  108. * Called by system SyncManager when a synchronization is required to be cancelled.
  109. *
  110. * Sets the mCancellation flag to 'true'. THe synchronization will be stopped when before a new folder is fetched. Data of the last folder
  111. * fetched will be still saved in the database. See onPerformSync implementation.
  112. */
  113. @Override
  114. public void onSyncCanceled() {
  115. Log.d(TAG, "Synchronization of " + getAccount().name + " has been requested to cancel");
  116. mCancellation = true;
  117. super.onSyncCanceled();
  118. }
  119. /**
  120. * Updates the locally stored version value of the ownCloud server
  121. */
  122. private void updateOCVersion() {
  123. UpdateOCVersionOperation update = new UpdateOCVersionOperation(getAccount(), getContext());
  124. RemoteOperationResult result = update.execute(getClient());
  125. if (!result.isSuccess()) {
  126. mLastFailedResult = result;
  127. }
  128. }
  129. /**
  130. * Synchronize the properties of files and folders contained in a remote folder given by remotePath.
  131. *
  132. * @param remotePath Remote path to the folder to synchronize.
  133. * @param parentId Database Id of the folder to synchronize.
  134. */
  135. private void fetchData(String remotePath, long parentId) {
  136. if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult))
  137. return;
  138. // perform folder synchronization
  139. SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( remotePath,
  140. mCurrentSyncTime,
  141. parentId,
  142. getStorageManager(),
  143. getAccount(),
  144. getContext()
  145. );
  146. RemoteOperationResult result = synchFolderOp.execute(getClient());
  147. // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess
  148. sendStickyBroadcast(true, remotePath, null);
  149. if (result.isSuccess()) {
  150. // synchronize children folders
  151. List<OCFile> children = synchFolderOp.getChildren();
  152. fetchChildren(children); // beware of the 'hidden' recursion here!
  153. } else {
  154. if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED) {
  155. mSyncResult.stats.numAuthExceptions++;
  156. } else if (result.getException() instanceof DavException) {
  157. mSyncResult.stats.numParseExceptions++;
  158. } else if (result.getException() instanceof IOException) {
  159. mSyncResult.stats.numIoExceptions++;
  160. }
  161. mFailedResultsCounter++;
  162. mLastFailedResult = result;
  163. }
  164. }
  165. /**
  166. * Checks if a failed result should terminate the synchronization process immediately, according to
  167. * OUR OWN POLICY
  168. *
  169. * @param failedResult Remote operation result to check.
  170. * @return 'True' if the result should immediately finish the synchronization
  171. */
  172. private boolean isFinisher(RemoteOperationResult failedResult) {
  173. if (failedResult != null) {
  174. RemoteOperationResult.ResultCode code = failedResult.getCode();
  175. return (code.equals(RemoteOperationResult.ResultCode.SSL_ERROR) ||
  176. code.equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) ||
  177. code.equals(RemoteOperationResult.ResultCode.BAD_OC_VERSION) ||
  178. code.equals(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED));
  179. }
  180. return false;
  181. }
  182. /**
  183. * Synchronize data of folders in the list of received files
  184. *
  185. * @param files Files to recursively fetch
  186. */
  187. private void fetchChildren(List<OCFile> files) {
  188. int i;
  189. for (i=0; i < files.size() && !mCancellation; i++) {
  190. OCFile newFile = files.get(i);
  191. if (newFile.isDirectory()) {
  192. fetchData(newFile.getRemotePath(), newFile.getFileId());
  193. }
  194. }
  195. if (mCancellation && i <files.size()) Log.d(TAG, "Leaving synchronization before synchronizing " + files.get(i).getRemotePath() + " because cancelation request");
  196. }
  197. /**
  198. * Sends a message to any application component interested in the progress of the synchronization.
  199. *
  200. * @param inProgress 'True' when the synchronization progress is not finished.
  201. * @param dirRemotePath Remote path of a folder that was just synchronized (with or without success)
  202. */
  203. private void sendStickyBroadcast(boolean inProgress, String dirRemotePath, RemoteOperationResult result) {
  204. Intent i = new Intent(FileSyncService.SYNC_MESSAGE);
  205. i.putExtra(FileSyncService.IN_PROGRESS, inProgress);
  206. i.putExtra(FileSyncService.ACCOUNT_NAME, getAccount().name);
  207. if (dirRemotePath != null) {
  208. i.putExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH, dirRemotePath);
  209. }
  210. if (result != null) {
  211. i.putExtra(FileSyncService.SYNC_RESULT, result);
  212. }
  213. getContext().sendStickyBroadcast(i);
  214. }
  215. /**
  216. * Notifies the user about a failed synchronization through the status notification bar
  217. */
  218. private void notifyFailedSynchronization() {
  219. Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_ticker), System.currentTimeMillis());
  220. notification.flags |= Notification.FLAG_AUTO_CANCEL;
  221. // TODO put something smart in the contentIntent below
  222. notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);
  223. notification.setLatestEventInfo(getContext().getApplicationContext(),
  224. getContext().getString(R.string.sync_fail_ticker),
  225. String.format(getContext().getString(R.string.sync_fail_content), getAccount().name),
  226. notification.contentIntent);
  227. ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_ticker, notification);
  228. }
  229. }