Merge two-way synch changes with synch-service refactoring for SSL warning
authorDavid A. Velasco <dvelasco@solidgear.es>
Thu, 25 Oct 2012 08:41:24 +0000 (10:41 +0200)
committerDavid A. Velasco <dvelasco@solidgear.es>
Thu, 25 Oct 2012 08:41:24 +0000 (10:41 +0200)
1  2 
src/com/owncloud/android/operations/SynchronizeFolderOperation.java
src/com/owncloud/android/syncadapter/FileSyncAdapter.java
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

index ad7df51,0000000..91de06e
mode 100644,000000..100644
--- /dev/null
@@@ -1,218 -1,0 +1,234 @@@
-                 
-                 
 +/* ownCloud Android client application
 + *   Copyright (C) 2012 Bartek Przybylski
 + *
 + *   This program is free software: you can redistribute it and/or modify
 + *   it under the terms of the GNU General Public License as published by
 + *   the Free Software Foundation, either version 3 of the License, or
 + *   (at your option) any later version.
 + *
 + *   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 General Public License
 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 + *
 + */
 +
 +package com.owncloud.android.operations;
 +
 +import java.util.List;
 +import java.util.Vector;
 +
 +import org.apache.http.HttpStatus;
 +import org.apache.jackrabbit.webdav.MultiStatus;
 +import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
 +
 +import android.accounts.Account;
 +import android.content.Context;
 +import android.content.Intent;
 +import android.util.Log;
 +
 +import com.owncloud.android.datamodel.DataStorageManager;
 +import com.owncloud.android.datamodel.OCFile;
 +import com.owncloud.android.files.services.FileDownloader;
++import com.owncloud.android.files.services.FileObserverService;
 +
 +import eu.alefzero.webdav.WebdavClient;
 +import eu.alefzero.webdav.WebdavEntry;
 +import eu.alefzero.webdav.WebdavUtils;
 +
 +
 +/**
 + * Remote operation performing the synchronization a the contents of a remote folder with the local database
 + * 
 + * @author David A. Velasco
 + */
 +public class SynchronizeFolderOperation extends RemoteOperation {
 +
 +    private static final String TAG = SynchronizeFolderOperation.class.getCanonicalName();
 +
 +    /** Remote folder to synchronize */
 +    private String mRemotePath;
 +    
 +    /** Timestamp for the synchronization in progress */
 +    private long mCurrentSyncTime;
 +    
 +    /** Id of the folder to synchronize in the local database */
 +    private long mParentId;
 +    
 +    /** Access to the local database */
 +    private DataStorageManager mStorageManager;
 +    
 +    /** Account where the file to synchronize belongs */
 +    private Account mAccount;
 +    
 +    /** Android context; necessary to send requests to the download service; maybe something to refactor */
 +    private Context mContext;
 +    
 +    /** Files and folders contained in the synchronized folder */
 +    private List<OCFile> mChildren;
 +    
 +    
 +    public SynchronizeFolderOperation(  String remotePath, 
 +                                        long currentSyncTime, 
 +                                        long parentId, 
 +                                        DataStorageManager dataStorageManager, 
 +                                        Account account, 
 +                                        Context context ) {
 +        mRemotePath = remotePath;
 +        mCurrentSyncTime = currentSyncTime;
 +        mParentId = parentId;
 +        mStorageManager = dataStorageManager;
 +        mAccount = account;
 +        mContext = context;
 +    }
 +    
 +    
 +    /**
 +     * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is complete.
 +     * 
 +     * @return      List of files and folders contained in the synchronized folder.
 +     */
 +    public List<OCFile> getChildren() {
 +        return mChildren;
 +    }
 +    
 +    
 +    @Override
 +    protected RemoteOperationResult run(WebdavClient client) {
 +        RemoteOperationResult result = null;
 +        
 +        // code before in FileSyncAdapter.fetchData
 +        PropFindMethod query = null;
 +        try {
 +            Log.d(TAG, "Synchronizing " + mAccount.name + ", fetching files in " + mRemotePath);
 +            
 +            // remote request 
 +            query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath));
 +            int status = client.executeMethod(query);
 +            
 +            // check and process response   - /// TODO take into account all the possible status per child-resource
 +            if (isMultiStatus(status)) { 
 +                MultiStatus resp = query.getResponseBodyAsMultiStatus();
 +            
 +                // synchronize properties of the parent folder, if necessary
 +                if (mParentId == DataStorageManager.ROOT_PARENT_ID) {
 +                    WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath());
 +                    OCFile parent = fillOCFile(we);
 +                    parent.setParentId(mParentId);
 +                    mStorageManager.saveFile(parent);
 +                    mParentId = parent.getFileId();
 +                }
 +                
 +                // read contents in folder
 +                List<OCFile> updatedFiles = new Vector<OCFile>(resp.getResponses().length - 1);
 +                for (int i = 1; i < resp.getResponses().length; ++i) {
 +                    WebdavEntry we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath());
 +                    OCFile file = fillOCFile(we);
 +                    file.setParentId(mParentId);
 +                    OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath());
 +                    if (oldFile != null) {
 +                        if (oldFile.keepInSync() && file.getModificationTimestamp() > oldFile.getModificationTimestamp()) {
++                            disableObservance(file);        // first disable observer so we won't get file upload right after download
 +                            requestContentDownload(file);
 +                        }
 +                        file.setKeepInSync(oldFile.keepInSync());
 +                    }
 +                
 +                    updatedFiles.add(file);
 +                }
