Merge branch 'develop' into cancel_transfer_for_deleted_users
[pub/Android/ownCloud.git] / src / com / owncloud / android / files / services / FileDownloader.java
index 5299b62..a126028 100644 (file)
@@ -1,6 +1,6 @@
 /* ownCloud Android client application
  *   Copyright (C) 2012 Bartek Przybylski
- *   Copyright (C) 2012-2013 ownCloud Inc.
+ *   Copyright (C) 2012-2015 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,
@@ -21,13 +21,14 @@ package com.owncloud.android.files.services;
 import java.io.File;
 import java.io.IOException;
 import java.util.AbstractList;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Vector;
+import java.util.concurrent.ConcurrentMap;
 
 import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
@@ -63,19 +64,19 @@ import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.support.v4.app.NotificationCompat;
+import android.util.Pair;
 
 public class FileDownloader extends Service implements OnDatatransferProgressListener {
     
     public static final String EXTRA_ACCOUNT = "ACCOUNT";
     public static final String EXTRA_FILE = "FILE";
 
-    public static final String ACTION_CANCEL_FILE_DOWNLOAD = "CANCEL_FILE_DOWNLOAD";
-
     private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
     private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
     public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";    
     public static final String EXTRA_FILE_PATH = "FILE_PATH";
     public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
+    public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
     public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
     
     private static final String TAG = "FileDownloader";
@@ -84,7 +85,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     private ServiceHandler mServiceHandler;
     private IBinder mBinder;
     private OwnCloudClient mDownloadClient = null;
-    private Account mLastAccount = null;
+    private Account mCurrentAccount = null;
     private FileDataStorageManager mStorageManager;
     
     private IndexedForest<DownloadFileOperation> mPendingDownloads = new IndexedForest<DownloadFileOperation>();
@@ -97,11 +98,11 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
 
     
     public static String getDownloadAddedMessage() {
-        return FileDownloader.class.getName().toString() + DOWNLOAD_ADDED_MESSAGE;
+        return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
     }
     
     public static String getDownloadFinishMessage() {
-        return FileDownloader.class.getName().toString() + DOWNLOAD_FINISH_MESSAGE;
+        return FileDownloader.class.getName() + DOWNLOAD_FINISH_MESSAGE;
     }
     
     /**
@@ -110,15 +111,31 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     @Override
     public void onCreate() {
         super.onCreate();
+        Log_OC.d(TAG, "Creating service");
         mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-        HandlerThread thread = new HandlerThread("FileDownloaderThread",
-                Process.THREAD_PRIORITY_BACKGROUND);
+        HandlerThread thread = new HandlerThread("FileDownloaderThread", Process.THREAD_PRIORITY_BACKGROUND);
         thread.start();
         mServiceLooper = thread.getLooper();
         mServiceHandler = new ServiceHandler(mServiceLooper, this);
         mBinder = new FileDownloaderBinder();
     }
 
+
+    /**
+     * Service clean up
+     */
+    @Override
+    public void onDestroy() {
+        Log_OC.v(TAG, "Destroying service" );
+        mBinder = null;
+        mServiceHandler = null;
+        mServiceLooper.quit();
+        mServiceLooper = null;
+        mNotificationManager = null;
+        super.onDestroy();
+    }
+
+
     /**
      * Entry point to add one or several files to the queue of downloads.
      *
@@ -127,6 +144,8 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
      */
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
+        Log_OC.d(TAG, "Starting command with id " + startId);
+
         if (    !intent.hasExtra(EXTRA_ACCOUNT) ||
                 !intent.hasExtra(EXTRA_FILE)
            ) {
@@ -136,24 +155,25 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             final Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
             final OCFile file = intent.getParcelableExtra(EXTRA_FILE);
 
-            if (ACTION_CANCEL_FILE_DOWNLOAD.equals(intent.getAction())) {
-
-                new Thread(new Runnable() {
-                    public void run() {
-                        // Cancel the download
-                        cancel(account, file);
-                    }
-                }).start();
-
-            } else {
+            /*Log_OC.v(
+                    "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                    "Received request to download file"
+            );*/
 
                 AbstractList<String> requestedDownloads = new Vector<String>();
                 try {
                     DownloadFileOperation newDownload = new DownloadFileOperation(account, file);
-                    String downloadKey = mPendingDownloads.putIfAbsent(account, file.getRemotePath(), newDownload);
                     newDownload.addDatatransferProgressListener(this);
                     newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
+                    Pair<String, String> putResult = mPendingDownloads.putIfAbsent(
+                        account, file.getRemotePath(), newDownload
+                    );
+                    String downloadKey = putResult.first;
                     requestedDownloads.add(downloadKey);
+                    /*Log_OC.v(
+                        "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Download on " + file.getRemotePath() + " added to queue"
+                    );*/
 
                     // Store file on db with state 'downloading'
                     /*
@@ -163,7 +183,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                     storageManager.saveFile(file);
                     */
 
-                    sendBroadcastNewDownload(newDownload);
+                    sendBroadcastNewDownload(newDownload, putResult.second);
 
                 } catch (IllegalArgumentException e) {
                     Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
@@ -176,7 +196,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                     msg.obj = requestedDownloads;
                     mServiceHandler.sendMessage(msg);
                 }
-            }
+            //}
         }
 
         return START_NOT_STICKY;
