Merge pull request #1119 from owncloud/strip-index-php-from-server-url
[pub/Android/ownCloud.git] / src / com / owncloud / android / files / services / FileUploader.java
index c5b9217..7c953b8 100644 (file)
@@ -1,4 +1,6 @@
-/* ownCloud Android client application
+/**
+ *   ownCloud Android client application
+ *
  *   Copyright (C) 2012 Bartek Przybylski
  *   Copyright (C) 2012-2015 ownCloud Inc.
  *
 package com.owncloud.android.files.services;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.AbstractList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Vector;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.accounts.AccountsException;
+import android.accounts.OnAccountsUpdateListener;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -43,6 +42,7 @@ import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.support.v4.app.NotificationCompat;
+import android.util.Pair;
 import android.webkit.MimeTypeMap;
 
 import com.owncloud.android.R;
@@ -54,9 +54,7 @@ import com.owncloud.android.db.DbHandler;
 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.accounts.AccountUtils.Constants;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
-import com.owncloud.android.lib.common.operations.OperationCancelledException;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
@@ -77,14 +75,15 @@ import com.owncloud.android.utils.ErrorMessageAdapter;
 import com.owncloud.android.utils.UriUtils;
 
 
-
-public class FileUploader extends Service implements OnDatatransferProgressListener {
+public class FileUploader extends Service
+        implements OnDatatransferProgressListener, OnAccountsUpdateListener {
 
     private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
     public static final String EXTRA_UPLOAD_RESULT = "RESULT";
     public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
     public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
     public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
+    public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
     public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
 
     public static final String KEY_FILE = "FILE";
@@ -112,10 +111,10 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     private ServiceHandler mServiceHandler;
     private IBinder mBinder;
     private OwnCloudClient mUploadClient = null;
-    private Account mLastAccount = null;
+    private Account mCurrentAccount = null;
     private FileDataStorageManager mStorageManager;
 
-    private ConcurrentMap<String, UploadFileOperation> mPendingUploads = new ConcurrentHashMap<String, UploadFileOperation>();
+    private IndexedForest<UploadFileOperation> mPendingUploads = new IndexedForest<UploadFileOperation>();
     private UploadFileOperation mCurrentUpload = null;
 
     private NotificationManager mNotificationManager;
@@ -127,21 +126,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
 
 
     public static String getUploadFinishMessage() {
-        return FileUploader.class.getName().toString() + UPLOAD_FINISH_MESSAGE;
-    }
-
-    /**
-     * Builds a key for mPendingUploads from the account and file to upload
-     *
-     * @param account   Account where the file to upload is stored
-     * @param file      File to upload
-     */
-    private String buildRemoteName(Account account, OCFile file) {
-        return account.name + file.getRemotePath();
-    }
-
-    private String buildRemoteName(Account account, String remotePath) {
-        return account.name + remotePath;
+        return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE;
     }
 
     /**
@@ -151,6 +136,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
      *            server.
      * @return 'True' if the ownCloud server with version supports chunked
      *         uploads.
+     *
+     * TODO - move to OCClient
      */
     private static boolean chunkedUploadIsSupported(OwnCloudVersion version) {
         return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0);
@@ -162,15 +149,40 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     @Override
     public void onCreate() {
         super.onCreate();
-        Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
+        Log_OC.d(TAG, "Creating service");
         mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-        HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND);
+        HandlerThread thread = new HandlerThread("FileUploaderThread",
+                Process.THREAD_PRIORITY_BACKGROUND);
         thread.start();
         mServiceLooper = thread.getLooper();
         mServiceHandler = new ServiceHandler(mServiceLooper, this);
         mBinder = new FileUploaderBinder();
+
+        // add AccountsUpdatedListener
+        AccountManager am = AccountManager.get(getApplicationContext());
+        am.addOnAccountsUpdatedListener(this, null, false);
+    }
+
+    /**
+     * Service clean up
+     */
+    @Override
+    public void onDestroy() {
+        Log_OC.v(TAG, "Destroying service" );
+        mBinder = null;
+        mServiceHandler = null;
+        mServiceLooper.quit();
+        mServiceLooper = null;
+        mNotificationManager = null;
+
+        // remove AccountsUpdatedListener
+        AccountManager am = AccountManager.get(getApplicationContext());
+        am.removeOnAccountsUpdatedListener(this);
+
+        super.onDestroy();
     }
 