-     
++                                
 +                // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed)
 +                mStorageManager.saveFiles(updatedFiles);
 +
 +                
 +                // removal of obsolete files
 +                mChildren = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId));
 +                OCFile file;
 +                String currentSavePath = FileDownloader.getSavePath(mAccount.name);
 +                for (int i=0; i < mChildren.size(); ) {
 +                    file = mChildren.get(i);
 +                    if (file.getLastSyncDate() != mCurrentSyncTime) {
 +                        Log.d(TAG, "removing file: " + file);
 +                        mStorageManager.removeFile(file, (file.isDown() && file.getStoragePath().startsWith(currentSavePath)));
 +                        mChildren.remove(i);
 +                    } else {
 +                        i++;
 +                    }
 +                }
 +                
 +            } else {
 +                client.exhaustResponse(query.getResponseBodyAsStream());
 +            }
 +            
 +            // prepare result object
 +            result = new RemoteOperationResult(isMultiStatus(status), status);
 +            Log.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage());
 +            
 +            
 +        } catch (Exception e) {
 +            result = new RemoteOperationResult(e);
 +            Log.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage(), result.getException());
 +
 +        } finally {
 +            if (query != null)
 +                query.releaseConnection();  // let the connection available for other methods
 +        }
 +        
 +        return result;
 +    }
 +    
++
 +    public boolean isMultiStatus(int status) {
 +        return (status == HttpStatus.SC_MULTI_STATUS); 
 +    }
 +
 +
 +    /**
 +     * Creates and populates a new {@link OCFile} object with the data read from the server.
 +     * 
 +     * @param we        WebDAV entry read from the server for a WebDAV resource (remote file or folder).
 +     * @return          New OCFile instance representing the remote resource described by we.
 +     */
 +    private OCFile fillOCFile(WebdavEntry we) {
 +        OCFile file = new OCFile(we.decodedPath());
 +        file.setCreationTimestamp(we.createTimestamp());
 +        file.setFileLength(we.contentLength());
 +        file.setMimetype(we.contentType());
 +        file.setModificationTimestamp(we.modifiedTimesamp());
 +        file.setLastSyncDate(mCurrentSyncTime);
 +        return file;
 +    }
 +    
 +    
++    /**
++     * Request to stop the observance of local updates for a file.  
++     * 
++     * @param file      OCFile representing the remote file to stop to monitor for local updates
++     */
++    private void disableObservance(OCFile file) {
++        Log.d(TAG, "Disabling observation of remote file" + file.getRemotePath());
++        Intent intent = new Intent(mContext, FileObserverService.class);
++        intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_ADD_DOWNLOADING_FILE);
++        intent.putExtra(FileObserverService.KEY_CMD_ARG, file.getRemotePath());
++        mContext.startService(intent);
++        
++    }
++
++
 +    /** 
 +     * Requests a download to the file download service
 +     * 
 +     * @param   file    OCFile representing the remote file to download
 +     */
 +    private void requestContentDownload(OCFile file) {
 +        Intent intent = new Intent(mContext, FileDownloader.class);
 +        intent.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
 +        intent.putExtra(FileDownloader.EXTRA_FILE, file);
 +        mContext.startService(intent);
 +    }
 +
 +
 +}
  package com.owncloud.android.syncadapter;\r
  \r
  import java.io.IOException;\r
 +import java.net.UnknownHostException;\r
  import java.util.List;\r
 -import java.util.Vector;\r
  \r
 -import org.apache.http.HttpStatus;\r
  import org.apache.jackrabbit.webdav.DavException;\r
 -import org.apache.jackrabbit.webdav.MultiStatus;\r
 -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;\r
 -import org.json.JSONObject;\r
  \r
 -import com.owncloud.android.AccountUtils;\r
  import com.owncloud.android.R;\r
 -import com.owncloud.android.authenticator.AccountAuthenticator;\r
 +import com.owncloud.android.datamodel.DataStorageManager;\r
  import com.owncloud.android.datamodel.FileDataStorageManager;\r
  import com.owncloud.android.datamodel.OCFile;\r
++//<<<<<<< HEAD
 +import com.owncloud.android.operations.RemoteOperationResult;\r
 +import com.owncloud.android.operations.SynchronizeFolderOperation;\r
 +import com.owncloud.android.operations.UpdateOCVersionOperation;\r