@@ -227,19 +247,65 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
          * @param file          A file in the queue of pending downloads
          */
         public void cancel(Account account, OCFile file) {
-            DownloadFileOperation download = null;
-            download = mPendingDownloads.remove(account, file.getRemotePath());
+            /*Log_OC.v(
+                    "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                    "Received request to cancel download of " + file.getRemotePath()
+            );
+            Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                    "Removing download of " + file.getRemotePath());*/
+            Pair<DownloadFileOperation, String> removeResult =
+                    mPendingDownloads.remove(account, file.getRemotePath());
+            DownloadFileOperation download = removeResult.first;
             if (download != null) {
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Canceling returned download of " + file.getRemotePath());*/
                 download.cancel();
             } else {
-                // TODO synchronize
-                if (mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
-                        account.name.equals(mLastAccount)) {
+                if (mCurrentDownload != null && mCurrentAccount != null &&
+                        mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
+                        account.name.equals(mCurrentAccount.name)) {
+                    /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                            "Canceling current sync as descendant: " + mCurrentDownload.getRemotePath());*/
                     mCurrentDownload.cancel();
                 }
             }
         }
-        
+
+        /**
+         * Cancels a pending or current upload for an account
+         *
+         * @param account Owncloud accountName where the remote file will be stored.
+         */
+        public void cancel(Account account) {
+            Log_OC.d(TAG, "Account= " + account.name);
+
+            if (mCurrentDownload != null) {
+                Log_OC.d(TAG, "Current Download Account= " + mCurrentDownload.getAccount().name);
+                if (mCurrentDownload.getAccount().name.equals(account.name)) {
+                    mCurrentDownload.cancel();
+                }
+            }
+            // Cancel pending downloads
+            ConcurrentMap downloadsAccount = mPendingDownloads.get(account);
+            Iterator<String> it = downloadsAccount.keySet().iterator();
+            Log_OC.d(TAG, "Number of pending downloads= " + downloadsAccount.size());
+            while (it.hasNext()) {
+                String key = it.next();
+                Log_OC.d(TAG, "download CANCELLED " + key);
+                if (key.startsWith(account.name)) {
+                    DownloadFileOperation download;
+                    synchronized (mPendingDownloads) {
+                        download = mPendingDownloads.get(key);
+                        if (download != null) {
+                            String remotePath = download.getRemotePath();
+                            if (mPendingDownloads.contains(account, remotePath)) {
+                                mPendingDownloads.remove(account, remotePath);
+                            }
+                        }
+                    }
+                }
+            }
+        }
         
         public void clearListeners() {
             mBoundListeners.clear();
@@ -305,6 +371,17 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                 boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
             }
         }
+
+        /**
+         * Review downloads and cancel it if its account doesn't exist
+         */
+        public void reviewDownloads() {
+            if (mCurrentDownload != null &&
+                    !AccountUtils.exists(mCurrentDownload.getAccount(), getApplicationContext())) {
+                mCurrentDownload.cancel();
+            }
+            // The rest of downloads are cancelled when they try to start
+        }
         
     }
     
@@ -331,64 +408,83 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             if (msg.obj != null) {
                 Iterator<String> it = requestedDownloads.iterator();
                 while (it.hasNext()) {
-                    mService.downloadFile(it.next());
+                    String next = it.next();
+                    mService.downloadFile(next);
                 }
             }
