X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/blobdiff_plain/296df1bb6550081535ff06dfacd1d28345c51545..e901b609baa4dd5f681e2a5257c9e504997e3377:/src/com/owncloud/android/services/OperationsService.java?ds=inline diff --git a/src/com/owncloud/android/services/OperationsService.java b/src/com/owncloud/android/services/OperationsService.java index 919bedb3..a5bb22db 100644 --- a/src/com/owncloud/android/services/OperationsService.java +++ b/src/com/owncloud/android/services/OperationsService.java @@ -1,5 +1,5 @@ /* ownCloud Android client application - * Copyright (C) 2012-2013 ownCloud Inc. + * 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, @@ -18,20 +18,42 @@ package com.owncloud.android.services; import java.io.IOException; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +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.lib.network.OwnCloudClientFactory; -import com.owncloud.android.lib.network.OwnCloudClient; -import com.owncloud.android.operations.GetSharesOperation; +import com.owncloud.android.lib.common.OwnCloudAccount; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; +import com.owncloud.android.lib.common.OwnCloudCredentials; +import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; +import com.owncloud.android.lib.resources.shares.ShareType; +import com.owncloud.android.lib.resources.users.GetRemoteUserNameOperation; 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 com.owncloud.android.operations.CreateFolderOperation; +import com.owncloud.android.operations.CreateShareOperation; +import com.owncloud.android.operations.GetServerInfoOperation; +import com.owncloud.android.operations.MoveFileOperation; +import com.owncloud.android.operations.OAuth2GetAccessToken; +import com.owncloud.android.operations.RemoveFileOperation; +import com.owncloud.android.operations.RenameFileOperation; +import com.owncloud.android.operations.SynchronizeFileOperation; +import com.owncloud.android.operations.UnshareLinkOperation; import android.accounts.Account; import android.accounts.AccountsException; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.app.Service; import android.content.Intent; import android.net.Uri; @@ -42,7 +64,6 @@ 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 { @@ -51,25 +72,67 @@ public class OperationsService extends Service { public static final String EXTRA_ACCOUNT = "ACCOUNT"; public static final String EXTRA_SERVER_URL = "SERVER_URL"; - public static final String EXTRA_RESULT = "RESULT"; + public static final String EXTRA_OAUTH2_QUERY_PARAMETERS = "OAUTH2_QUERY_PARAMETERS"; + public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH"; + public static final String EXTRA_SEND_INTENT = "SEND_INTENT"; + public static final String EXTRA_NEWNAME = "NEWNAME"; + public static final String EXTRA_REMOVE_ONLY_LOCAL = "REMOVE_LOCAL_COPY"; + public static final String EXTRA_CREATE_FULL_PATH = "CREATE_FULL_PATH"; + public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS"; + public static final String EXTRA_RESULT = "RESULT"; + public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH"; + + // TODO review if ALL OF THEM are necessary + public static final String EXTRA_SUCCESS_IF_ABSENT = "SUCCESS_IF_ABSENT"; + public static final String EXTRA_USERNAME = "USERNAME"; + public static final String EXTRA_PASSWORD = "PASSWORD"; + public static final String EXTRA_AUTH_TOKEN = "AUTH_TOKEN"; + public static final String EXTRA_COOKIE = "COOKIE"; + + public static final String ACTION_CREATE_SHARE = "CREATE_SHARE"; + public static final String ACTION_UNSHARE = "UNSHARE"; + public static final String ACTION_GET_SERVER_INFO = "GET_SERVER_INFO"; + public static final String ACTION_OAUTH2_GET_ACCESS_TOKEN = "OAUTH2_GET_ACCESS_TOKEN"; + public static final String ACTION_EXISTENCE_CHECK = "EXISTENCE_CHECK"; + public static final String ACTION_GET_USER_NAME = "GET_USER_NAME"; + public static final String ACTION_RENAME = "RENAME"; + public static final String ACTION_REMOVE = "REMOVE"; + public static final String ACTION_CREATE_FOLDER = "CREATE_FOLDER"; + public static final String ACTION_SYNC_FILE = "SYNC_FILE"; + public static final String ACTION_MOVE_FILE = "MOVE_FILE"; public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED"; public static final String ACTION_OPERATION_FINISHED = OperationsService.class.getName() + ".OPERATION_FINISHED"; - private ConcurrentLinkedQueue> mPendingOperations = new ConcurrentLinkedQueue>(); + private ConcurrentLinkedQueue> mPendingOperations = + new ConcurrentLinkedQueue>(); + + private ConcurrentMap> + mUndispatchedFinishedOperations = + new ConcurrentHashMap>(); private static class Target { public Uri mServerUrl = null; public Account mAccount = null; - public Target(Account account, Uri serverUrl) { + public String mUsername = null; + public String mPassword = null; + public String mAuthToken = null; + public String mCookie = null; + + public Target(Account account, Uri serverUrl, String username, String password, String authToken, + String cookie) { mAccount = account; mServerUrl = serverUrl; + mUsername = username; + mPassword = password; + mAuthToken = authToken; + mCookie = cookie; } } private Looper mServiceLooper; private ServiceHandler mServiceHandler; - private IBinder mBinder; + private OperationsServiceBinder mBinder; private OwnCloudClient mOwnCloudClient = null; private Target mLastTarget = null; private FileDataStorageManager mStorageManager; @@ -89,6 +152,7 @@ public class OperationsService extends Service { mBinder = new OperationsServiceBinder(); } + /** * Entry point to add a new operation to the queue of operations. * @@ -100,37 +164,48 @@ public class OperationsService extends Service { */ @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; - } + //Log_OC.wtf(TAG, "onStartCommand init" ); + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + mServiceHandler.sendMessage(msg); + //Log_OC.wtf(TAG, "onStartCommand end" ); + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + //Log_OC.wtf(TAG, "onDestroy init" ); + // Saving cookies 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); + OwnCloudClientManagerFactory.getDefaultSingleton(). + saveAllClients(this, MainApp.getAccountType()); - } catch (IllegalArgumentException e) { - Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage()); - return START_NOT_STICKY; + // TODO - get rid of these exceptions + } catch (AccountNotFoundException e) { + e.printStackTrace(); + } catch (AuthenticatorException e) { + e.printStackTrace(); + } catch (OperationCanceledException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); } - return START_NOT_STICKY; + //Log_OC.wtf(TAG, "Clear mUndispatchedFinisiedOperations" ); + mUndispatchedFinishedOperations.clear(); + + //Log_OC.wtf(TAG, "onDestroy end" ); + super.onDestroy(); } - + /** * 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) { + //Log_OC.wtf(TAG, "onBind" ); return mBinder; } @@ -140,7 +215,7 @@ public class OperationsService extends Service { */ @Override public boolean onUnbind(Intent intent) { - //((OperationsServiceBinder)mBinder).clearListeners(); + ((OperationsServiceBinder)mBinder).clearListeners(); return false; // not accepting rebinding (default behaviour) } @@ -150,8 +225,203 @@ public class OperationsService extends Service { * * It provides by itself the available operations. */ - public class OperationsServiceBinder extends Binder { - // TODO + public class OperationsServiceBinder extends Binder /* implements OnRemoteOperationListener */ { + + /** + * Map of listeners that will be reported about the end of operations from a {@link OperationsServiceBinder} instance + */ + private ConcurrentMap mBoundListeners = + new ConcurrentHashMap(); + + /** + * Cancels an operation + * + * TODO + */ + public void cancel() { + // TODO + } + + + public void clearListeners() { + + mBoundListeners.clear(); + } + + + /** + * Adds a listener interested in being reported about the end of operations. + * + * @param listener Object to notify about the end of operations. + * @param callbackHandler {@link Handler} to access the listener without breaking Android threading protection. + */ + public void addOperationListener (OnRemoteOperationListener listener, Handler callbackHandler) { + synchronized (mBoundListeners) { + mBoundListeners.put(listener, callbackHandler); + } + } + + + /** + * Removes a listener from the list of objects interested in the being reported about the end of operations. + * + * @param listener Object to notify about progress of transfer. + */ + public void removeOperationListener (OnRemoteOperationListener listener) { + synchronized (mBoundListeners) { + mBoundListeners.remove(listener); + } + } + + + /** + * TODO - IMPORTANT: update implementation when more operations are moved into the service + * + * @return 'True' when an operation that enforces the user to wait for completion is in process. + */ + public boolean isPerformingBlockingOperation() { + return (!mPendingOperations.isEmpty()); + } + + + /** + * Creates and adds to the queue a new operation, as described by operationIntent + * + * @param operationIntent Intent describing a new operation to queue and execute. + * @return Identifier of the operation created, or null if failed. + */ + public long newOperation(Intent operationIntent) { + RemoteOperation operation = null; + Target target = null; + try { + if (!operationIntent.hasExtra(EXTRA_ACCOUNT) && + !operationIntent.hasExtra(EXTRA_SERVER_URL)) { + Log_OC.e(TAG, "Not enough information provided in intent"); + + } else { + Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT); + String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL); + String username = operationIntent.getStringExtra(EXTRA_USERNAME); + String password = operationIntent.getStringExtra(EXTRA_PASSWORD); + String authToken = operationIntent.getStringExtra(EXTRA_AUTH_TOKEN); + String cookie = operationIntent.getStringExtra(EXTRA_COOKIE); + target = new Target( + account, + (serverUrl == null) ? null : Uri.parse(serverUrl), + username, + password, + authToken, + cookie + ); + + String action = operationIntent.getAction(); + if (action.equals(ACTION_CREATE_SHARE)) { // Create Share + String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); + Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT); + if (remotePath.length() > 0) { + operation = new CreateShareOperation(remotePath, ShareType.PUBLIC_LINK, + "", false, "", 1, sendIntent); + } + + } else if (action.equals(ACTION_UNSHARE)) { // Unshare file + String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); + if (remotePath.length() > 0) { + operation = new UnshareLinkOperation( + remotePath, + OperationsService.this); + } + + } else if (action.equals(ACTION_GET_SERVER_INFO)) { + // check OC server and get basic information from it + operation = new GetServerInfoOperation(serverUrl, OperationsService.this); + + } else if (action.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN)) { + /// GET ACCESS TOKEN to the OAuth server + String oauth2QueryParameters = + operationIntent.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS); + operation = new OAuth2GetAccessToken( + getString(R.string.oauth2_client_id), + getString(R.string.oauth2_redirect_uri), + getString(R.string.oauth2_grant_type), + oauth2QueryParameters); + + } else if (action.equals(ACTION_EXISTENCE_CHECK)) { + // Existence Check + String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); + boolean successIfAbsent = operationIntent.getBooleanExtra(EXTRA_SUCCESS_IF_ABSENT, false); + operation = new ExistenceCheckRemoteOperation(remotePath, OperationsService.this, successIfAbsent); + + } else if (action.equals(ACTION_GET_USER_NAME)) { + // Get User Name + operation = new GetRemoteUserNameOperation(); + + } else if (action.equals(ACTION_RENAME)) { + // Rename file or folder + String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); + String newName = operationIntent.getStringExtra(EXTRA_NEWNAME); + operation = new RenameFileOperation(remotePath, account, newName); + + } else if (action.equals(ACTION_REMOVE)) { + // Remove file or folder + String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); + boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false); + operation = new RemoveFileOperation(remotePath, onlyLocalCopy); + + } else if (action.equals(ACTION_CREATE_FOLDER)) { + // Create Folder + String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); + boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true); + operation = new CreateFolderOperation(remotePath, createFullPath); + + } else if (action.equals(ACTION_SYNC_FILE)) { + // Sync file + String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); + boolean syncFileContents = operationIntent.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS, true); + operation = new SynchronizeFileOperation(remotePath, account, syncFileContents, getApplicationContext()); + } else if (action.equals(ACTION_MOVE_FILE)) { + // Move file/folder + String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); + String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH); + operation = new MoveFileOperation(remotePath,newParentPath,account); + } + + } + + } catch (IllegalArgumentException e) { + Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage()); + operation = null; + } + + if (operation != null) { + mPendingOperations.add(new Pair(target, operation)); + startService(new Intent(OperationsService.this, OperationsService.class)); + //Log_OC.wtf(TAG, "New operation added, opId: " + operation.hashCode()); + // better id than hash? ; should be good enough by the time being + return operation.hashCode(); + + } else { + //Log_OC.wtf(TAG, "New operation failed, returned Long.MAX_VALUE"); + return Long.MAX_VALUE; + } + } + + public boolean dispatchResultIfFinished(int operationId, OnRemoteOperationListener listener) { + Pair undispatched = + mUndispatchedFinishedOperations.remove(operationId); + if (undispatched != null) { + listener.onRemoteOperationFinish(undispatched.first, undispatched.second); + return true; + //Log_OC.wtf(TAG, "Sending callback later"); + } else { + if (!mPendingOperations.isEmpty()) { + return true; + } else { + return false; + } + //Log_OC.wtf(TAG, "Not finished yet"); + } + } + } @@ -184,11 +454,13 @@ public class OperationsService extends Service { */ private void nextOperation() { + //Log_OC.wtf(TAG, "nextOperation init" ); + Pair next = null; synchronized(mPendingOperations) { next = mPendingOperations.peek(); } - + if (next != null) { mCurrentOperation = next.second; @@ -198,10 +470,35 @@ public class OperationsService extends Service { 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()); + OwnCloudAccount ocAccount = new OwnCloudAccount(mLastTarget.mAccount, this); + mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton(). + getClientFor(ocAccount, this); + mStorageManager = + new FileDataStorageManager( + mLastTarget.mAccount, + getContentResolver()); } else { - mOwnCloudClient = OwnCloudClientFactory.createOwnCloudClient(mLastTarget.mServerUrl, getApplicationContext(), true); // this is not good enough + OwnCloudCredentials credentials = null; + if (mLastTarget.mUsername != null && + mLastTarget.mUsername.length() > 0) { + credentials = OwnCloudCredentialsFactory.newBasicCredentials( + mLastTarget.mUsername, + mLastTarget.mPassword); // basic + + } else if (mLastTarget.mAuthToken != null && + mLastTarget.mAuthToken.length() > 0) { + credentials = OwnCloudCredentialsFactory.newBearerCredentials( + mLastTarget.mAuthToken); // bearer token + + } else if (mLastTarget.mCookie != null && + mLastTarget.mCookie.length() > 0) { + credentials = OwnCloudCredentialsFactory.newSamlSsoCredentials( + mLastTarget.mCookie); // SAML SSO + } + OwnCloudAccount ocAccount = new OwnCloudAccount( + mLastTarget.mServerUrl, credentials); + mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton(). + getClientFor(ocAccount, this); mStorageManager = null; } } @@ -212,20 +509,20 @@ public class OperationsService extends Service { } 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); + Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e); } else { - Log_OC.e(TAG, "Error while trying to get autorization for " + mLastTarget.mAccount.name, e); + Log_OC.e(TAG, "Error while trying to get authorization 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); + Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e); } else { - Log_OC.e(TAG, "Error while trying to get autorization for " + mLastTarget.mAccount.name, e); + Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e); } result = new RemoteOperationResult(e); } catch (Exception e) { @@ -242,15 +539,16 @@ public class OperationsService extends Service { } } - sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result); + //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result); + dispatchResultToOperationListeners(mLastTarget, mCurrentOperation, result); } } /** - * Sends a LOCAL broadcast when a new operation is added to the queue. + * Sends a broadcast when a new operation is added to the queue. * - * Local broadcasts are only delivered to activities in the same process. + * Local broadcasts are only delivered to activities in the same process, but can't be done sticky :\ * * @param target Account or URL pointing to an OC server. * @param operation Added operation. @@ -291,6 +589,40 @@ public class OperationsService extends Service { //lbm.sendBroadcast(intent); sendStickyBroadcast(intent); } + + /** + * Notifies the currently subscribed listeners about the end of an operation. + * + * @param target Account or URL pointing to an OC server. + * @param operation Finished operation. + * @param result Result of the operation. + */ + private void dispatchResultToOperationListeners( + Target target, final RemoteOperation operation, final RemoteOperationResult result) { + int count = 0; + Iterator listeners = mBinder.mBoundListeners.keySet().iterator(); + while (listeners.hasNext()) { + final OnRemoteOperationListener listener = listeners.next(); + final Handler handler = mBinder.mBoundListeners.get(listener); + if (handler != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onRemoteOperationFinish(operation, result); + } + }); + count += 1; + } + } + if (count == 0) { + //mOperationResults.put(operation.hashCode(), result); + Pair undispatched = + new Pair(operation, result); + mUndispatchedFinishedOperations.put(operation.hashCode(), undispatched); + } + Log_OC.d(TAG, "Called " + count + " listeners"); + } + }