++/*=======
+ import com.owncloud.android.files.services.FileDownloader;\r
+ import com.owncloud.android.files.services.FileObserverService;\r
+ import com.owncloud.android.utils.OwnCloudVersion;\r
++>>>>>>> origin/master*/
  \r
  import android.accounts.Account;\r
  import android.app.Notification;\r
@@@ -43,6 -48,8 +49,6 @@@ import android.content.Intent
  import android.content.SyncResult;\r
  import android.os.Bundle;\r
  import android.util.Log;\r
 -import eu.alefzero.webdav.WebdavEntry;\r
 -import eu.alefzero.webdav.WebdavUtils;\r
  \r
  /**\r
   * SyncAdapter implementation for syncing sample SyncAdapter contacts to the\r
   */\r
  public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {\r
  \r
 -    private final static String TAG = "FileSyncAdapter"; \r
 -    \r
 -    /*  Commented code for ugly performance tests\r
 -    private final static int MAX_DELAYS = 100;\r
 -    private static long[] mResponseDelays = new long[MAX_DELAYS]; \r
 -    private static long[] mSaveDelays = new long[MAX_DELAYS];\r
 -    private int mDelaysIndex = 0;\r
 -    private int mDelaysCount = 0;\r
 -    */\r
 +    private final static String TAG = "FileSyncAdapter";\r
 +\r
 +    /** \r
 +     * Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation\r
 +     */\r
 +    private static final int MAX_FAILED_RESULTS = 3; \r
      \r
      private long mCurrentSyncTime;\r
      private boolean mCancellation;\r
      private boolean mIsManualSync;\r
 -    private boolean mRightSync;\r
 +    private int mFailedResultsCounter;    \r
 +    private RemoteOperationResult mLastFailedResult;\r
 +    private SyncResult mSyncResult;\r
      \r
      public FileSyncAdapter(Context context, boolean autoInitialize) {\r
          super(context, autoInitialize);\r
      }\r
  \r
 +    /**\r
 +     * {@inheritDoc}\r
 +     */\r
      @Override\r
      public synchronized void onPerformSync(Account account, Bundle extras,\r
              String authority, ContentProviderClient provider,\r
  \r
          mCancellation = false;\r
          mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);\r
 -        mRightSync = true;\r
 +        mFailedResultsCounter = 0;\r
 +        mLastFailedResult = null;\r
 +        mSyncResult = syncResult;\r
          \r
          this.setAccount(account);\r
          this.setContentProvider(provider);\r
          this.setStorageManager(new FileDataStorageManager(account, getContentProvider()));\r
 +        try {\r
 +            this.initClientForCurrentAccount();\r
 +        } catch (UnknownHostException e) {\r
 +            /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again\r
 +            mSyncResult.tooManyRetries = true;\r
 +            notifyFailedSynchronization();\r
 +            return;\r
 +        }\r
          \r
 -        /*  Commented code for ugly performance tests\r
 -        mDelaysIndex = 0;\r
 -        mDelaysCount = 0;\r
 -        */\r
 -\r
 -        Log.d(TAG, "syncing owncloud account " + account.name);\r
 -\r
 -        sendStickyBroadcast(true, null);  // message to signal the start to the UI\r
 +        Log.d(TAG, "Synchronization of ownCloud account " + account.name + " starting");\r
 +        sendStickyBroadcast(true, null, null);  // message to signal the start of the synchronization to the UI\r
          \r
 -        updateOCVersion();\r
 -\r
 -        String uri = getUri().toString();\r
 -        PropFindMethod query = null;\r
          try {\r
 +            updateOCVersion();\r
              mCurrentSyncTime = System.currentTimeMillis();\r
 -            query = new PropFindMethod(uri + "/");\r
 -            int status = getClient().executeMethod(query);\r
 -            if (status != HttpStatus.SC_UNAUTHORIZED) {\r
 -                MultiStatus resp = query.getResponseBodyAsMultiStatus();\r
 -\r
 -                if (resp.getResponses().length > 0) {\r
 -                    WebdavEntry we = new WebdavEntry(resp.getResponses()[0], getUri().getPath());\r
 -                    OCFile file = fillOCFile(we);\r
 -                    file.setParentId(0);\r
 -                    getStorageManager().saveFile(file);\r
 -                    if (!mCancellation) {\r
 -                        fetchData(uri, syncResult, file.getFileId());\r
 -                    }\r
 -                }\r
 -\r
 +            if (!mCancellation) {\r
 +                fetchData(OCFile.PATH_SEPARATOR, DataStorageManager.ROOT_PARENT_ID);\r
 +                \r
              } else {\r
 -                syncResult.stats.numAuthExceptions++;\r
 +                Log.d(TAG, "Leaving synchronization before any remote request due to cancellation was requested");\r
              }\r
 -        } catch (IOException e) {\r
 -            syncResult.stats.numIoExceptions++;\r
 -            logException(e, uri + "/");\r
 -            \r
 -        } catch (DavException e) {\r
 -            syncResult.stats.numParseExceptions++;\r
 -            logException(e, uri + "/");\r
              \r
 -        } catch (Exception e) {\r
 -            // TODO something smart with syncresult\r
 -            logException(e, uri + "/");\r
 -            mRightSync = false;\r
              \r
          } finally {\r
 -            if (query != null)\r
 -                query.releaseConnection();  // let the connection available for other methods\r
 -            mRightSync &= (syncResult.stats.numIoExceptions == 0 && syncResult.stats.numAuthExceptions == 0 && syncResult.stats.numParseExceptions == 0);\r
 -            if (!mRightSync && mIsManualSync) {\r
 +            // it's important making this although very unexpected errors occur; that's the reason for the finally\r
 +            \r
 +            if (mFailedResultsCounter > 0 && mIsManualSync) {\r
                  /// don't let the system synchronization manager retries MANUAL synchronizations\r
                  //      (be careful: "MANUAL" currently includes the synchronization requested when a new account is created and when the user changes the current account)\r
 -                syncResult.tooManyRetries = true;\r
 +                mSyncResult.tooManyRetries = true;\r
                  \r
                  /// notify the user about the failure of MANUAL synchronization\r
 -                Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_ticker), System.currentTimeMillis());\r
 -                notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
 -                // TODO put something smart in the contentIntent below\r
 -                notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);\r
 -                notification.setLatestEventInfo(getContext().getApplicationContext(), \r
 -                                                getContext().getString(R.string.sync_fail_ticker), \r
 -                                                String.format(getContext().getString(R.string.sync_fail_content), account.name), \r
 -                                                notification.contentIntent);\r
 -                ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_ticker, notification);\r
 +                notifyFailedSynchronization();\r
              }\r
 -            sendStickyBroadcast(false, null);        // message to signal the end to the UI\r
 +            sendStickyBroadcast(false, null, mLastFailedResult);        // message to signal the end to the UI\r
          }\r
          \r
 -        /*  Commented code for ugly performance tests\r
 -        long sum = 0, mean = 0, max = 0, min = Long.MAX_VALUE;\r
 -        for (int i=0; i<MAX_DELAYS && i<mDelaysCount; i++) {\r
 -            sum += mResponseDelays[i];\r
 -            max = Math.max(max, mResponseDelays[i]);\r
 -            min = Math.min(min, mResponseDelays[i]);\r
 -        }\r
 -        mean = sum / mDelaysCount;\r
 -        Log.e(TAG, "SYNC STATS - response: mean time = " + mean + " ; max time = " + max + " ; min time = " + min);\r
 -        \r
 -        sum = 0; max = 0; min = Long.MAX_VALUE;\r
 -        for (int i=0; i<MAX_DELAYS && i<mDelaysCount; i++) {\r
 -            sum += mSaveDelays[i];\r
 -            max = Math.max(max, mSaveDelays[i]);\r
 -            min = Math.min(min, mSaveDelays[i]);\r
 +    }\r
 +    \r
 +    \r
 +    \r
 +    /**\r
 +     * Called by system SyncManager when a synchronization is required to be cancelled.\r
 +     * \r
 +     * Sets the mCancellation flag to 'true'. THe synchronization will be stopped when before a new folder is fetched. Data of the last folder\r
 +     * fetched will be still saved in the database. See onPerformSync implementation.\r
 +     */\r
 +    @Override\r
 +    public void onSyncCanceled() {\r
 +        Log.d(TAG, "Synchronization of " + getAccount().name + " has been requested to cancel");\r
 +        mCancellation = true;\r
 +        super.onSyncCanceled();\r
 +    }\r
 +    \r
 +    \r
 +    /**\r
 +     * Updates the locally stored version value of the ownCloud server\r
 +     */\r
 +    private void updateOCVersion() {\r
 +        UpdateOCVersionOperation update = new UpdateOCVersionOperation(getAccount(), getContext());\r
 +        RemoteOperationResult result = update.execute(getClient());\r
 +        if (!result.isSuccess()) {\r
 +            mLastFailedResult = result; \r
          }\r
 -        mean = sum / mDelaysCount;\r
 -        Log.e(TAG, "SYNC STATS - save:     mean time = " + mean + " ; max time = " + max + " ; min time = " + min);\r
 -        Log.e(TAG, "SYNC STATS - folders measured: " + mDelaysCount);\r
 -        */\r
 -        \r
      }\r
  \r
 -    private void fetchData(String uri, SyncResult syncResult, long parentId) {\r
 -        PropFindMethod query = null;\r
 -        Vector<OCFile> children = null;\r
 -        try {\r
 -            Log.d(TAG, "fetching " + uri);\r
 -            \r
 -            // remote request \r
 -            query = new PropFindMethod(uri);\r
 -            /*  Commented code for ugly performance tests\r
 -            long responseDelay = System.currentTimeMillis();\r
 -            */\r
 -            int status = getClient().executeMethod(query);\r
 -            /*  Commented code for ugly performance tests\r
 -            responseDelay = System.currentTimeMillis() - responseDelay;\r
 -            Log.e(TAG, "syncing: RESPONSE TIME for " + uri + " contents, " + responseDelay + "ms");\r
 -            */\r
 -            if (status != HttpStatus.SC_UNAUTHORIZED) {\r
 -                MultiStatus resp = query.getResponseBodyAsMultiStatus();\r
 +    \r
 +    \r
 +    /**\r
 +     * Synchronize the properties of files and folders contained in a remote folder given by remotePath.\r
 +     * \r
 +     * @param remotePath        Remote path to the folder to synchronize.\r
 +     * @param parentId          Database Id of the folder to synchronize.\r
 +     */\r
 +    private void fetchData(String remotePath, long parentId) {\r
 +        \r
 +        if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult))\r
 +            return;\r
 +        \r
 +        // perform folder synchronization\r
 +        SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation(  remotePath, \r
 +                                                                                    mCurrentSyncTime, \r
 +                                                                                    parentId, \r
 +                                                                                    getStorageManager(), \r
 +                                                                                    getAccount(), \r
 +                                                                                    getContext()\r
 +                                                                                  );\r
 +        RemoteOperationResult result = synchFolderOp.execute(getClient());\r
 +        \r
 +        \r
 +        // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess\r
 +        sendStickyBroadcast(true, remotePath, null);\r
 +        \r
 +        if (result.isSuccess()) {\r
 +            // synchronize children folders \r
 +            List<OCFile> children = synchFolderOp.getChildren();\r
 +            fetchChildren(children);    // beware of the 'hidden' recursion here!\r
              \r
++//<<<<<<< HEAD
 +        } else {\r
 +            if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED) {\r
 +                mSyncResult.stats.numAuthExceptions++;\r
 +                \r
 +            } else if (result.getException() instanceof DavException) {\r
 +                mSyncResult.stats.numParseExceptions++;\r
 +                \r
 +            } else if (result.getException() instanceof IOException) { \r
 +                mSyncResult.stats.numIoExceptions++;\r
++/*=======
+                 // insertion or update of files\r
+                 List<OCFile> updatedFiles = new Vector<OCFile>(resp.getResponses().length - 1);\r
+                 for (int i = 1; i < resp.getResponses().length; ++i) {\r
+                     WebdavEntry we = new WebdavEntry(resp.getResponses()[i], getUri().getPath());\r
+                     OCFile file = fillOCFile(we);\r
+                     file.setParentId(parentId);\r
+                     if (getStorageManager().getFileByPath(file.getRemotePath()) != null &&\r
+                             getStorageManager().getFileByPath(file.getRemotePath()).keepInSync() &&\r
+                             file.getModificationTimestamp() > getStorageManager().getFileByPath(file.getRemotePath())\r
+                                                                          .getModificationTimestamp()) {\r
+                         // first disable observer so we won't get file upload right after download\r
+                         Log.d(TAG, "Disabling observation of remote file" + file.getRemotePath());\r
+                         Intent intent = new Intent(getContext(), FileObserverService.class);\r
+                         intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_ADD_DOWNLOADING_FILE);\r
+                         intent.putExtra(FileObserverService.KEY_CMD_ARG, file.getRemotePath());\r
+                         getContext().startService(intent);\r
+                         intent = new Intent(this.getContext(), FileDownloader.class);\r
+                         intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount());\r
+                         intent.putExtra(FileDownloader.EXTRA_FILE, file);\r
+                         file.setKeepInSync(true);\r
+                         getContext().startService(intent);\r
+                     }\r
+                     if (getStorageManager().getFileByPath(file.getRemotePath()) != null)\r
+                         file.setKeepInSync(getStorageManager().getFileByPath(file.getRemotePath()).keepInSync());\r
++>>>>>>> origin/master*/
                  \r
 -                    // Log.v(TAG, "adding file: " + file);\r
 -                    updatedFiles.add(file);\r
 -                }\r
 -                /*  Commented code for ugly performance tests\r
 -                long saveDelay = System.currentTimeMillis();\r
 -                 */            \r
 -                getStorageManager().saveFiles(updatedFiles);    // all "at once" ; trying to get a best performance in database update\r
 -                /*  Commented code for ugly performance tests\r
 -                saveDelay = System.currentTimeMillis() - saveDelay;\r
 -                Log.e(TAG, "syncing: SAVE TIME for " + uri + " contents, " + mSaveDelays[mDelaysIndex] + "ms");\r
 -                 */\r
 -            \r
 -                // removal of obsolete files\r
 -                 children = getStorageManager().getDirectoryContent(\r
 -                        getStorageManager().getFileById(parentId));\r
 -                OCFile file;\r
 -                String currentSavePath = FileDownloader.getSavePath(getAccount().name);\r
 -                for (int i=0; i < children.size(); ) {\r
 -                    file = children.get(i);\r
 -                    if (file.getLastSyncDate() != mCurrentSyncTime) {\r
 -                        Log.v(TAG, "removing file: " + file);\r
 -                        getStorageManager().removeFile(file, (file.isDown() && file.getStoragePath().startsWith(currentSavePath)));\r
 -                        children.remove(i);\r
 -                    } else {\r
 -                        i++;\r
 -                    }\r
 -                }\r
 -            \r
 -            } else {\r
 -                syncResult.stats.numAuthExceptions++;\r
              }\r
 -        } catch (IOException e) {\r
 -            syncResult.stats.numIoExceptions++;\r
 -            logException(e, uri);\r
 -            \r
 -        } catch (DavException e) {\r
 -            syncResult.stats.numParseExceptions++;\r
 -            logException(e, uri);\r
 +            mFailedResultsCounter++;\r
 +            mLastFailedResult = result;\r
 +        }\r
              \r
 -        } catch (Exception e) {\r
 -            // TODO something smart with syncresult\r
 -            mRightSync = false;\r
 -            logException(e, uri);\r
 -\r
 -        } finally {\r
 -            if (query != null)\r
 -                query.releaseConnection();  // let the connection available for other methods\r
 +    }\r
  \r
 -            // synchronized folder -> notice to UI\r
 -            sendStickyBroadcast(true, getStorageManager().getFileById(parentId).getRemotePath());\r
 +    /**\r
 +     * Checks if a failed result should terminate the synchronization process immediately, according to\r
 +     * OUR OWN POLICY\r
 +     * \r
 +     * @param   failedResult        Remote operation result to check.\r
 +     * @return                      'True' if the result should immediately finish the synchronization\r
 +     */\r
 +    private boolean isFinisher(RemoteOperationResult failedResult) {\r
 +        if  (failedResult != null) {\r
 +            RemoteOperationResult.ResultCode code = failedResult.getCode();\r
 +            return (code.equals(RemoteOperationResult.ResultCode.SSL_ERROR) ||\r
 +                    code.equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) ||\r
 +                    code.equals(RemoteOperationResult.ResultCode.BAD_OC_VERSION) ||\r
 +                    code.equals(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED));\r
          }\r
 -        \r
 -        \r
 -        fetchChildren(children, syncResult);\r
 -        if (mCancellation) Log.d(TAG, "Leaving " + uri + " because cancelation request");\r
 -        \r
 -        \r
 -        /*  Commented code for ugly performance tests\r
 -        mResponseDelays[mDelaysIndex] = responseDelay;\r
 -        mSaveDelays[mDelaysIndex] = saveDelay;\r
 -        mDelaysCount++;\r
 -        mDelaysIndex++;\r
 -        if (mDelaysIndex >= MAX_DELAYS)\r
 -            mDelaysIndex = 0;\r
 -         */\r
 -        \r
 +        return false;\r
      }\r
  \r
      /**\r
       * Synchronize data of folders in the list of received files\r
       * \r
       * @param files         Files to recursively fetch \r
 -     * @param syncResult    Updated object to provide results to the Synchronization Manager\r
       */\r
 -    private void fetchChildren(Vector<OCFile> files, SyncResult syncResult) {\r
 -        for (int i=0; i < files.size() && !mCancellation; i++) {\r
 +    private void fetchChildren(List<OCFile> files) {\r
 +        int i;\r
 +        for (i=0; i < files.size() && !mCancellation; i++) {\r
              OCFile newFile = files.get(i);\r
 -            if (newFile.getMimetype().equals("DIR")) {\r
 -                fetchData(getUri().toString() + WebdavUtils.encodePath(newFile.getRemotePath()), syncResult, newFile.getFileId());\r
 +            if (newFile.isDirectory()) {\r
 +                fetchData(newFile.getRemotePath(), newFile.getFileId());\r
              }\r
          }\r
 +        if (mCancellation && i <files.size()) Log.d(TAG, "Leaving synchronization before synchronizing " + files.get(i).getRemotePath() + " because cancelation request");\r
      }\r
  \r
      \r
 -    private OCFile fillOCFile(WebdavEntry we) {\r
 -        OCFile file = new OCFile(we.decodedPath());\r
 -        file.setCreationTimestamp(we.createTimestamp());\r
 -        file.setFileLength(we.contentLength());\r
 -        file.setMimetype(we.contentType());\r
 -        file.setModificationTimestamp(we.modifiedTimesamp());\r
 -        file.setLastSyncDate(mCurrentSyncTime);\r
 -        return file;\r
 -    }\r
 -    \r
 -    \r
 -    private void sendStickyBroadcast(boolean inProgress, String dirRemotePath) {\r
 +    /**\r
 +     * Sends a message to any application component interested in the progress of the synchronization.\r
 +     * \r
 +     * @param inProgress        'True' when the synchronization progress is not finished.\r
 +     * @param dirRemotePath     Remote path of a folder that was just synchronized (with or without success)\r
 +     */\r
 +    private void sendStickyBroadcast(boolean inProgress, String dirRemotePath, RemoteOperationResult result) {\r
          Intent i = new Intent(FileSyncService.SYNC_MESSAGE);\r
          i.putExtra(FileSyncService.IN_PROGRESS, inProgress);\r
          i.putExtra(FileSyncService.ACCOUNT_NAME, getAccount().name);\r
          if (dirRemotePath != null) {\r
              i.putExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH, dirRemotePath);\r
          }\r
 +        if (result != null) {\r
 +            i.putExtra(FileSyncService.SYNC_RESULT, result);\r
 +        }\r
          getContext().sendStickyBroadcast(i);\r
      }\r
 +\r
 +    \r
      \r
      /**\r
 -     * Called by system SyncManager when a synchronization is required to be cancelled.\r
 -     * \r
 -     * Sets the mCancellation flag to 'true'. THe synchronization will be stopped when before a new folder is fetched. Data of the last folder\r
 -     * fetched will be still saved in the database. See onPerformSync implementation.\r
 +     * Notifies the user about a failed synchronization through the status notification bar \r
       */\r
 -    @Override\r
 -    public void onSyncCanceled() {\r
 -        Log.d(TAG, "Synchronization of " + getAccount().name + " has been requested to cancel");\r
 -        mCancellation = true;\r
 -        super.onSyncCanceled();\r
 +    private void notifyFailedSynchronization() {\r
 +        Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_ticker), System.currentTimeMillis());\r
 +        notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
 +        // TODO put something smart in the contentIntent below\r
 +        notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);\r
 +        notification.setLatestEventInfo(getContext().getApplicationContext(), \r
 +                                        getContext().getString(R.string.sync_fail_ticker), \r
 +                                        String.format(getContext().getString(R.string.sync_fail_content), getAccount().name), \r
 +                                        notification.contentIntent);\r
 +        ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_ticker, notification);\r
      }\r
  \r
      \r
 -    /**\r
 -     * Logs an exception triggered in a synchronization request. \r
 -     * \r
 -     * @param e       Caught exception.\r
 -     * @param uri     Uri to the remote directory that was fetched when the synchronization failed \r
 -     */\r
 -    private void logException(Exception e, String uri) {\r
 -        if (e instanceof IOException) {\r
 -            Log.e(TAG, "Unrecovered transport exception while synchronizing " + uri + " at " + getAccount().name, e);\r
  \r
 -        } else if (e instanceof DavException) {\r
 -            Log.e(TAG, "Unexpected WebDAV exception while synchronizing " + uri + " at " + getAccount().name, e);\r
 -\r
 -        } else {\r
 -            Log.e(TAG, "Unexpected exception while synchronizing " + uri  + " at " + getAccount().name, e);\r
 -        }\r
 -    }\r
 -\r
 -    private void updateOCVersion() {\r
 -        String statUrl = getAccountManager().getUserData(getAccount(), AccountAuthenticator.KEY_OC_BASE_URL);\r
 -        statUrl += AccountUtils.STATUS_PATH;\r
 -        \r
 -        try {\r
 -            String result = getClient().getResultAsString(statUrl);\r
 -            if (result != null) {\r
 -                try {\r
 -                    JSONObject json = new JSONObject(result);\r
 -                    if (json != null && json.getString("version") != null) {\r
 -                        OwnCloudVersion ocver = new OwnCloudVersion(json.getString("version"));\r
 -                        if (ocver.isVersionValid()) {\r
 -                            getAccountManager().setUserData(getAccount(), AccountAuthenticator.KEY_OC_VERSION, ocver.toString());\r
 -                            Log.d(TAG, "Got new OC version " + ocver.toString());\r
 -                        } else {\r
 -                            Log.w(TAG, "Invalid version number received from server: " + json.getString("version"));\r
 -                        }\r
 -                    }\r
 -                } catch (Throwable e) {\r
 -                    Log.w(TAG, "Couldn't parse version response", e);\r
 -                }\r
 -            } else {\r
 -                Log.w(TAG, "Problem while getting ocversion from server");\r
 -            }\r
 -        } catch (Exception e) {\r
 -            Log.e(TAG, "Problem getting response from server", e);\r
 -        }\r
 -    }\r
  }\r