+            Log_OC.d(TAG, "Stopping after command with id " + msg.arg1);
             mService.stopSelf(msg.arg1);
         }
     }
-    
+
 
     /**
      * Core download method: requests a file to download and stores it.
-     * 
+     *
      * @param downloadKey   Key to access the download to perform, contained in mPendingDownloads 
      */
     private void downloadFile(String downloadKey) {
 
+        /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                "Getting download of " + downloadKey);*/
         mCurrentDownload = mPendingDownloads.get(downloadKey);
 
         if (mCurrentDownload != null) {
-            
-            notifyDownloadStart(mCurrentDownload);
-
-            RemoteOperationResult downloadResult = null;
-            try {
-                /// prepare client object to send the request to the ownCloud server
-                if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) {
-                    mLastAccount = mCurrentDownload.getAccount();
-                    mStorageManager = 
-                            new FileDataStorageManager(mLastAccount, getContentResolver());
-                    OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this);
+            // Detect if the account exists
+            if (AccountUtils.exists(mCurrentDownload.getAccount(), getApplicationContext())) {
+                Log_OC.d(TAG, "Account " + mCurrentDownload.getAccount().toString() + " exists");
+                notifyDownloadStart(mCurrentDownload);
+
+                RemoteOperationResult downloadResult = null;
+                try {
+                    /// prepare client object to send the request to the ownCloud server
+                    if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentDownload.getAccount())) {
+                        mCurrentAccount = mCurrentDownload.getAccount();
+                        mStorageManager = new FileDataStorageManager(
+                                mCurrentAccount,
+                                getContentResolver()
+                        );
+                    }   // else, reuse storage manager from previous operation
+
+                    // always get client from client manager, to get fresh credentials in case of update
+                    OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this);
                     mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
                             getClientFor(ocAccount, this);
-                }
 
-                /// perform the download
-                downloadResult = mCurrentDownload.execute(mDownloadClient);
-                if (downloadResult.isSuccess()) {
-                    saveDownloadedFile();
-                /*} else {
-                    updateUnsuccessfulDownloadedFile();
-                    */
+
+                    /// perform the download
+                    /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Executing download of " + mCurrentDownload.getRemotePath());*/
+                    downloadResult = mCurrentDownload.execute(mDownloadClient);
+                    if (downloadResult.isSuccess()) {
+                        saveDownloadedFile();
+                    }
+
+                } catch (AccountsException e) {
+                    Log_OC.e(TAG, "Error while trying to get authorization for " + mCurrentAccount.name, e);
+                    downloadResult = new RemoteOperationResult(e);
+                } catch (IOException e) {
+                    Log_OC.e(TAG, "Error while trying to get authorization for " + mCurrentAccount.name, e);
+                    downloadResult = new RemoteOperationResult(e);
+
+                } finally {
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Removing payload " + mCurrentDownload.getRemotePath());*/
+
+                    Pair<DownloadFileOperation, String> removeResult =
+                            mPendingDownloads.removePayload(mCurrentAccount, mCurrentDownload.getRemotePath());
+
+                    /// notify result
+                    notifyDownloadResult(mCurrentDownload, downloadResult);
+
+                    sendBroadcastDownloadFinished(mCurrentDownload, downloadResult, removeResult.second);
                 }
-            
-            } catch (AccountsException e) {
-                Log_OC.e(TAG, "Error while trying to get authorization for " + mLastAccount.name, e);
-                downloadResult = new RemoteOperationResult(e);
-            } catch (IOException e) {
-                Log_OC.e(TAG, "Error while trying to get authorization for " + mLastAccount.name, e);
-                downloadResult = new RemoteOperationResult(e);
-                
-            } finally {
-                mPendingDownloads.remove(mLastAccount, mCurrentDownload.getRemotePath());
-            }
+            } else {
+                // Cancel the transfer
+                Log_OC.d(TAG, "Account " + mCurrentDownload.getAccount().toString() + " doesn't exist");
+                cancelDownloadsForAccount(mCurrentDownload.getAccount());
 
-            
-            /// notify result
-            notifyDownloadResult(mCurrentDownload, downloadResult);
-            
-            sendBroadcastDownloadFinished(mCurrentDownload, downloadResult);
+            }
         }
     }
 
@@ -409,7 +505,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
         file.setStoragePath(mCurrentDownload.getSavePath());
         file.setFileLength((new File(mCurrentDownload.getSavePath()).length()));
         file.setRemoteId(mCurrentDownload.getFile().getRemoteId());