+
     /**
      * Entry point to add one or several files to the queue of uploads.
      *
@@ -180,6 +192,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
      */
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
+        Log_OC.d(TAG, "Starting command with id " + startId);
+
         if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE)
                 || !(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
             Log_OC.e(TAG, "Not enough information provided in intent");
@@ -200,7 +214,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         if (uploadType == UPLOAD_SINGLE_FILE) {
 
             if (intent.hasExtra(KEY_FILE)) {
-                files = new OCFile[] { (OCFile) intent.getParcelableExtra(KEY_FILE) };
+                files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) };
 
             } else {
                 localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
@@ -225,7 +239,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
             }
         }
 
-        FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
+        FileDataStorageManager storageManager = new FileDataStorageManager(account,
+                getContentResolver());
 
         boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
         boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false);
@@ -251,8 +266,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
 
             files = new OCFile[localPaths.length];
             for (int i = 0; i < localPaths.length; i++) {
-                files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes != null) ? mimeTypes[i]
-                        : (String) null), storageManager);
+                files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i],
+                        ((mimeTypes != null) ? mimeTypes[i] : null));
                 if (files[i] == null) {
                     // TODO @andomaex add failure Notification
                     return Service.START_NOT_STICKY;
@@ -260,9 +275,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
             }
         }
 
-        AccountManager aMgr = AccountManager.get(this);
-        String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION);
-        OwnCloudVersion ocv = new OwnCloudVersion(version);
+        OwnCloudVersion ocv = AccountUtils.getServerVersion(account);
 
         boolean chunked = FileUploader.chunkedUploadIsSupported(ocv);
         AbstractList<String> requestedUploads = new Vector<String>();
@@ -270,16 +283,23 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         UploadFileOperation newUpload = null;
         try {
             for (int i = 0; i < files.length; i++) {
-                uploadKey = buildRemoteName(account, files[i].getRemotePath());
-                newUpload = new UploadFileOperation(account, files[i], chunked, isInstant, forceOverwrite, localAction,
-                        getApplicationContext());
+                newUpload = new UploadFileOperation(
+                        account,
+                        files[i],
+                        chunked,
+                        isInstant,
+                        forceOverwrite, localAction,
+                        getApplicationContext()
+                );
                 if (isInstant) {
                     newUpload.setRemoteFolderToBeCreated();
                 }
-                mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that the file only upload once time
-
                 newUpload.addDatatransferProgressListener(this);
-                newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder);
+                newUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder);
+                Pair<String, String> putResult = mPendingUploads.putIfAbsent(
+                        account, files[i].getRemotePath(), newUpload
+                );
+                uploadKey = putResult.first;
                 requestedUploads.add(uploadKey);
             }
 
@@ -303,7 +323,6 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
             msg.obj = requestedUploads;
             mServiceHandler.sendMessage(msg);
         }
-        Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
         return Service.START_NOT_STICKY;
     }
 
@@ -328,6 +347,16 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         return false;   // not accepting rebinding (default behaviour)
     }
 
+    @Override
+    public void onAccountsUpdated(Account[] accounts) {
+        // Review current upload, and cancel it if its account doen't exist
+        if (mCurrentUpload != null &&
+                !AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
+            mCurrentUpload.cancel();
+        }
+        // The rest of uploads are cancelled when they try to start
+    }
+
     /**
      * Binder to let client components to perform operations on the queue of
      * uploads.
@@ -337,88 +366,68 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
 
         /**
-         * Map of listeners that will be reported about progress of uploads from a {@link FileUploaderBinder} instance 
+         * Map of listeners that will be reported about progress of uploads from a
+         * {@link FileUploaderBinder} instance
          */
