Эх сурвалжийг харах

Added service to run operations and created SyncOperations as common class for operations that work both both with remote and local storage

David A. Velasco 11 жил өмнө
parent
commit
72d9d63acb

+ 1 - 1
owncloud-android-library

@@ -1 +1 @@
-Subproject commit 7126c73e6324ca8d3615fc4757191848e03d5644
+Subproject commit 27232c3aa430951cde0d05c1846250eaf6385e4a

+ 6 - 11
src/com/owncloud/android/operations/GetSharesOperation.java

@@ -28,6 +28,7 @@ import com.owncloud.android.lib.operations.common.OCShare;
 import com.owncloud.android.lib.operations.common.ShareType;
 import com.owncloud.android.lib.operations.remote.GetRemoteSharesOperation;
 import com.owncloud.android.lib.utils.FileUtils;
+import com.owncloud.android.operations.common.SyncOperation;
 import com.owncloud.android.utils.Log_OC;
 
 /**
@@ -35,19 +36,13 @@ import com.owncloud.android.utils.Log_OC;
  * Save the data in Database
  * 
  * @author masensio
+ * @author David A. Velasco
  */
 
-public class GetSharesOperation extends RemoteOperation {
+public class GetSharesOperation extends SyncOperation {
 
     private static final String TAG = GetSharesOperation.class.getSimpleName();
 
-    protected FileDataStorageManager mStorageManager;
-
-
-    public GetSharesOperation(FileDataStorageManager storageManager) {
-        mStorageManager = storageManager;
-    }
-
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
         GetRemoteSharesOperation operation = new GetRemoteSharesOperation();
@@ -72,7 +67,7 @@ public class GetSharesOperation extends RemoteOperation {
 
         if (shares.size() > 0) {
             // Save share file
-            mStorageManager.saveShares(shares);
+            getStorageManager().saveShares(shares);
 
             ArrayList<OCFile> sharedFiles = new ArrayList<OCFile>();
 
@@ -84,7 +79,7 @@ public class GetSharesOperation extends RemoteOperation {
                 }           
 
                 // Update OCFile with data from share: ShareByLink  ¿and publicLink?
-                OCFile file = mStorageManager.getFileByPath(path);
+                OCFile file = getStorageManager().getFileByPath(path);
                 if (file != null) {
                     if (share.getShareType().equals(ShareType.PUBLIC_LINK)) {
                         file.setShareByLink(true);
@@ -94,7 +89,7 @@ public class GetSharesOperation extends RemoteOperation {
             }
             
             if (sharedFiles.size() > 0) {
-                mStorageManager.updateSharedFiles(sharedFiles);
+                getStorageManager().updateSharedFiles(sharedFiles);
             }
         }
     }

+ 129 - 0
src/com/owncloud/android/operations/common/SyncOperation.java

@@ -0,0 +1,129 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2012-2014 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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.common;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.lib.network.OwnCloudClient;
+import com.owncloud.android.lib.operations.common.OnRemoteOperationListener;
+import com.owncloud.android.lib.operations.common.RemoteOperation;
+import com.owncloud.android.lib.operations.common.RemoteOperationResult;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Handler;
+
+
+/**
+ * Operation which execution involves both interactions with an ownCloud server and
+ * with local data in the device.
+ * 
+ * Provides methods to execute the operation both synchronously or asynchronously.
+ * 
+ * @author David A. Velasco 
+ */
+public abstract class SyncOperation extends RemoteOperation {
+	
+    //private static final String TAG = SyncOperation.class.getSimpleName();
+
+    private FileDataStorageManager mStorageManager;
+    
+    public FileDataStorageManager getStorageManager() {
+        return mStorageManager;
+    }
+	
+
+    /**
+     * Synchronously executes the operation on the received ownCloud account.
+     * 
+     * Do not call this method from the main thread.
+     * 
+     * This method should be used whenever an ownCloud account is available, instead of {@link #execute(OwnCloudClient)}. 
+     * 
+     * @param account   ownCloud account in remote ownCloud server to reach during the execution of the operation.
+     * @param context   Android context for the component calling the method.
+     * @return          Result of the operation.
+     */
+    public RemoteOperationResult execute(FileDataStorageManager storageManager, Context context) {
+        if (storageManager == null) {
+            throw new IllegalArgumentException("Trying to execute a sync operation with a NULL storage manager");
+        }
+        if (storageManager.getAccount() == null) {
+            throw new IllegalArgumentException("Trying to execute a sync operation with a storage manager for a NULL account");
+        }
+        mStorageManager = storageManager;
+        return super.execute(mStorageManager.getAccount(), context);
+    }
+    
+	
+	/**
+	 * Synchronously executes the remote operation
+	 * 
+     * Do not call this method from the main thread.
+     * 
+	 * @param client	Client object to reach an ownCloud server during the execution of the operation.
+	 * @return			Result of the operation.
+	 */
+	public RemoteOperationResult execute(OwnCloudClient client, FileDataStorageManager storageManager) {
+        if (storageManager == null)
+            throw new IllegalArgumentException("Trying to execute a sync operation with a NULL storage manager");
+        mStorageManager = storageManager;
+		return super.execute(client);
+	}
+
+	
+    /**
+     * Asynchronously executes the remote operation
+     * 
+     * This method should be used whenever an ownCloud account is available, instead of {@link #execute(OwnCloudClient)}. 
+     * 
+     * @param account           ownCloud account in remote ownCloud server to reach during the execution of the operation.
+     * @param context           Android context for the component calling the method.
+     * @param listener          Listener to be notified about the execution of the operation.
+     * @param listenerHandler   Handler associated to the thread where the methods of the listener objects must be called.
+     * @return                  Thread were the remote operation is executed.
+     */
+    public Thread execute(FileDataStorageManager storageManager, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) {
+        if (storageManager == null) {
+            throw new IllegalArgumentException("Trying to execute a sync operation with a NULL storage manager");
+        }
+        if (storageManager.getAccount() == null) {
+            throw new IllegalArgumentException("Trying to execute a sync operation with a storage manager for a NULL account");
+        }
+        mStorageManager = storageManager;
+        return super.execute(storageManager.getAccount(), context, listener, listenerHandler, callerActivity);
+    }
+
+    
+	/**
+	 * Asynchronously executes the remote operation
+	 * 
+	 * @param client			Client object to reach an ownCloud server during the execution of the operation.
+	 * @param listener			Listener to be notified about the execution of the operation.
+	 * @param listenerHandler	Handler associated to the thread where the methods of the listener objects must be called.
+	 * @return					Thread were the remote operation is executed.
+	 */
+	public Thread execute(OwnCloudClient client, FileDataStorageManager storageManager, OnRemoteOperationListener listener, Handler listenerHandler) {
+        if (storageManager == null) {
+            throw new IllegalArgumentException("Trying to execute a sync operation with a NULL storage manager");
+        }
+        mStorageManager = storageManager;
+        return super.execute(client, listener, listenerHandler);
+	}
+
+	
+}

+ 284 - 0
src/com/owncloud/android/services/OperationsService.java

@@ -0,0 +1,284 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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.services;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+
+import com.owncloud.android.lib.network.OwnCloudClientFactory;
+import com.owncloud.android.lib.network.OwnCloudClient;
+import com.owncloud.android.operations.GetSharesOperation;
+import com.owncloud.android.operations.common.SyncOperation;
+import com.owncloud.android.lib.operations.common.RemoteOperation;
+import com.owncloud.android.lib.operations.common.RemoteOperationResult;
+import com.owncloud.android.utils.Log_OC;
+
+import android.accounts.Account;
+import android.accounts.AccountsException;
+import android.app.Service;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Pair;
+
+public class OperationsService extends Service {
+    
+    private static final String TAG = OperationsService.class.getSimpleName();
+    
+    public static final String EXTRA_ACCOUNT = "ACCOUNT";
+    public static final String EXTRA_SERVER_URL = "SERVER_URL";
+    public static final String EXTRA_RESULT = "RESULT";    
+    private static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
+    private static final String ACTION_OPERATION_FINISHED = OperationsService.class.getName() + ".OPERATION_FINISHED";
+
+    private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations = new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
+    
+    private static class Target {
+        public Uri mServerUrl = null;
+        public Account mAccount = null;
+        public Target(Account account, Uri serverUrl) {
+            mAccount = account;
+            mServerUrl = serverUrl;
+        }
+    }
+
+    private Looper mServiceLooper;
+    private ServiceHandler mServiceHandler;
+    private IBinder mBinder;
+    private OwnCloudClient mOwnCloudClient = null;
+    private Target mLastTarget = null;
+    private FileDataStorageManager mStorageManager;
+    private RemoteOperation mCurrentOperation = null;
+    
+    
+    /**
+     * Service initialization
+     */
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        HandlerThread thread = new HandlerThread("Operations service thread", Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+        mServiceLooper = thread.getLooper();
+        mServiceHandler = new ServiceHandler(mServiceLooper, this);
+        mBinder = new OperationsServiceBinder();
+    }
+
+    /**
+     * Entry point to add a new operation to the queue of operations.
+     * 
+     * New operations are added calling to startService(), resulting in a call to this method. 
+     * This ensures the service will keep on working although the caller activity goes away.
+     * 
+     * IMPORTANT: the only operations performed here right now is {@link GetSharedFilesOperation}. The class
+     * is taking advantage of it due to time constraints.
+     */
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (!intent.hasExtra(EXTRA_ACCOUNT) && !intent.hasExtra(EXTRA_SERVER_URL)) {
+            Log_OC.e(TAG, "Not enough information provided in intent");
+            return START_NOT_STICKY;
+        }
+        try {
+            Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+            String serverUrl = intent.getStringExtra(EXTRA_SERVER_URL);
+            Target target = new Target(account, (serverUrl == null) ? null : Uri.parse(serverUrl));
+            GetSharesOperation operation = new GetSharesOperation();
+            mPendingOperations.add(new Pair<Target , RemoteOperation>(target, operation));
+            sendBroadcastNewOperation(target, operation);
+            
+            Message msg = mServiceHandler.obtainMessage();
+            msg.arg1 = startId;
+            mServiceHandler.sendMessage(msg);
+            
+        } catch (IllegalArgumentException e) {
+            Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
+            return START_NOT_STICKY;
+        }
+        
+        return START_NOT_STICKY;
+    }
+
+    
+    /**
+     * Provides a binder object that clients can use to perform actions on the queue of operations, 
+     * except the addition of new operations. 
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    
+    /**
+     * Called when ALL the bound clients were unbound.
+     */
+    @Override
+    public boolean onUnbind(Intent intent) {
+        //((OperationsServiceBinder)mBinder).clearListeners();
+        return false;   // not accepting rebinding (default behaviour)
+    }
+
+    
+    /**
+     *  Binder to let client components to perform actions on the queue of operations.
+     * 
+     *  It provides by itself the available operations.
+     */
+    public class OperationsServiceBinder extends Binder {
+        // TODO
+    }
+    
+    
+    /** 
+     * Operations 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 ServiceHandler 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;
+        public ServiceHandler(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) {
+            mService.nextOperation();
+            mService.stopSelf(msg.arg1);
+        }
+    }
+    
+
+    /**
+     * Performs the next operation in the queue
+     */
+    private void nextOperation() {
+        
+        Pair<Target, RemoteOperation> next = null;
+        synchronized(mPendingOperations) {
+            next = mPendingOperations.peek();
+        }
+        
+        if (next != null) {
+            
+            mCurrentOperation = next.second;
+            RemoteOperationResult result = null;
+            try {
+                /// prepare client object to send the request to the ownCloud server
+                if (mLastTarget == null || !mLastTarget.equals(next.first)) {
+                    mLastTarget = next.first;
+                    if (mLastTarget.mAccount != null) {
+                        mOwnCloudClient = OwnCloudClientFactory.createOwnCloudClient(mLastTarget.mAccount, getApplicationContext());
+                        mStorageManager = new FileDataStorageManager(mLastTarget.mAccount, getContentResolver());
+                    } else {
+                        mOwnCloudClient = OwnCloudClientFactory.createOwnCloudClient(mLastTarget.mServerUrl, getApplicationContext(), true);    // this is not good enough
+                        mStorageManager = null;
+                    }
+                }
+
+                /// perform the operation
+                if (mCurrentOperation instanceof SyncOperation) {
+                    result = ((SyncOperation)mCurrentOperation).execute(mOwnCloudClient, mStorageManager);
+                } else {
+                    result = mCurrentOperation.execute(mOwnCloudClient);
+                }
+            
+            } catch (AccountsException e) {
+                if (mLastTarget.mAccount == null) {
+                    Log_OC.e(TAG, "Error while trying to get autorization for a NULL account", e);
+                } else {
+                    Log_OC.e(TAG, "Error while trying to get autorization for " + mLastTarget.mAccount.name, e);
+                }
+                result = new RemoteOperationResult(e);
+                
+            } catch (IOException e) {
+                if (mLastTarget.mAccount == null) {
+                    Log_OC.e(TAG, "Error while trying to get autorization for a NULL account", e);
+                } else {
+                    Log_OC.e(TAG, "Error while trying to get autorization for " + mLastTarget.mAccount.name, e);
+                }
+                result = new RemoteOperationResult(e);
+                
+            } finally {
+                synchronized(mPendingOperations) {
+                    mPendingOperations.poll();
+                }
+            }
+            
+            sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
+        }
+    }
+
+
+    /**
+     * Sends a LOCAL broadcast when a new operation is added to the queue.
+     * 
+     * Local broadcasts are only delivered to activities in the same process.
+     * 
+     * @param target            Account or URL pointing to an OC server.
+     * @param operation         Added operation.
+     */
+    private void sendBroadcastNewOperation(Target target, RemoteOperation operation) {
+        Intent intent = new Intent(ACTION_OPERATION_ADDED);
+        if (target.mAccount != null) {
+            intent.putExtra(EXTRA_ACCOUNT, target.mAccount);    
+        } else {
+            intent.putExtra(EXTRA_SERVER_URL, target.mServerUrl);    
+        }
+        LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
+        lbm.sendBroadcast(intent);
+    }
+
+    
+    /**
+     * Sends a LOCAL broadcast when an operations finishes in order to the interested activities can update their view
+     * 
+     * Local broadcasts are only delivered to activities in the same process.
+     * 
+     * @param target            Account or URL pointing to an OC server.
+     * @param operation         Finished operation.
+     * @param result            Result of the operation.
+     */
+    private void sendBroadcastOperationFinished(Target target, RemoteOperation operation, RemoteOperationResult result) {
+        Intent intent = new Intent(ACTION_OPERATION_FINISHED);
+        intent.putExtra(EXTRA_RESULT, result);
+        if (target.mAccount != null) {
+            intent.putExtra(EXTRA_ACCOUNT, target.mAccount);    
+        } else {
+            intent.putExtra(EXTRA_SERVER_URL, target.mServerUrl);    
+        }
+        LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
+        lbm.sendBroadcast(intent);
+    }
+    
+    
+}

+ 3 - 2
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -80,6 +80,7 @@ import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.common.SyncOperation;
 import com.owncloud.android.syncadapter.FileSyncService;
 import com.owncloud.android.ui.dialog.EditNameDialog;
 import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
@@ -1531,8 +1532,8 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa
     
     private void startGetShares() {
         // Get shared files/folders
-        RemoteOperation getShares = new GetSharesOperation(mStorageManager);
-        getShares.execute(getAccount(), this, this, mHandler, this);
+        SyncOperation getShares = new GetSharesOperation();
+        getShares.execute(mStorageManager, this, this, mHandler, this);
         
         mRefreshSharesInProgress = true;
         setSupportProgressBarIndeterminateVisibility(true);