-        //file.setDownloading(false);
         mStorageManager.saveFile(file);
         mStorageManager.triggerMediaScan(file.getStoragePath());
     }
@@ -523,8 +618,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                     .setContentIntent(PendingIntent.getActivity(
                         this, (int) System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT));
                 
-                mDownloadClient = null;  // grant that future retries on the same account will get the fresh credentials
-                
             } else {
                 // TODO put something smart in showDetailsIntent
                 Intent   showDetailsIntent = new Intent();
@@ -554,15 +647,22 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     /**
      * Sends a broadcast when a download finishes in order to the interested activities can update their view
      * 
-     * @param download          Finished download operation
-     * @param downloadResult    Result of the download operation
+     * @param download                  Finished download operation
+     * @param downloadResult            Result of the download operation
+     * @param unlinkedFromRemotePath    Path in the downloads tree where the download was unlinked from
      */
-    private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) {
+    private void sendBroadcastDownloadFinished(
+            DownloadFileOperation download,
+            RemoteOperationResult downloadResult,
+            String unlinkedFromRemotePath) {
         Intent end = new Intent(getDownloadFinishMessage());
         end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
         end.putExtra(ACCOUNT_NAME, download.getAccount().name);
         end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
         end.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+        if (unlinkedFromRemotePath != null) {
+            end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
+        }
         sendStickyBroadcast(end);
     }
     
@@ -570,59 +670,44 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     /**
      * Sends a broadcast when a new download is added to the queue.
      * 
-     * @param download          Added download operation
+     * @param download              Added download operation
+     * @param linkedToRemotePath    Path in the downloads tree where the download was linked to
      */
-    private void sendBroadcastNewDownload(DownloadFileOperation download) {
+    private void sendBroadcastNewDownload(DownloadFileOperation download, String linkedToRemotePath) {
         Intent added = new Intent(getDownloadAddedMessage());
         added.putExtra(ACCOUNT_NAME, download.getAccount().name);
         added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
         added.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+        added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath);
         sendStickyBroadcast(added);
     }
 
     /**
-     * Cancel operation
-     * @param account       ownCloud account where the remote file is stored.
-     * @param file          File OCFile
+     * Remove downloads of an account
+     * @param account
      */
-    public void cancel(Account account, OCFile file){
-        DownloadFileOperation download = null;
-        //String targetKey = buildKey(account, file.getRemotePath());
-        ArrayList<String> keyItems = new ArrayList<String>();
-        if (file.isFolder()) {
-            Log_OC.d(TAG, "Folder download. Canceling pending downloads (from folder)");
-
-            // TODO
-            /*
-            Iterator<String> it = mPendingDownloads.keySet().iterator();
-            boolean found = false;
-            while (it.hasNext()) {
-                String keyDownloadOperation = it.next();
-                found = keyDownloadOperation.startsWith(targetKey);
-                if (found) {
-                    keyItems.add(keyDownloadOperation);
-                }
-            }
-
-            for (String item: keyItems) {
-                download = mPendingDownloads.remove(item);
-                Log_OC.d(TAG, "Key removed: " + item);
-
-                if (download != null) {
-                    download.cancel();
+    private void cancelDownloadsForAccount(Account account){
+        // Cancel pending downloads
+        ConcurrentMap downloadsAccount = mPendingDownloads.get(account);
+        Iterator<String> it = downloadsAccount.keySet().iterator();
+        Log_OC.d(TAG, "Number of pending downloads= " + downloadsAccount.size());
+        while (it.hasNext()) {
+            String key = it.next();
+            Log_OC.d(TAG, "download CANCELLED " + key);
+            if (key.startsWith(account.name)) {
+                DownloadFileOperation download;
+                synchronized (mPendingDownloads) {
+                    download = mPendingDownloads.get(key);
+                    if (download != null) {
+                        String remotePath = download.getRemotePath();
+                        if (mPendingDownloads.contains(account, remotePath)) {
+                            mPendingDownloads.remove(account, remotePath);
+                        }
+                    }
                 }
             }
-
-            */
-
-        } else {
-            // this is not really expected...
-            Log_OC.d(TAG, "Canceling file download");
-            download = mPendingDownloads.remove(account, file.getRemotePath());
-            if (download != null) {
-                download.cancel();
-            }
         }
     }
 
+
 }