-        private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<String, OnDatatransferProgressListener>();
+        private Map<String, OnDatatransferProgressListener> mBoundListeners =
+                new HashMap<String, OnDatatransferProgressListener>();
 
         /**
          * Cancels a pending or current upload of a remote file.
          *
-         * @param account Owncloud account where the remote file will be stored.
-         * @param file A file in the queue of pending uploads
+         * @param account   ownCloud account where the remote file will be stored.
+         * @param file      A file in the queue of pending uploads
          */
         public void cancel(Account account, OCFile file) {
-            UploadFileOperation upload = null;
-            synchronized (mPendingUploads) {
-                upload = mPendingUploads.remove(buildRemoteName(account, file));
-            }
+            Pair<UploadFileOperation, String> removeResult = mPendingUploads.remove(account, file.getRemotePath());
+            UploadFileOperation upload = removeResult.first;
             if (upload != null) {
-                mCurrentUpload.cancel();
+                upload.cancel();
+            } else {
+                if (mCurrentUpload != null && mCurrentAccount != null &&
+                        mCurrentUpload.getRemotePath().startsWith(file.getRemotePath()) &&
+                        account.name.equals(mCurrentAccount.name)) {
+                    mCurrentUpload.cancel();
+                }
             }
         }
 
         /**
-         * Cancels a pending or current upload for an account
+         * Cancels all the uploads for an account
          *
-         * @param account Owncloud accountName where the remote file will be stored.
+         * @param account   ownCloud account.
          */
         public void cancel(Account account) {
             Log_OC.d(TAG, "Account= " + account.name);
 
             if (mCurrentUpload != null) {
                 Log_OC.d(TAG, "Current Upload Account= " + mCurrentUpload.getAccount().name);
-                if (mCurrentUpload.getAccount().name == account.name) {
+                if (mCurrentUpload.getAccount().name.equals(account.name)) {
                     mCurrentUpload.cancel();
                 }
             }
             // Cancel pending uploads
-            Iterator<String> it = mPendingUploads.keySet().iterator();
-            Log_OC.d(TAG, "Number of pending updloads= "  + mPendingUploads.size());
-            while (it.hasNext()) {
-                String key = it.next();
-                Log_OC.d(TAG, "mPendingUploads CANCELLED " + key);
-                if (key.startsWith(account.name)) {
-                    UploadFileOperation upload;
-                    synchronized (mPendingUploads) {
-                        upload = mPendingUploads.remove(key);
-                    }
-                    if (upload != null) {
-                        mCurrentUpload.cancel();
-                    }
-                }
-            }
+            cancelUploadsForAccount(account);
         }
 
         public void clearListeners() {
             mBoundListeners.clear();
         }
 
+
         /**
          * Returns True when the file described by 'file' is being uploaded to
          * the ownCloud account 'account' or waiting for it
          *
-         * If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload. 
+         * If 'file' is a directory, returns 'true' if some of its descendant files
+         * is uploading or waiting to upload.
          *
          * @param account   ownCloud account where the remote file will be stored.
          * @param file      A file that could be in the queue of pending uploads
          */
         public boolean isUploading(Account account, OCFile file) {
-            if (account == null || file == null)
-                return false;
-            String targetKey = buildRemoteName(account, file);
-            synchronized (mPendingUploads) {
-                if (file.isFolder()) {
-                    // this can be slow if there are many uploads :(
-                    Iterator<String> it = mPendingUploads.keySet().iterator();
-                    boolean found = false;
-                    while (it.hasNext() && !found) {
-                        found = it.next().startsWith(targetKey);
-                    }
-                    return found;
-                } else {
-                    return (mPendingUploads.containsKey(targetKey));
-                }
-            }
+            if (account == null || file == null) return false;
+            return (mPendingUploads.contains(account, file.getRemotePath()));
         }
 
 
@@ -429,7 +438,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
          * @param account       ownCloud account holding the file of interest.
          * @param file          {@link OCFile} of interest for listener.
          */
-        public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+        public void addDatatransferProgressListener (OnDatatransferProgressListener listener,
+                                                     Account account, OCFile file) {
             if (account == null || file == null || listener == null) return;
             String targetKey = buildRemoteName(account, file);
             mBoundListeners.put(targetKey, listener);
@@ -444,7 +454,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
          * @param account       ownCloud account holding the file of interest.
          * @param file          {@link OCFile} of interest for listener.
          */
-        public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+        public void removeDatatransferProgressListener (OnDatatransferProgressListener listener,
+                                                        Account account, OCFile file) {
             if (account == null || file == null || listener == null) return;
             String targetKey = buildRemoteName(account, file);
             if (mBoundListeners.get(targetKey) == listener) {
@@ -454,15 +465,30 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
 
 
         @Override
-        public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
-                                       String fileName) {
+        public void onTransferProgress(long progressRate, long totalTransferredSoFar,
+                                       long totalToTransfer, String fileName) {
             String key = buildRemoteName(mCurrentUpload.getAccount(), mCurrentUpload.getFile());
             OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
             if (boundListener != null) {
-                boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
+                boundListener.onTransferProgress(progressRate, totalTransferredSoFar,
+                        totalToTransfer, fileName);
             }
         }
 
+        /**
+         * Builds a key for the map of listeners.
+         *
+         * TODO remove and replace key with file.getFileId() after changing current policy (upload file, then
+         * add to local database) to better policy (add to local database, then upload)
+         *
+         * @param account       ownCloud account where the file to upload belongs.
+         * @param file          File to upload
+         * @return              Key
+         */
+        private String buildRemoteName(Account account, OCFile file) {
+            return account.name + file.getRemotePath();
+        }
+
     }
 
     /**
@@ -494,6 +520,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
                     mService.uploadFile(it.next());
                 }
             }
+            Log_OC.d(TAG, "Stopping command after id " + msg.arg1);
             mService.stopSelf(msg.arg1);
         }
     }
@@ -501,39 +528,41 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     /**
      * Core upload method: sends the file(s) to upload
      *
-     * @param uploadKey Key to access the upload to perform, contained in
-     *            mPendingUploads
+     * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
      */
     public void uploadFile(String uploadKey) {
 
-        synchronized (mPendingUploads) {
-            mCurrentUpload = mPendingUploads.get(uploadKey);
-        }
+        mCurrentUpload = mPendingUploads.get(uploadKey);
 
         if (mCurrentUpload != null) {
-
             // Detect if the account exists
             if (AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
-                Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() + " exists");
+                Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().name + " exists");
 
                 notifyUploadStart(mCurrentUpload);
 
-                RemoteOperationResult uploadResult = null, grantResult = null;
+                RemoteOperationResult uploadResult = null, grantResult;
 
                 try {
-                    /// prepare client object to send requests to the ownCloud server
-                    if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) {
-                        mLastAccount = mCurrentUpload.getAccount();
-                        mStorageManager =
-                                new FileDataStorageManager(mLastAccount, getContentResolver());
-                        OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this);
-                        mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
-                                getClientFor(ocAccount, this);
-                    }
+                    /// prepare client object to send the request to the ownCloud server
+                    if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentUpload.getAccount())) {
+                        mCurrentAccount = mCurrentUpload.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);
+                    mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                            getClientFor(ocAccount, this);
+
 
                     /// check the existence of the parent folder for the file to upload
                     String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent();
-                    remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
+                    remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
+                            remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
                     grantResult = grantFolderExistence(remoteParentPath);
 
                     /// perform the upload
@@ -543,39 +572,44 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
                         uploadResult = mCurrentUpload.execute(mUploadClient);
                         if (uploadResult.isSuccess()) {
                             saveUploadedFile();
+
+                        } else if (uploadResult.getCode() == ResultCode.SYNC_CONFLICT) {
+                            mStorageManager.saveConflict(mCurrentUpload.getFile(),
+                                    mCurrentUpload.getFile().getEtagInConflict());
                         }
                     } else {
                         uploadResult = grantResult;
                     }
 
