Added cancelation for subtrees
[pub/Android/ownCloud.git] / src / com / owncloud / android / services / OperationsService.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2014 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 package com.owncloud.android.services;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Iterator;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.ConcurrentLinkedQueue;
26 import java.util.concurrent.ConcurrentMap;
27
28 import com.owncloud.android.MainApp;
29 import com.owncloud.android.R;
30 import com.owncloud.android.datamodel.FileDataStorageManager;
31 import com.owncloud.android.datamodel.OCFile;
32 import com.owncloud.android.files.services.FileDownloader;
33 import com.owncloud.android.lib.common.OwnCloudAccount;
34 import com.owncloud.android.lib.common.OwnCloudClient;
35 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
36 import com.owncloud.android.lib.common.OwnCloudCredentials;
37 import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
38 import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
39 import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
40 import com.owncloud.android.lib.common.operations.RemoteOperation;
41 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
42 import com.owncloud.android.lib.common.utils.Log_OC;
43 import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
44 import com.owncloud.android.lib.resources.shares.ShareType;
45 import com.owncloud.android.lib.resources.users.GetRemoteUserNameOperation;
46 import com.owncloud.android.operations.common.SyncOperation;
47 import com.owncloud.android.operations.CreateFolderOperation;
48 import com.owncloud.android.operations.CreateShareOperation;
49 import com.owncloud.android.operations.GetServerInfoOperation;
50 import com.owncloud.android.operations.MoveFileOperation;
51 import com.owncloud.android.operations.OAuth2GetAccessToken;
52 import com.owncloud.android.operations.RemoveFileOperation;
53 import com.owncloud.android.operations.RenameFileOperation;
54 import com.owncloud.android.operations.SynchronizeFileOperation;
55 import com.owncloud.android.operations.SynchronizeFolderOperation;
56 import com.owncloud.android.operations.UnshareLinkOperation;
57 import com.owncloud.android.utils.FileStorageUtils;
58
59 import android.accounts.Account;
60 import android.accounts.AccountsException;
61 import android.accounts.AuthenticatorException;
62 import android.accounts.OperationCanceledException;
63 import android.app.Service;
64 import android.content.Intent;
65 import android.net.Uri;
66 import android.os.Binder;
67 import android.os.Handler;
68 import android.os.HandlerThread;
69 import android.os.IBinder;
70 import android.os.Looper;
71 import android.os.Message;
72 import android.os.Process;
73 import android.util.Pair;
74
75 public class OperationsService extends Service {
76
77 private static final String TAG = OperationsService.class.getSimpleName();
78
79 public static final String EXTRA_ACCOUNT = "ACCOUNT";
80 public static final String EXTRA_SERVER_URL = "SERVER_URL";
81 public static final String EXTRA_OAUTH2_QUERY_PARAMETERS = "OAUTH2_QUERY_PARAMETERS";
82 public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
83 public static final String EXTRA_SEND_INTENT = "SEND_INTENT";
84 public static final String EXTRA_NEWNAME = "NEWNAME";
85 public static final String EXTRA_REMOVE_ONLY_LOCAL = "REMOVE_LOCAL_COPY";
86 public static final String EXTRA_CREATE_FULL_PATH = "CREATE_FULL_PATH";
87 public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
88 public static final String EXTRA_RESULT = "RESULT";
89 public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH";
90 public static final String EXTRA_FILE = "FILE";
91
92 // TODO review if ALL OF THEM are necessary
93 public static final String EXTRA_SUCCESS_IF_ABSENT = "SUCCESS_IF_ABSENT";
94 public static final String EXTRA_USERNAME = "USERNAME";
95 public static final String EXTRA_PASSWORD = "PASSWORD";
96 public static final String EXTRA_AUTH_TOKEN = "AUTH_TOKEN";
97 public static final String EXTRA_COOKIE = "COOKIE";
98
99 public static final String ACTION_CREATE_SHARE = "CREATE_SHARE";
100 public static final String ACTION_UNSHARE = "UNSHARE";
101 public static final String ACTION_GET_SERVER_INFO = "GET_SERVER_INFO";
102 public static final String ACTION_OAUTH2_GET_ACCESS_TOKEN = "OAUTH2_GET_ACCESS_TOKEN";
103 public static final String ACTION_EXISTENCE_CHECK = "EXISTENCE_CHECK";
104 public static final String ACTION_GET_USER_NAME = "GET_USER_NAME";
105 public static final String ACTION_RENAME = "RENAME";
106 public static final String ACTION_REMOVE = "REMOVE";
107 public static final String ACTION_CREATE_FOLDER = "CREATE_FOLDER";
108 public static final String ACTION_SYNC_FILE = "SYNC_FILE";
109 public static final String ACTION_SYNC_FOLDER = "SYNC_FOLDER"; // for the moment, just to download
110 public static final String ACTION_CANCEL_SYNC_FOLDER = "CANCEL_SYNC_FOLDER"; // for the moment, just to download
111 public static final String ACTION_MOVE_FILE = "MOVE_FILE";
112
113 public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
114 public static final String ACTION_OPERATION_FINISHED = OperationsService.class.getName() + ".OPERATION_FINISHED";
115
116
117 private ConcurrentMap<Integer, Pair<RemoteOperation, RemoteOperationResult>>
118 mUndispatchedFinishedOperations =
119 new ConcurrentHashMap<Integer, Pair<RemoteOperation, RemoteOperationResult>>();
120
121 private static class Target {
122 public Uri mServerUrl = null;
123 public Account mAccount = null;
124 public String mUsername = null;
125 public String mPassword = null;
126 public String mAuthToken = null;
127 public String mCookie = null;
128
129 public Target(Account account, Uri serverUrl, String username, String password, String authToken,
130 String cookie) {
131 mAccount = account;
132 mServerUrl = serverUrl;
133 mUsername = username;
134 mPassword = password;
135 mAuthToken = authToken;
136 mCookie = cookie;
137 }
138 }
139
140 private ServiceHandler mOperationsHandler;
141 private OperationsServiceBinder mOperationsBinder;
142
143 private SyncFolderHandler mSyncFolderHandler;
144
145 /**
146 * Service initialization
147 */
148 @Override
149 public void onCreate() {
150 super.onCreate();
151 /// First worker thread for most of operations
152 HandlerThread thread = new HandlerThread("Operations thread", Process.THREAD_PRIORITY_BACKGROUND);
153 thread.start();
154 mOperationsHandler = new ServiceHandler(thread.getLooper(), this);
155 mOperationsBinder = new OperationsServiceBinder(mOperationsHandler);
156
157 /// Separated worker thread for download of folders (WIP)
158 thread = new HandlerThread("Syncfolder thread", Process.THREAD_PRIORITY_BACKGROUND);
159 thread.start();
160 mSyncFolderHandler = new SyncFolderHandler(thread.getLooper(), this);
161 }
162
163
164 /**
165 * Entry point to add a new operation to the queue of operations.
166 *
167 * New operations are added calling to startService(), resulting in a call to this method.
168 * This ensures the service will keep on working although the caller activity goes away.
169 */
170 @Override
171 public int onStartCommand(Intent intent, int flags, int startId) {
172 // WIP: for the moment, only SYNC_FOLDER and CANCEL_SYNC_FOLDER is expected here;
173 // the rest of the operations are requested through the Binder
174 if (ACTION_SYNC_FOLDER.equals(intent.getAction())) {
175
176 if (!intent.hasExtra(EXTRA_ACCOUNT) || !intent.hasExtra(EXTRA_REMOTE_PATH)) {
177 Log_OC.e(TAG, "Not enough information provided in intent");
178 return START_NOT_STICKY;
179 }
180 Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
181 String remotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
182
183 Pair<Account, String> itemSyncKey = new Pair<Account , String>(account, remotePath);
184
185 Pair<Target, RemoteOperation> itemToQueue = newOperation(intent);
186 if (itemToQueue != null) {
187 mSyncFolderHandler.add(account, remotePath, (SynchronizeFolderOperation)itemToQueue.second);
188 mSyncFolderHandler.sendBroadcastNewSyncFolder(account, remotePath);
189 Message msg = mSyncFolderHandler.obtainMessage();
190 msg.arg1 = startId;
191 msg.obj = itemSyncKey;
192 mSyncFolderHandler.sendMessage(msg);
193 }
194
195 } else if (ACTION_CANCEL_SYNC_FOLDER.equals(intent.getAction())) {
196 if (!intent.hasExtra(EXTRA_ACCOUNT) || !intent.hasExtra(EXTRA_FILE)) {
197 Log_OC.e(TAG, "Not enough information provided in intent");
198 return START_NOT_STICKY;
199 }
200 final Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
201 final OCFile file = intent.getParcelableExtra(EXTRA_FILE);
202 // Cancel operation
203 new Thread(new Runnable() {
204 public void run() {
205 // Cancel the download
206 mSyncFolderHandler.cancel(account,file);
207 }
208 }).start();
209
210 } else {
211 Message msg = mOperationsHandler.obtainMessage();
212 msg.arg1 = startId;
213 mOperationsHandler.sendMessage(msg);
214 }
215
216 return START_NOT_STICKY;
217 }
218
219 @Override
220 public void onDestroy() {
221 //Log_OC.wtf(TAG, "onDestroy init" );
222 // Saving cookies
223 try {
224 OwnCloudClientManagerFactory.getDefaultSingleton().
225 saveAllClients(this, MainApp.getAccountType());
226
227 // TODO - get rid of these exceptions
228 } catch (AccountNotFoundException e) {
229 e.printStackTrace();
230 } catch (AuthenticatorException e) {
231 e.printStackTrace();
232 } catch (OperationCanceledException e) {
233 e.printStackTrace();
234 } catch (IOException e) {
235 e.printStackTrace();
236 }
237
238 //Log_OC.wtf(TAG, "Clear mUndispatchedFinisiedOperations" );
239 mUndispatchedFinishedOperations.clear();
240
241 //Log_OC.wtf(TAG, "onDestroy end" );
242 super.onDestroy();
243 }
244
245 /**
246 * Provides a binder object that clients can use to perform actions on the queue of operations,
247 * except the addition of new operations.
248 */
249 @Override
250 public IBinder onBind(Intent intent) {
251 //Log_OC.wtf(TAG, "onBind" );
252 return mOperationsBinder;
253 }
254
255
256 /**
257 * Called when ALL the bound clients were unbound.
258 */
259 @Override
260 public boolean onUnbind(Intent intent) {
261 ((OperationsServiceBinder)mOperationsBinder).clearListeners();
262 return false; // not accepting rebinding (default behaviour)
263 }
264
265
266 /**
267 * Binder to let client components to perform actions on the queue of operations.
268 *
269 * It provides by itself the available operations.
270 */
271 public class OperationsServiceBinder extends Binder /* implements OnRemoteOperationListener */ {
272
273 /**
274 * Map of listeners that will be reported about the end of operations from a {@link OperationsServiceBinder} instance
275 */
276 private ConcurrentMap<OnRemoteOperationListener, Handler> mBoundListeners =
277 new ConcurrentHashMap<OnRemoteOperationListener, Handler>();
278
279 private ServiceHandler mServiceHandler = null;
280
281
282 public OperationsServiceBinder(ServiceHandler serviceHandler) {
283 mServiceHandler = serviceHandler;
284 }
285
286
287 /**
288 * Cancels an operation
289 *
290 * TODO
291 */
292 public void cancel() {
293 // TODO
294 }
295
296
297 public void clearListeners() {
298
299 mBoundListeners.clear();
300 }
301
302
303 /**
304 * Adds a listener interested in being reported about the end of operations.
305 *
306 * @param listener Object to notify about the end of operations.
307 * @param callbackHandler {@link Handler} to access the listener without breaking Android threading protection.
308 */
309 public void addOperationListener (OnRemoteOperationListener listener, Handler callbackHandler) {
310 synchronized (mBoundListeners) {
311 mBoundListeners.put(listener, callbackHandler);
312 }
313 }
314
315
316 /**
317 * Removes a listener from the list of objects interested in the being reported about the end of operations.
318 *
319 * @param listener Object to notify about progress of transfer.
320 */
321 public void removeOperationListener (OnRemoteOperationListener listener) {
322 synchronized (mBoundListeners) {
323 mBoundListeners.remove(listener);
324 }
325 }
326
327
328 /**
329 * TODO - IMPORTANT: update implementation when more operations are moved into the service
330 *
331 * @return 'True' when an operation that enforces the user to wait for completion is in process.
332 */
333 public boolean isPerformingBlockingOperation() {
334 return (!mServiceHandler.mPendingOperations.isEmpty());
335 }
336
337
338 /**
339 * Creates and adds to the queue a new operation, as described by operationIntent.
340 *
341 * Calls startService to make the operation is processed by the ServiceHandler.
342 *
343 * @param operationIntent Intent describing a new operation to queue and execute.
344 * @return Identifier of the operation created, or null if failed.
345 */
346 public long queueNewOperation(Intent operationIntent) {
347 Pair<Target, RemoteOperation> itemToQueue = newOperation(operationIntent);
348 if (itemToQueue != null) {
349 mServiceHandler.mPendingOperations.add(itemToQueue);
350 startService(new Intent(OperationsService.this, OperationsService.class));
351 return itemToQueue.second.hashCode();
352
353 } else {
354 return Long.MAX_VALUE;
355 }
356 }
357
358
359 public boolean dispatchResultIfFinished(int operationId, OnRemoteOperationListener listener) {
360 Pair<RemoteOperation, RemoteOperationResult> undispatched =
361 mUndispatchedFinishedOperations.remove(operationId);
362 if (undispatched != null) {
363 listener.onRemoteOperationFinish(undispatched.first, undispatched.second);
364 return true;
365 //Log_OC.wtf(TAG, "Sending callback later");
366 } else {
367 if (!mServiceHandler.mPendingOperations.isEmpty()) {
368 return true;
369 } else {
370 return false;
371 }
372 //Log_OC.wtf(TAG, "Not finished yet");
373 }
374 }
375
376
377 /**
378 * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download.
379 *
380 * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download.
381 *
382 * @param account ownCloud account where the remote file is stored.
383 * @param file A file that could be affected
384 */
385 /*
386 public boolean isSynchronizing(Account account, String remotePath) {
387 return mSyncFolderHandler.isSynchronizing(account, remotePath);
388 }
389 */
390
391 }
392
393
394 /**
395 * SyncFolder worker. Performs the pending operations in the order they were requested.
396 *
397 * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.
398 */
399 private static class SyncFolderHandler extends Handler {
400
401 // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
402
403 OperationsService mService;
404
405 private ConcurrentMap<String,SynchronizeFolderOperation> mPendingOperations =
406 new ConcurrentHashMap<String,SynchronizeFolderOperation>();
407 private OwnCloudClient mOwnCloudClient = null;
408 private FileDataStorageManager mStorageManager;
409 private SynchronizeFolderOperation mCurrentSyncOperation;
410
411
412 public SyncFolderHandler(Looper looper, OperationsService service) {
413 super(looper);
414 if (service == null) {
415 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
416 }
417 mService = service;
418 }
419
420
421 public boolean isSynchronizing(Account account, String remotePath) {
422 if (account == null || remotePath == null) return false;
423 String targetKey = buildRemoteName(account, remotePath);
424 synchronized (mPendingOperations) {
425 // TODO - this can be slow when synchronizing a big tree - need a better data structure
426 Iterator<String> it = mPendingOperations.keySet().iterator();
427 boolean found = false;
428 while (it.hasNext() && !found) {
429 found = it.next().startsWith(targetKey);
430 }
431 return found;
432 }
433 }
434
435
436 @Override
437 public void handleMessage(Message msg) {
438 Pair<Account, String> itemSyncKey = (Pair<Account, String>) msg.obj;
439 doOperation(itemSyncKey.first, itemSyncKey.second);
440 mService.stopSelf(msg.arg1);
441 }
442
443
444 /**
445 * Performs the next operation in the queue
446 */
447 private void doOperation(Account account, String remotePath) {
448
449 String syncKey = buildRemoteName(account,remotePath);
450
451 synchronized(mPendingOperations) {
452 mCurrentSyncOperation = mPendingOperations.get(syncKey);
453 }
454
455 if (mCurrentSyncOperation != null) {
456 RemoteOperationResult result = null;
457
458 try {
459
460 OwnCloudAccount ocAccount = new OwnCloudAccount(account, mService);
461 mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
462 getClientFor(ocAccount, mService);
463 mStorageManager = new FileDataStorageManager(
464 account,
465 mService.getContentResolver()
466 );
467
468 result = mCurrentSyncOperation.execute(mOwnCloudClient, mStorageManager);
469
470 } catch (AccountsException e) {
471 Log_OC.e(TAG, "Error while trying to get autorization", e);
472 } catch (IOException e) {
473 Log_OC.e(TAG, "Error while trying to get autorization", e);
474 } finally {
475 synchronized (mPendingOperations) {
476 mPendingOperations.remove(syncKey);
477 /*
478 SynchronizeFolderOperation checkedOp = mCurrentSyncOperation;
479 String checkedKey = syncKey;
480 while (checkedOp.getPendingChildrenCount() <= 0) {
481 // while (!checkedOp.hasChildren()) {
482 mPendingOperations.remove(checkedKey);
483 String parentKey = buildRemoteName(account, (new File(checkedOp.getFolderPath())).getParent());
484 // String parentKey = buildRemoteName(account, checkedOp.getParentPath());
485 SynchronizeFolderOperation parentOp = mPendingOperations.get(parentKey);
486 if (parentOp != null) {
487 parentOp.decreasePendingChildrenCount();
488 }
489 }
490 */
491 }
492
493 mService.dispatchResultToOperationListeners(null, mCurrentSyncOperation, result);
494
495 sendBroadcastFinishedSyncFolder(account, remotePath, result.isSuccess());
496 }
497 }
498 }
499
500 public void add(Account account, String remotePath, SynchronizeFolderOperation syncFolderOperation){
501 String syncKey = buildRemoteName(account,remotePath);
502 mPendingOperations.putIfAbsent(syncKey,syncFolderOperation);
503 }
504
505 /**
506 * Cancels sync operations.
507 * @param account Owncloud account where the remote file is stored.
508 * @param file File OCFile
509 */
510 public void cancel(Account account, OCFile file){
511 SynchronizeFolderOperation syncOperation = null;
512 String targetKey = buildRemoteName(account, file.getRemotePath());
513 ArrayList<String> keyItems = new ArrayList<String>();
514 synchronized (mPendingOperations) {
515 if (file.isFolder()) {
516 Log_OC.d(TAG, "Canceling pending sync operations");
517 Iterator<String> it = mPendingOperations.keySet().iterator();
518 boolean found = false;
519 while (it.hasNext()) {
520 String keySyncOperation = it.next();
521 found = keySyncOperation.startsWith(targetKey);
522 if (found) {
523 keyItems.add(keySyncOperation);
524 }
525 }
526
527 } else {
528 // this is not really expected...
529 Log_OC.d(TAG, "Canceling sync operation");
530 keyItems.add(buildRemoteName(account, file.getRemotePath()));
531 }
532 for (String item: keyItems) {
533 syncOperation = mPendingOperations.remove(item);
534 if (syncOperation != null) {
535 syncOperation.cancel();
536 }
537 }
538 }
539
540 //sendBroadcastFinishedSyncFolder(account, file.getRemotePath());
541 }
542
543 /**
544 * Builds a key from the account and file to download
545 *
546 * @param account Account where the file to download is stored
547 * @param path File path
548 */
549 private String buildRemoteName(Account account, String path) {
550 return account.name + path;
551 }
552
553
554 /**
555 * TODO review this method when "folder synchronization" replaces "folder download"; this is a fast and ugly
556 * patch.
557 */
558 private void sendBroadcastNewSyncFolder(Account account, String remotePath) {
559 Intent added = new Intent(FileDownloader.getDownloadAddedMessage());
560 added.putExtra(FileDownloader.ACCOUNT_NAME, account.name);
561 added.putExtra(FileDownloader.EXTRA_REMOTE_PATH, remotePath);
562 added.putExtra(FileDownloader.EXTRA_FILE_PATH, FileStorageUtils.getSavePath(account.name) + remotePath);
563 mService.sendStickyBroadcast(added);
564 }
565
566 /**
567 * TODO review this method when "folder synchronization" replaces "folder download"; this is a fast and ugly
568 * patch.
569 */
570 private void sendBroadcastFinishedSyncFolder(Account account, String remotePath, boolean success) {
571 Intent finished = new Intent(FileDownloader.getDownloadFinishMessage());
572 finished.putExtra(FileDownloader.ACCOUNT_NAME, account.name);
573 finished.putExtra(FileDownloader.EXTRA_REMOTE_PATH, remotePath);
574 finished.putExtra(FileDownloader.EXTRA_FILE_PATH, FileStorageUtils.getSavePath(account.name) + remotePath);
575 finished.putExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, success);
576 mService.sendStickyBroadcast(finished);
577 }
578
579
580 }
581
582
583 /**
584 * Operations worker. Performs the pending operations in the order they were requested.
585 *
586 * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.
587 */
588 private static class ServiceHandler extends Handler {
589 // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
590
591
592 OperationsService mService;
593
594
595 private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations =
596 new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
597 private RemoteOperation mCurrentOperation = null;
598 private Target mLastTarget = null;
599 private OwnCloudClient mOwnCloudClient = null;
600 private FileDataStorageManager mStorageManager;
601
602
603 public ServiceHandler(Looper looper, OperationsService service) {
604 super(looper);
605 if (service == null) {
606 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
607 }
608 mService = service;
609 }
610
611 @Override
612 public void handleMessage(Message msg) {
613 nextOperation();
614 mService.stopSelf(msg.arg1);
615 }
616
617
618 /**
619 * Performs the next operation in the queue
620 */
621 private void nextOperation() {
622
623 //Log_OC.wtf(TAG, "nextOperation init" );
624
625 Pair<Target, RemoteOperation> next = null;
626 synchronized(mPendingOperations) {
627 next = mPendingOperations.peek();
628 }
629
630 if (next != null) {
631
632 mCurrentOperation = next.second;
633 RemoteOperationResult result = null;
634 try {
635 /// prepare client object to send the request to the ownCloud server
636 if (mLastTarget == null || !mLastTarget.equals(next.first)) {
637 mLastTarget = next.first;
638 if (mLastTarget.mAccount != null) {
639 OwnCloudAccount ocAccount = new OwnCloudAccount(mLastTarget.mAccount, mService);
640 mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
641 getClientFor(ocAccount, mService);
642 mStorageManager = new FileDataStorageManager(
643 mLastTarget.mAccount,
644 mService.getContentResolver()
645 );
646 } else {
647 OwnCloudCredentials credentials = null;
648 if (mLastTarget.mUsername != null &&
649 mLastTarget.mUsername.length() > 0) {
650 credentials = OwnCloudCredentialsFactory.newBasicCredentials(
651 mLastTarget.mUsername,
652 mLastTarget.mPassword); // basic
653
654 } else if (mLastTarget.mAuthToken != null &&
655 mLastTarget.mAuthToken.length() > 0) {
656 credentials = OwnCloudCredentialsFactory.newBearerCredentials(
657 mLastTarget.mAuthToken); // bearer token
658
659 } else if (mLastTarget.mCookie != null &&
660 mLastTarget.mCookie.length() > 0) {
661 credentials = OwnCloudCredentialsFactory.newSamlSsoCredentials(
662 mLastTarget.mCookie); // SAML SSO
663 }
664 OwnCloudAccount ocAccount = new OwnCloudAccount(
665 mLastTarget.mServerUrl, credentials);
666 mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
667 getClientFor(ocAccount, mService);
668 mStorageManager = null;
669 }
670 }
671
672 /// perform the operation
673 if (mCurrentOperation instanceof SyncOperation) {
674 result = ((SyncOperation)mCurrentOperation).execute(mOwnCloudClient, mStorageManager);
675 } else {
676 result = mCurrentOperation.execute(mOwnCloudClient);
677 }
678
679 } catch (AccountsException e) {
680 if (mLastTarget.mAccount == null) {
681 Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
682 } else {
683 Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
684 }
685 result = new RemoteOperationResult(e);
686
687 } catch (IOException e) {
688 if (mLastTarget.mAccount == null) {
689 Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
690 } else {
691 Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
692 }
693 result = new RemoteOperationResult(e);
694 } catch (Exception e) {
695 if (mLastTarget.mAccount == null) {
696 Log_OC.e(TAG, "Unexpected error for a NULL account", e);
697 } else {
698 Log_OC.e(TAG, "Unexpected error for " + mLastTarget.mAccount.name, e);
699 }
700 result = new RemoteOperationResult(e);
701
702 } finally {
703 synchronized(mPendingOperations) {
704 mPendingOperations.poll();
705 }
706 }
707
708 //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
709 mService.dispatchResultToOperationListeners(mLastTarget, mCurrentOperation, result);
710 }
711 }
712
713
714
715 }
716
717
718 /**
719 * Creates a new operation, as described by operationIntent.
720 *
721 * TODO - move to ServiceHandler (probably)
722 *
723 * @param operationIntent Intent describing a new operation to queue and execute.
724 * @return Pair with the new operation object and the information about its target server.
725 */
726 private Pair<Target , RemoteOperation> newOperation(Intent operationIntent) {
727 RemoteOperation operation = null;
728 Target target = null;
729 try {
730 if (!operationIntent.hasExtra(EXTRA_ACCOUNT) &&
731 !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
732 Log_OC.e(TAG, "Not enough information provided in intent");
733
734 } else {
735 Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT);
736 String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL);
737 String username = operationIntent.getStringExtra(EXTRA_USERNAME);
738 String password = operationIntent.getStringExtra(EXTRA_PASSWORD);
739 String authToken = operationIntent.getStringExtra(EXTRA_AUTH_TOKEN);
740 String cookie = operationIntent.getStringExtra(EXTRA_COOKIE);
741 target = new Target(
742 account,
743 (serverUrl == null) ? null : Uri.parse(serverUrl),
744 username,
745 password,
746 authToken,
747 cookie
748 );
749
750 String action = operationIntent.getAction();
751 if (action.equals(ACTION_CREATE_SHARE)) { // Create Share
752 String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
753 Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
754 if (remotePath.length() > 0) {
755 operation = new CreateShareOperation(OperationsService.this, remotePath, ShareType.PUBLIC_LINK,
756 "", false, "", 1, sendIntent);
757 }
758
759 } else if (action.equals(ACTION_UNSHARE)) { // Unshare file
760 String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
761 if (remotePath.length() > 0) {
762 operation = new UnshareLinkOperation(
763 remotePath,
764 OperationsService.this);
765 }
766
767 } else if (action.equals(ACTION_GET_SERVER_INFO)) {
768 // check OC server and get basic information from it
769 operation = new GetServerInfoOperation(serverUrl, OperationsService.this);
770
771 } else if (action.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN)) {
772 /// GET ACCESS TOKEN to the OAuth server
773 String oauth2QueryParameters =
774 operationIntent.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS);
775 operation = new OAuth2GetAccessToken(
776 getString(R.string.oauth2_client_id),
777 getString(R.string.oauth2_redirect_uri),
778 getString(R.string.oauth2_grant_type),
779 oauth2QueryParameters);
780
781 } else if (action.equals(ACTION_EXISTENCE_CHECK)) {
782 // Existence Check
783 String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
784 boolean successIfAbsent = operationIntent.getBooleanExtra(EXTRA_SUCCESS_IF_ABSENT, false);
785 operation = new ExistenceCheckRemoteOperation(remotePath, OperationsService.this, successIfAbsent);
786
787 } else if (action.equals(ACTION_GET_USER_NAME)) {
788 // Get User Name
789 operation = new GetRemoteUserNameOperation();
790
791 } else if (action.equals(ACTION_RENAME)) {
792 // Rename file or folder
793 String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
794 String newName = operationIntent.getStringExtra(EXTRA_NEWNAME);
795 operation = new RenameFileOperation(remotePath, newName);
796
797 } else if (action.equals(ACTION_REMOVE)) {
798 // Remove file or folder
799 String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
800 boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false);
801 operation = new RemoveFileOperation(remotePath, onlyLocalCopy);
802
803 } else if (action.equals(ACTION_CREATE_FOLDER)) {
804 // Create Folder
805 String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
806 boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true);
807 operation = new CreateFolderOperation(remotePath, createFullPath);
808
809 } else if (action.equals(ACTION_SYNC_FILE)) {
810 // Sync file
811 String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
812 boolean syncFileContents = operationIntent.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS, true);
813 operation = new SynchronizeFileOperation(
814 remotePath, account, syncFileContents, getApplicationContext()
815 );
816
817 } else if (action.equals(ACTION_SYNC_FOLDER)) {
818 // Sync file
819 String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
820 operation = new SynchronizeFolderOperation(
821 this, // TODO remove this dependency from construction time
822 remotePath,
823 account,
824 System.currentTimeMillis() // TODO remove this dependency from construction time
825 );
826
827 } else if (action.equals(ACTION_MOVE_FILE)) {
828 // Move file/folder
829 String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
830 String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
831 operation = new MoveFileOperation(remotePath,newParentPath,account);
832 }
833
834 }
835
836 } catch (IllegalArgumentException e) {
837 Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
838 operation = null;
839 }
840
841 if (operation != null) {
842 return new Pair<Target , RemoteOperation>(target, operation);
843 } else {
844 return null;
845 }
846 }
847
848
849 /**
850 * Sends a broadcast when a new operation is added to the queue.
851 *
852 * Local broadcasts are only delivered to activities in the same process, but can't be done sticky :\
853 *
854 * @param target Account or URL pointing to an OC server.
855 * @param operation Added operation.
856 */
857 private void sendBroadcastNewOperation(Target target, RemoteOperation operation) {
858 Intent intent = new Intent(ACTION_OPERATION_ADDED);
859 if (target.mAccount != null) {
860 intent.putExtra(EXTRA_ACCOUNT, target.mAccount);
861 } else {
862 intent.putExtra(EXTRA_SERVER_URL, target.mServerUrl);
863 }
864 //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
865 //lbm.sendBroadcast(intent);
866 sendStickyBroadcast(intent);
867 }
868
869
870 // TODO - maybe add a notification for real start of operations
871
872 /**
873 * Sends a LOCAL broadcast when an operations finishes in order to the interested activities can update their view
874 *
875 * Local broadcasts are only delivered to activities in the same process.
876 *
877 * @param target Account or URL pointing to an OC server.
878 * @param operation Finished operation.
879 * @param result Result of the operation.
880 */
881 private void sendBroadcastOperationFinished(Target target, RemoteOperation operation, RemoteOperationResult result) {
882 Intent intent = new Intent(ACTION_OPERATION_FINISHED);
883 intent.putExtra(EXTRA_RESULT, result);
884 if (target.mAccount != null) {
885 intent.putExtra(EXTRA_ACCOUNT, target.mAccount);
886 } else {
887 intent.putExtra(EXTRA_SERVER_URL, target.mServerUrl);
888 }
889 //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
890 //lbm.sendBroadcast(intent);
891 sendStickyBroadcast(intent);
892 }
893
894
895 /**
896 * Notifies the currently subscribed listeners about the end of an operation.
897 *
898 * @param target Account or URL pointing to an OC server.
899 * @param operation Finished operation.
900 * @param result Result of the operation.
901 */
902 private void dispatchResultToOperationListeners(
903 Target target, final RemoteOperation operation, final RemoteOperationResult result) {
904 int count = 0;
905 Iterator<OnRemoteOperationListener> listeners = mOperationsBinder.mBoundListeners.keySet().iterator();
906 while (listeners.hasNext()) {
907 final OnRemoteOperationListener listener = listeners.next();
908 final Handler handler = mOperationsBinder.mBoundListeners.get(listener);
909 if (handler != null) {
910 handler.post(new Runnable() {
911 @Override
912 public void run() {
913 listener.onRemoteOperationFinish(operation, result);
914 }
915 });
916 count += 1;
917 }
918 }
919 if (count == 0) {
920 //mOperationResults.put(operation.hashCode(), result);
921 Pair<RemoteOperation, RemoteOperationResult> undispatched =
922 new Pair<RemoteOperation, RemoteOperationResult>(operation, result);
923 mUndispatchedFinishedOperations.put(operation.hashCode(), undispatched);
924 }
925 Log_OC.d(TAG, "Called " + count + " listeners");
926 }
927 }