@@@ -68,13 -68,11 +68,14 @@@ import com.owncloud.android.datamodel.F
  import com.owncloud.android.datamodel.OCFile;\r
  import com.owncloud.android.files.services.FileDownloader;\r
  import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;\r
+ import com.owncloud.android.files.services.FileObserverService;\r
  import com.owncloud.android.files.services.FileUploader;\r
  import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;\r
  import com.owncloud.android.network.OwnCloudClientUtils;\r
 +import com.owncloud.android.operations.RemoteOperationResult;\r
  import com.owncloud.android.syncadapter.FileSyncService;\r
 +import com.owncloud.android.ui.dialog.SslValidatorDialog;\r
 +import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;\r
  import com.owncloud.android.ui.fragment.FileDetailFragment;\r
  import com.owncloud.android.ui.fragment.OCFileListFragment;\r
  \r
@@@ -89,7 -87,7 +90,7 @@@ import eu.alefzero.webdav.WebdavClient
   */\r
  \r
  public class FileDisplayActivity extends SherlockFragmentActivity implements\r
 -    OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener {\r
 +    OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener {\r
      \r
      private ArrayAdapter<String> mDirectories;\r
      private OCFile mCurrentDir = null;\r
      private FileDownloaderBinder mDownloaderBinder = null;\r
      private FileUploaderBinder mUploaderBinder = null;\r
      private ServiceConnection mDownloadConnection = null, mUploadConnection = null;\r
 +    private RemoteOperationResult mLastSslUntrustedServerResult = null;\r
      \r
      private OCFileListFragment mFileList;\r
      \r
      private static final int DIALOG_ABOUT_APP = 2;\r
      public static final int DIALOG_SHORT_WAIT = 3;\r
      private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 4;\r
 +    private static final int DIALOG_SSL_VALIDATOR = 5;\r
 +    private static final int DIALOG_CERT_NOT_SAVED = 6;\r
 +\r
      \r
      private static final int ACTION_SELECT_CONTENT_FROM_APPS = 1;\r
      private static final int ACTION_SELECT_MULTIPLE_FILES = 2;\r
          }\r
  \r
          // file observer\r
-         /*Intent observer_intent = new Intent(this, FileObserverService.class);\r
+         Intent observer_intent = new Intent(this, FileObserverService.class);\r
          observer_intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_INIT_OBSERVED_LIST);\r
          startService(observer_intent);\r
-         */\r
+         \r
              \r
          /// USER INTERFACE\r
          requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);\r
                  break;\r
              }\r
              case R.id.startSync: {\r
 -                ContentResolver.cancelSync(null, AccountAuthenticator.AUTH_TOKEN_TYPE);   // cancel the current synchronizations of any ownCloud account\r
 -                Bundle bundle = new Bundle();\r
 -                bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);\r
 -                ContentResolver.requestSync(\r
 -                        AccountUtils.getCurrentOwnCloudAccount(this),\r
 -                        AccountAuthenticator.AUTH_TOKEN_TYPE, bundle);\r
 +                startSynchronization();\r
                  break;\r
              }\r
              case R.id.action_upload: {\r
          return retval;\r
      }\r
  \r
 +    private void startSynchronization() {\r
 +        ContentResolver.cancelSync(null, AccountAuthenticator.AUTH_TOKEN_TYPE);   // cancel the current synchronizations of any ownCloud account\r
 +        Bundle bundle = new Bundle();\r
 +        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);\r
 +        ContentResolver.requestSync(\r
 +                AccountUtils.getCurrentOwnCloudAccount(this),\r
 +                AccountAuthenticator.AUTH_TOKEN_TYPE, bundle);\r
 +    }\r
 +\r
 +\r
      @Override\r
      public boolean onNavigationItemSelected(int itemPosition, long itemId) {\r
          int i = itemPosition;\r
          Log.d(getClass().toString(), "onPause() end");\r
      }\r
  \r
 +    \r
 +    @Override\r
 +    protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {\r
 +        if (id == DIALOG_SSL_VALIDATOR && mLastSslUntrustedServerResult != null) {\r
 +            ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult);\r
 +        }\r
 +    }\r
 +\r
 +    \r
      @Override\r
      protected Dialog onCreateDialog(int id) {\r
          Dialog dialog = null;\r
              dialog = builder.create();\r
              break;\r
          }\r
 +        case DIALOG_SSL_VALIDATOR: {\r
 +            dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this);\r
 +            break;\r
 +        }\r
 +        case DIALOG_CERT_NOT_SAVED: {\r
 +            builder = new AlertDialog.Builder(this);\r
 +            builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved));\r
 +            builder.setCancelable(false);\r
 +            builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {\r
 +                    @Override\r
 +                    public void onClick(DialogInterface dialog, int which) {\r
 +                        dialog.dismiss();\r
 +                    };\r
 +                });\r
 +            dialog = builder.create();\r
 +            break;\r
 +        }\r
          default:\r
              dialog = null;\r
          }\r
      }\r
  \r
      private class SyncBroadcastReceiver extends BroadcastReceiver {\r
 +\r
          /**\r
           * {@link BroadcastReceiver} to enable syncing feedback in UI\r
           */\r
                  setSupportProgressBarIndeterminateVisibility(inProgress);\r
                  \r
              }\r
 +            \r
 +            RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT);\r
 +            if (synchResult != null) {\r
 +                if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) {\r
 +                    mLastSslUntrustedServerResult = synchResult;\r
 +                    showDialog(DIALOG_SSL_VALIDATOR); \r
 +                }\r
 +            }\r
          }\r
      }\r
      \r
      }\r
  \r
  \r
 +    @Override\r
 +    public void onSavedCertificate() {\r
 +        startSynchronization();                \r
 +    }\r
 +\r
 +\r
 +    @Override\r
 +    public void onFailedSavingCertificate() {\r
 +        showDialog(DIALOG_CERT_NOT_SAVED);\r
 +    }\r
 +\r
 +\r
  }\r