-                } catch (AccountsException e) {
-                    Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
-                    uploadResult = new RemoteOperationResult(e);
-
-                } catch (IOException e) {
-                    Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+                } catch (Exception e) {
+                    Log_OC.e(TAG, "Error uploading", e);
                     uploadResult = new RemoteOperationResult(e);
 
                 } finally {
-                    synchronized (mPendingUploads) {
-                        mPendingUploads.remove(uploadKey);
-                        Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map.");
-                    }
-                    if (uploadResult.isException()) {
-                        // enforce the creation of a new client object for next uploads; this grant that a new socket will
-                        // be created in the future if the current exception is due to an abrupt lose of network connection
-                        mUploadClient = null;
+                    Pair<UploadFileOperation, String> removeResult;
+                    if (mCurrentUpload.wasRenamed()) {
+                        removeResult = mPendingUploads.removePayload(
+                                mCurrentAccount,
+                                mCurrentUpload.getOldFile().getRemotePath()
+                        );
+                    } else {
+                        removeResult = mPendingUploads.removePayload(
+                                mCurrentAccount,
+                                mCurrentUpload.getRemotePath()
+                        );
                     }
-                }
 
-                /// notify result
-                notifyUploadResult(uploadResult, mCurrentUpload);
-                sendFinalBroadcast(mCurrentUpload, uploadResult);
+                    /// notify result
+                    notifyUploadResult(mCurrentUpload, uploadResult);
+
+                    sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second);
+                }
 
             } else {
                 // Cancel the transfer
-                Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() + " doesn't exist");
-                cancelUploadForAccount(mCurrentUpload.getAccount().name);
+                Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() +
+                        " doesn't exist");
+                cancelUploadsForAccount(mCurrentUpload.getAccount());
 
             }
         }
