Browse Source

Merge pull request #803 from owncloud/download_folder_cancel

Add cancellation of folder synchronizations to OperationsService.
David A. Velasco 10 years ago
parent
commit
47559478ba

+ 25 - 14
src/com/owncloud/android/files/FileOperationsHelper.java

@@ -278,23 +278,34 @@ public class FileOperationsHelper {
         mFileActivity.showLoadingDialog();
     }
 
-    
+    /**
+     * Cancel the transference in downloads (files/folders) and file uploads
+     * @param file OCFile
+     */
     public void cancelTransference(OCFile file) {
         Account account = mFileActivity.getAccount();
-        FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
-        FileUploaderBinder uploaderBinder =  mFileActivity.getFileUploaderBinder();
-        if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) {
-            // Remove etag for parent, if file is a keep_in_sync
-            if (file.keepInSync()) {
-               OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
-               parent.setEtag("");
-               mFileActivity.getStorageManager().saveFile(parent);
+        if (!file.isFolder()) {
+            FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
+            FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
+            if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) {
+                // Remove etag for parent, if file is a keep_in_sync
+                if (file.keepInSync()) {
+                    OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
+                    parent.setEtag("");
+                    mFileActivity.getStorageManager().saveFile(parent);
+                }
+
+                downloaderBinder.cancel(account, file);
+
+            } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
+                uploaderBinder.cancel(account, file);
             }
-            
-            downloaderBinder.cancel(account, file);
-            
-        } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
-            uploaderBinder.cancel(account, file);
+        } else {
+            Intent intent = new Intent(mFileActivity, OperationsService.class);
+            intent.setAction(OperationsService.ACTION_CANCEL_SYNC_FOLDER);
+            intent.putExtra(OperationsService.EXTRA_ACCOUNT, account);
+            intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+            mFileActivity.startService(intent);
         }
     }
 

+ 18 - 0
src/com/owncloud/android/operations/SynchronizeFolderOperation.java

@@ -26,6 +26,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.operations.OperationCancelledException;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -48,6 +49,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Vector;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 //import android.support.v4.content.LocalBroadcastManager;
 
