From: David A. Velasco Date: Thu, 30 Jan 2014 14:02:50 +0000 (+0100) Subject: Added service to run operations and created SyncOperations as common class for operat... X-Git-Tag: oc-android-1.5.5~35^2~40^2~3 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/72d9d63acb800adefdd32e63196e0434ec0f7d96?ds=inline;hp=-c Added service to run operations and created SyncOperations as common class for operations that work both both with remote and local storage --- 72d9d63acb800adefdd32e63196e0434ec0f7d96 diff --git a/owncloud-android-library b/owncloud-android-library index 7126c73e..27232c3a 160000 --- a/owncloud-android-library +++ b/owncloud-android-library @@ -1 +1 @@ -Subproject commit 7126c73e6324ca8d3615fc4757191848e03d5644 +Subproject commit 27232c3aa430951cde0d05c1846250eaf6385e4a diff --git a/src/com/owncloud/android/operations/GetSharesOperation.java b/src/com/owncloud/android/operations/GetSharesOperation.java index 3b879c06..8949a8ed 100644 --- a/src/com/owncloud/android/operations/GetSharesOperation.java +++ b/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 sharedFiles = new ArrayList(); @@ -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); } } } diff --git a/src/com/owncloud/android/operations/common/SyncOperation.java b/src/com/owncloud/android/operations/common/SyncOperation.java new file mode 100644 index 00000000..7a8e356a --- /dev/null +++ b/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 . + * + */ + +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); + } + + +} diff --git a/src/com/owncloud/android/services/OperationsService.java b/src/com/owncloud/android/services/OperationsService.java new file mode 100644 index 00000000..b7290c8b --- /dev/null +++ b/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 . + * + */ + +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> mPendingOperations = new ConcurrentLinkedQueue>(); + + 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, 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 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); + } + + +} diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 0eb1966c..db4e2628 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/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);