@@ -583,14 +617,15 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     }
 
     /**
-     * Checks the existence of the folder where the current file will be uploaded both in the remote server 
-     * and in the local database.
+     * Checks the existence of the folder where the current file will be uploaded both
+     * in the remote server and in the local database.
      *
-     * If the upload is set to enforce the creation of the folder, the method tries to create it both remote
-     * and locally.
+     * If the upload is set to enforce the creation of the folder, the method tries to
+     * create it both remote and locally.
      *
      *  @param  pathToGrant     Full remote path whose existence will be granted.
-     *  @return  An {@link OCFile} instance corresponding to the folder where the file will be uploaded.
+     *  @return  An {@link OCFile} instance corresponding to the folder where the file
+     *  will be uploaded.
      */
     private RemoteOperationResult grantFolderExistence(String pathToGrant) {
         RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, this, false);
@@ -641,7 +676,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
      * synchronized with the server, specially the modification time and Etag
      * (where available)
      *
-     * TODO refactor this ugly thing
+     * TODO move into UploadFileOperation
      */
     private void saveUploadedFile() {
         OCFile file = mCurrentUpload.getFile();
@@ -653,11 +688,14 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
 
         // new PROPFIND to keep data consistent with server 
         // in theory, should return the same we already have
-        ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mCurrentUpload.getRemotePath());
+        ReadRemoteFileOperation operation =
+                new ReadRemoteFileOperation(mCurrentUpload.getRemotePath());
         RemoteOperationResult result = operation.execute(mUploadClient);
         if (result.isSuccess()) {
             updateOCFile(file, (RemoteFile) result.getData().get(0));
             file.setLastSyncDateForProperties(syncDate);
+        } else {
+            Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt...");
         }
 
         // / maybe this would be better as part of UploadFileOperation... or
@@ -667,6 +705,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
             if (oldFile.fileExists()) {
                 oldFile.setStoragePath(null);
                 mStorageManager.saveFile(oldFile);
+                mStorageManager.saveConflict(oldFile, null);
 
             } // else: it was just an automatic renaming due to a name
             // coincidence; nothing else is needed, the storagePath is right
@@ -674,6 +713,10 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         }
         file.setNeedsUpdateThumbnail(true);
         mStorageManager.saveFile(file);
+        mStorageManager.saveConflict(file, null);
+        
+        mStorageManager.triggerMediaScan(file.getStoragePath());
+
     }
 
     private void updateOCFile(OCFile file, RemoteFile remoteFile) {
@@ -682,12 +725,11 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         file.setMimetype(remoteFile.getMimeType());
         file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
         file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
-        // file.setEtag(remoteFile.getEtag());    // TODO Etag, where available
+        file.setEtag(remoteFile.getEtag());
         file.setRemoteId(remoteFile.getRemoteId());
     }
 