@@ -96,6 +98,7 @@ public class SynchronizeFolderOperation extends SyncOperation {
 
     /** 'True' means that the remote folder changed and should be fetched */
     private boolean mRemoteFolderChanged;
+    private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
 
 
     /**
@@ -150,6 +153,13 @@ public class SynchronizeFolderOperation extends SyncOperation {
         mConflictsFound = 0;
         mForgottenLocalFiles.clear();
 
+        /// perform the download
+        synchronized(mCancellationRequested) {
+            if (mCancellationRequested.get()) {
+                return new RemoteOperationResult(new OperationCancelledException());
+            }
+        }
+
         result = checkForChanges(client);
 
         if (result.isSuccess()) {
@@ -514,6 +524,14 @@ public class SynchronizeFolderOperation extends SyncOperation {
         mContext.startService(i);
     }
 
+    /**
+     * Cancel operation
+     */
+    public void cancel(){
+        // WIP Cancel the sync operation
+        mCancellationRequested.set(true);
+    }
+
     public boolean getRemoteFolderChanged() {
         return mRemoteFolderChanged;
     }

+ 136 - 13
src/com/owncloud/android/services/OperationsService.java

@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentMap;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
@@ -101,6 +102,7 @@ public class OperationsService extends Service {
     public static final String ACTION_CREATE_FOLDER = "CREATE_FOLDER";
     public static final String ACTION_SYNC_FILE = "SYNC_FILE";
     public static final String ACTION_SYNC_FOLDER = "SYNC_FOLDER";  // for the moment, just to download
+    public static final String ACTION_CANCEL_SYNC_FOLDER = "CANCEL_SYNC_FOLDER";  // for the moment, just to download
     public static final String ACTION_MOVE_FILE = "MOVE_FILE";
     
     public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
@@ -133,7 +135,7 @@ public class OperationsService extends Service {
     private ServiceHandler mOperationsHandler;
     private OperationsServiceBinder mOperationsBinder;
     
-    private ServiceHandler mSyncFolderHandler;
+    private SyncFolderHandler mSyncFolderHandler;
     
     /**
      * Service initialization
@@ -150,7 +152,7 @@ public class OperationsService extends Service {
         /// Separated worker thread for download of folders (WIP)
         thread = new HandlerThread("Syncfolder thread", Process.THREAD_PRIORITY_BACKGROUND);
         thread.start();
-        mSyncFolderHandler = new ServiceHandler(thread.getLooper(), this);
+        mSyncFolderHandler = new SyncFolderHandler(thread.getLooper(), this);
     }
 
     
@@ -162,17 +164,36 @@ public class OperationsService extends Service {
      */
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
+        // WIP: for the moment, only SYNC_FOLDER and CANCEL_SYNC_FOLDER is expected here;
+        // the rest of the operations are requested through the Binder
         if (ACTION_SYNC_FOLDER.equals(intent.getAction())) {
-            // WIP: for the moment, only SYNC_FOLDER is expected here; the rest of the operations are requested through
-            // the Binder
+            if (!intent.hasExtra(EXTRA_ACCOUNT) || !intent.hasExtra(EXTRA_REMOTE_PATH)) {
+                Log_OC.e(TAG, "Not enough information provided in intent");
+                return START_NOT_STICKY;
+            }
+            Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+            String remotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
+
+            Pair<Account, String> itemSyncKey =  new Pair<Account , String>(account, remotePath);
+
             Pair<Target, RemoteOperation> itemToQueue = newOperation(intent);
             if (itemToQueue != null) {
-                mSyncFolderHandler.mPendingOperations.add(itemToQueue);
+                mSyncFolderHandler.add(account, remotePath, (SynchronizeFolderOperation)itemToQueue.second);
                 Message msg = mSyncFolderHandler.obtainMessage();
                 msg.arg1 = startId;
+                msg.obj = itemSyncKey;
                 mSyncFolderHandler.sendMessage(msg);
             }
-            
+        } else if (ACTION_CANCEL_SYNC_FOLDER.equals(intent.getAction())) {
+            if (!intent.hasExtra(EXTRA_ACCOUNT) || !intent.hasExtra(EXTRA_REMOTE_PATH)) {
+                Log_OC.e(TAG, "Not enough information provided in intent");
+                return START_NOT_STICKY;
+            }
+            Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+            String remotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
+
+            // Cancel operation
+            mSyncFolderHandler.cancel(account,remotePath);
         } else {
             Message msg = mOperationsHandler.obtainMessage();
             msg.arg1 = startId;
@@ -208,7 +229,6 @@ public class OperationsService extends Service {
         super.onDestroy();
     }
 
-
     /**
      * Provides a binder object that clients can use to perform actions on the queue of operations, 
      * except the addition of new operations. 
@@ -229,7 +249,7 @@ public class OperationsService extends Service {
         return false;   // not accepting rebinding (default behaviour)
     }
 
-    
+
     /**
      *  Binder to let client components to perform actions on the queue of operations.
      * 
@@ -341,8 +361,113 @@ public class OperationsService extends Service {
         }
 
     }
-    
-    
+
+
+    /**
+     * SyncFolder worker. Performs the pending operations in the order they were requested.
+     *
+     * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.
+     */
+    private static class SyncFolderHandler extends Handler {
+
+        // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
+
+        OperationsService mService;
+
+        private ConcurrentMap<String,SynchronizeFolderOperation> mPendingOperations =
+                new ConcurrentHashMap<String,SynchronizeFolderOperation>();
+        private OwnCloudClient mOwnCloudClient = null;
+        private FileDataStorageManager mStorageManager;
+        private SynchronizeFolderOperation mCurrentSyncOperation;
+
+
+        public SyncFolderHandler(Looper looper, OperationsService service) {
+            super(looper);
+            if (service == null) {
+                throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
+            }
+            mService = service;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Pair<Account, String> itemSyncKey = (Pair<Account, String>) msg.obj;
+            doOperation(itemSyncKey.first, itemSyncKey.second);
+            mService.stopSelf(msg.arg1);
+        }
+
+
+        /**
+         * Performs the next operation in the queue
+         */
+        private void doOperation(Account account, String remotePath) {
+
+            String syncKey = buildRemoteName(account,remotePath);
+
+            synchronized(mPendingOperations) {
+                mCurrentSyncOperation = mPendingOperations.get(syncKey);
+            }
+
+            if (mCurrentSyncOperation != null) {
+
+                try {
+
+                    OwnCloudAccount ocAccount = new OwnCloudAccount(account, mService);
+                    mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                            getClientFor(ocAccount, mService);
+                    mStorageManager = new FileDataStorageManager(
+                            account,
+                            mService.getContentResolver()
+                    );
+
+                    mCurrentSyncOperation.execute(mOwnCloudClient, mStorageManager);
+
+                } catch (AccountsException e) {
+                    Log_OC.e(TAG, "Error while trying to get autorization", e);
+                } catch (IOException e) {
+                    Log_OC.e(TAG, "Error while trying to get autorization", e);
+                } finally {
+                    synchronized(mPendingOperations) {
+                        mPendingOperations.remove(syncKey);
+                    }
+                }
+            }
+        }
+
+        public void add(Account account, String remotePath, SynchronizeFolderOperation syncFolderOperation){
+            String syncKey = buildRemoteName(account,remotePath);
+            mPendingOperations.putIfAbsent(syncKey,syncFolderOperation);
+        }
+
+
+        /**
+         * Cancels a pending or current sync operation.
+         *
+         * @param account       Owncloud account where the remote file is stored.
+         * @param remotePath    A remote file path
+         */
+        public void cancel(Account account, String remotePath) {
+            SynchronizeFolderOperation syncOperation = null;
+            synchronized (mPendingOperations) {
+                syncOperation = mPendingOperations.remove(buildRemoteName(account, remotePath));
+            }
+            if (syncOperation != null) {
+                syncOperation.cancel();
+            }
+        }
+
+        /**
+         * Builds a key from the account and file to download
+         *
+         * @param account   Account where the file to download is stored
+         * @param path      File path
+         */
+        private String buildRemoteName(Account account, String path) {
+            return account.name + path;
+        }
+    }
+
+
     /** 
      * Operations worker. Performs the pending operations in the order they were requested. 
      * 
@@ -355,7 +480,7 @@ public class OperationsService extends Service {
         OperationsService mService;
         
         
-        private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations = 
+        private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations =
                 new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
         private RemoteOperation mCurrentOperation = null;
         private Target mLastTarget = null;
@@ -687,6 +812,4 @@ public class OperationsService extends Service {
         }
         Log_OC.d(TAG, "Called " + count + " listeners");
     }
-    
-
 }