-    private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType,
-                                           FileDataStorageManager storageManager) {
+    private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) {
 
         // MIME type
         if (mimeType == null || mimeType.length() <= 0) {
@@ -695,7 +737,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
                 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
                         remotePath.substring(remotePath.lastIndexOf('.') + 1));
             } catch (IndexOutOfBoundsException e) {
-                Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + remotePath);
+                Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " +
+                        remotePath);
             }
         }
         if (mimeType == null) {
@@ -760,11 +803,13 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
      * Callback method to update the progress bar in the status notification
      */
     @Override
-    public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath) {
+    public void onTransferProgress(long progressRate, long totalTransferredSoFar,
+                                   long totalToTransfer, String filePath) {
         int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
         if (percent != mLastPercent) {
             mNotificationBuilder.setProgress(100, percent, false);
-            String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
+            String fileName = filePath.substring(
+                    filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
             String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName);
             mNotificationBuilder.setContentText(text);
             mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build());
@@ -775,11 +820,11 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     /**
      * Updates the status notification with the result of an upload operation.
      *
-     * @param uploadResult Result of the upload operation.
-     * @param upload Finished upload operation
+     * @param uploadResult  Result of the upload operation.
+     * @param upload        Finished upload operation
      */
-    private void notifyUploadResult(
-            RemoteOperationResult uploadResult, UploadFileOperation upload) {
+    private void notifyUploadResult(UploadFileOperation upload,
+                                    RemoteOperationResult uploadResult) {
         Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode());
         // / cancelled operation or success -> silent removal of progress notification
         mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
@@ -789,7 +834,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
             int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker :
                     R.string.uploader_upload_failed_ticker;
 
-            String content = null;
+            String content;
 
             // check credentials error
             boolean needsToUpdateCredentials = (
@@ -886,10 +931,15 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
      * Sends a broadcast in order to the interested activities can update their
      * view
      *
-     * @param upload Finished upload operation
-     * @param uploadResult Result of the upload operation
+     * @param upload                    Finished upload operation
+     * @param uploadResult              Result of the upload operation
+     * @param unlinkedFromRemotePath    Path in the uploads tree where the upload was unlinked from
      */
-    private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) {
+    private void sendBroadcastUploadFinished(
+            UploadFileOperation upload,
+            RemoteOperationResult uploadResult,
+            String unlinkedFromRemotePath) {
+
         Intent end = new Intent(getUploadFinishMessage());
         end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
         // path, after
@@ -902,17 +952,24 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
         end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
         end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
+        if (unlinkedFromRemotePath != null) {
+            end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
+        }
+
         sendStickyBroadcast(end);
     }
 
     /**
      * Checks if content provider, using the content:// scheme, returns a file with mime-type 
      * 'application/pdf' but file has not extension
-     * @param localPath
-     * @param mimeType
+     * @param localPath         Full path to a file in the local file system.
+     * @param mimeType          MIME type of the file.
      * @return true if is needed to add the pdf file extension to the file
+     *
+     * TODO - move to OCFile or Utils class
      */
-    private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, String mimeType) {
+    private boolean isPdfFileFromContentProviderWithoutExtension(String localPath,
+                                                                 String mimeType) {
         return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) &&
                 mimeType.equals(MIME_TYPE_PDF) &&
                 !localPath.endsWith(FILE_EXTENSION_PDF);
@@ -920,20 +977,11 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
 
     /**
      * Remove uploads of an account
-     * @param accountName
+     *
+     * @param account       Downloads account to remove
      */
-    private void cancelUploadForAccount(String accountName){
-        // this can be slow if there are many uploads :(
-        Iterator<String> it = mPendingUploads.keySet().iterator();
-        Log_OC.d(TAG, "Number of pending updloads= "  + mPendingUploads.size());
-        while (it.hasNext()) {
-            String key = it.next();
-            Log_OC.d(TAG, "mPendingUploads CANCELLED " + key);
-            if (key.startsWith(accountName)) {
-                synchronized (mPendingUploads) {
-                    mPendingUploads.remove(key);
-                }
-            }
-        }
+    private void cancelUploadsForAccount(Account account){
+        // Cancel pending uploads
+        mPendingUploads.remove(account);
     }
 }