Merge remote-tracking branch 'remotes/upstream/cancelUploadOnWlanExit' into beta
authortobiasKaminsky <tobias@kaminsky.me>
Fri, 13 Nov 2015 16:44:03 +0000 (17:44 +0100)
committertobiasKaminsky <tobias@kaminsky.me>
Fri, 13 Nov 2015 16:44:03 +0000 (17:44 +0100)
1  2 
src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java
src/com/owncloud/android/files/services/FileUploader.java
src/com/owncloud/android/operations/UploadFileOperation.java

@@@ -29,16 -29,14 +29,16 @@@ import com.owncloud.android.files.servi
  import com.owncloud.android.lib.common.utils.Log_OC;
  import com.owncloud.android.utils.FileStorageUtils;
  
 -
  import android.accounts.Account;
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
 +import android.content.IntentFilter;
 +import android.content.SharedPreferences;
  import android.database.Cursor;
  import android.net.ConnectivityManager;
  import android.net.NetworkInfo.State;
 +import android.os.BatteryManager;
  import android.preference.PreferenceManager;
  import android.provider.MediaStore.Images;
  import android.provider.MediaStore.Video;
@@@ -60,7 -58,7 +60,7 @@@ public class InstantUploadBroadcastRece
      @Override
      public void onReceive(Context context, Intent intent) {
          Log_OC.d(TAG, "Received: " + intent.getAction());
 -        if (intent.getAction().equals(android.net.ConnectivityManager.CONNECTIVITY_ACTION)) {
 +        if (intent.getAction().equals(android.net.ConnectivityManager.CONNECTIVITY_ACTION) || intent.getAction().equals(Intent.ACTION_POWER_CONNECTED)) {
              handleConnectivityAction(context, intent);
          }else if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) {
              handleNewPictureAction(context, intent); 
          file_name = c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME));
          mime_type = c.getString(c.getColumnIndex(Images.Media.MIME_TYPE));
          c.close();
 -        
          Log_OC.d(TAG, file_path + "");
  
          // save always temporally the picture to upload
          db.putFileForLater(file_path, account.name, null);
          db.close();
  
 -        if (!isOnline(context) || (instantPictureUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) {
 +        if (!isOnline(context) 
 +                || (instantPictureUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))
 +                || (instantUploadWhenChargingOnly(context) && !isCharging(context))
 +           ) {
              return;
          }
  
          i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
          i.putExtra(FileUploader.KEY_MIME_TYPE, mime_type);
          i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
 +
 +        // instant upload behaviour
 +        SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(context);
 +        String behaviour = appPreferences.getString("prefs_instant_behaviour", "NOTHING");
 +
 +        if (behaviour.equalsIgnoreCase("NOTHING")) {
 +            Log_OC.d(TAG, "upload file and do nothing");
 +            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_FORGET);
 +        } else if (behaviour.equalsIgnoreCase("COPY")) {
 +            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_COPY);
 +            Log_OC.d(TAG, "upload file and copy file to oc folder");
 +        } else if (behaviour.equalsIgnoreCase("MOVE")) {
 +            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
 +            Log_OC.d(TAG, "upload file and move file to oc folder");
 +        } else if (behaviour.equalsIgnoreCase("DELETE")){
 +            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_REMOVE);
 +            Log_OC.d(TAG, "upload file and delete file in original place");
 +        }
 +
          context.startService(i);
      }
  
          mime_type = c.getString(c.getColumnIndex(Video.Media.MIME_TYPE));
          c.close();
          Log_OC.d(TAG, file_path + "");
 +        
 +        // save always temporally the picture to upload
 +        DbHandler db = new DbHandler(context);
 +        db.putFileForLater(file_path, account.name, null);
 +        db.close();
  
 -        if (!isOnline(context) || (instantVideoUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) {
 +        if (!isOnline(context) 
 +                || (instantVideoUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))
 +                || (instantVideoUploadWhenChargingOnly(context) && !isCharging(context))
 +           ) {
              return;
          }
  
          i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
          i.putExtra(FileUploader.KEY_MIME_TYPE, mime_type);
          i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
 +
 +        // instant upload behaviour
 +        SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(context);
 +        String behaviour = appPreferences.getString("prefs_instant_behaviour", "NOTHING");
 +
 +        if (behaviour.equalsIgnoreCase("NOTHING")) {
 +            Log_OC.d(TAG, "upload file and do nothing");
 +            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_FORGET);
 +        } else if (behaviour.equalsIgnoreCase("COPY")) {
 +            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_COPY);
 +            Log_OC.d(TAG, "upload file and copy file to oc folder");
 +        } else if (behaviour.equalsIgnoreCase("MOVE")) {
 +            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
 +            Log_OC.d(TAG, "upload file and move file to oc folder");
 +        } else if (behaviour.equalsIgnoreCase("DELETE")){
 +            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_REMOVE);
 +            Log_OC.d(TAG, "upload file and delete file in original place");
 +        }
 +
          context.startService(i);
  
      }
  
      private void handleConnectivityAction(Context context, Intent intent) {
 -        if (!instantPictureUploadEnabled(context)) {
 +        if (!instantPictureUploadEnabled(context) && !instantVideoUploadEnabled(context)) {
              Log_OC.d(TAG, "Instant upload disabled, don't upload anything");
              return;
          }
  
+         if (instantPictureUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context)){
+             Account account = AccountUtils.getCurrentOwnCloudAccount(context);
+             if (account == null) {
+                 Log_OC.w(TAG, "No owncloud account found for instant upload, aborting");
+                 return;
+             }
+             Intent i = new Intent(context, FileUploader.class);
+             i.putExtra(FileUploader.KEY_ACCOUNT, account);
+             i.putExtra(FileUploader.KEY_CANCEL_ALL, true);
+             context.startService(i);
+         }
          if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY)
                  && isOnline(context)
 -                && (!instantPictureUploadViaWiFiOnly(context) || (instantPictureUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) {
 +                && (!instantUploadWhenChargingOnly(context) || (instantUploadWhenChargingOnly(context) && isCharging(context)))
 +                && (!instantVideoUploadWhenChargingOnly(context) || (instantVideoUploadWhenChargingOnly(context) && isCharging(context)))
 +                && (!instantPictureUploadViaWiFiOnly(context) || (instantPictureUploadViaWiFiOnly(context) && isConnectedViaWiFi(context)))
 +                && (!instantVideoUploadViaWiFiOnly(context) || (instantVideoUploadViaWiFiOnly(context) && isConnectedViaWiFi(context)))
 +            ) {
              DbHandler db = new DbHandler(context);
              Cursor c = db.getAwaitingFiles();
              if (c.moveToFirst()) {
                  do {
+                     if (instantPictureUploadViaWiFiOnly(context) &&
+                             !isConnectedViaWiFi(context)){
+                         break;
+                     }
                      String account_name = c.getString(c.getColumnIndex("account"));
                      String file_path = c.getString(c.getColumnIndex("path"));
                      File f = new File(file_path);
                          i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, f.getName()));
                          i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
                          i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
 +
 +                        // instant upload behaviour
 +                        SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(context);
 +                        String behaviour = appPreferences.getString("prefs_instant_behaviour", "NOTHING");
 +
 +                        if (behaviour.equalsIgnoreCase("NOTHING")) {
 +                            Log_OC.d(TAG, "upload file and do nothing");
 +                            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_FORGET);
 +                        } else if (behaviour.equalsIgnoreCase("COPY")) {
 +                            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_COPY);
 +                            Log_OC.d(TAG, "upload file and copy file to oc folder");
 +                        } else if (behaviour.equalsIgnoreCase("MOVE")) {
 +                            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
 +                            Log_OC.d(TAG, "upload file and move file to oc folder");
 +                        } else if (behaviour.equalsIgnoreCase("DELETE")){
 +                            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_REMOVE);
 +                            Log_OC.d(TAG, "upload file and delete file in original place");
 +                        }
 +
                          context.startService(i);
  
                      } else {
              c.close();
              db.close();
          }
 -
      }
  
      public static boolean isOnline(Context context) {
                  && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI
                  && cm.getActiveNetworkInfo().getState() == State.CONNECTED;
      }
 +    
 +    public static boolean isCharging(Context context){
 +        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
 +        Intent batteryStatus = context.registerReceiver(null, ifilter);
 +
 +        int status = 0;
 +        if (batteryStatus != null) {
 +            status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
 +        }
 +        return status == BatteryManager.BATTERY_STATUS_CHARGING ||
 +                status == BatteryManager.BATTERY_STATUS_FULL;
 +    }
  
      public static boolean instantPictureUploadEnabled(Context context) {
          return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_uploading", false);
      public static boolean instantVideoUploadViaWiFiOnly(Context context) {
          return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_video_upload_on_wifi", false);
      }
 +    public static boolean instantUploadWhenChargingOnly(Context context) {
 +        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_upload_on_charging", false);
 +    }
 +    public static boolean instantVideoUploadWhenChargingOnly(Context context) {
 +        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_video_upload_on_charging", false);
 +    }
  }
  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;
@@@ -42,7 -46,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;
@@@ -83,7 -86,6 +83,7 @@@ public class FileUploader extends Servi
      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";
      public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD";
      public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
  
+     public static final String KEY_CANCEL_ALL = "CANCEL_ALL";
      public static final int LOCAL_BEHAVIOUR_COPY = 0;
      public static final int LOCAL_BEHAVIOUR_MOVE = 1;
      public static final int LOCAL_BEHAVIOUR_FORGET = 2;
 +    public static final int LOCAL_BEHAVIOUR_REMOVE = 3;
  
      public static final int UPLOAD_SINGLE_FILE = 0;
      public static final int UPLOAD_MULTIPLE_FILES = 1;
      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;
      }
  
      /**
 -     * 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;
 -    }
 -
 -    /**
       * Checks if an ownCloud server version should support chunked uploads.
       *
       * @param version OwnCloud version instance corresponding to an ownCloud
       *            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);
      public int onStartCommand(Intent intent, int flags, int startId) {
          Log_OC.d(TAG, "Starting command with id " + startId);
  
+         if (intent.hasExtra(KEY_CANCEL_ALL) && intent.hasExtra(KEY_ACCOUNT)){
+             Account account = intent.getParcelableExtra(KEY_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.equals(account.name)) {
+                     mCurrentUpload.cancel();
+                 }
+             }
+             // Cancel pending uploads
+             cancelUploadForAccount(account.name);
+         }
          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");
              files = new OCFile[localPaths.length];
              for (int i = 0; i < localPaths.length; i++) {
                  files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i],
 -                        ((mimeTypes != null) ? mimeTypes[i] : null), storageManager);
 +                        ((mimeTypes != null) ? mimeTypes[i] : null));
                  if (files[i] == null) {
                      // TODO @andomaex add failure Notification
                      return Service.START_NOT_STICKY;
          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,
 +                newUpload = new UploadFileOperation(
 +                        account,
 +                        files[i],
 +                        chunked,
 +                        isInstant,
                          forceOverwrite, localAction,
 -                        getApplicationContext());
 +                        getApplicationContext()
 +                );
                  if (isInstant) {
                      newUpload.setRemoteFolderToBeCreated();
                  }
 -                // Grants that the file only upload once time
 -                mPendingUploads.putIfAbsent(uploadKey, newUpload);
 -
                  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);
              }
  
              msg.obj = requestedUploads;
              mServiceHandler.sendMessage(msg);
          }
 -        Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
          return Service.START_NOT_STICKY;
      }
  
          /**
           * 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;
 -            synchronized (mPendingUploads) {
 -                upload = mPendingUploads.remove(buildRemoteName(account, file));
 -            }
 +            Pair<UploadFileOperation, String> removeResult = mPendingUploads.remove(account, file.getRemotePath());
 +            UploadFileOperation upload = removeResult.first;
              if (upload != null) {
                  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);
                  }
              }
              // Cancel pending uploads
 -            cancelUploadForAccount(account.name);
 +            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
           * @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()));
          }
  
  
          }
  
          /**
 -         * Review uploads and cancel it if its account doesn't exist
 +         * 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
           */
 -        public void checkAccountOfCurrentUpload() {
 -            if (mCurrentUpload != null &&
 -                    !AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
 -                mCurrentUpload.cancel();
 -            }
 -            // The rest of uploads are cancelled when they try to start
 +        private String buildRemoteName(Account account, OCFile file) {
 +            return account.name + file.getRemotePath();
          }
 +
      }
  
      /**
      /**
       * 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().name + " exists");
                  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();
                          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 != null && 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);
 +                cancelUploadsForAccount(mCurrentUpload.getAccount());
  
              }
          }
       * 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();
          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
              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
          }
          file.setNeedsUpdateThumbnail(true);
          mStorageManager.saveFile(file);
 +        mStorageManager.saveConflict(file, null);
 +        
          mStorageManager.triggerMediaScan(file.getStoragePath());
 +
      }
  
      private void updateOCFile(OCFile file, RemoteFile remoteFile) {
          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) {
      /**
       * 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);
       * 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
          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);
      }
  
       * @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) {
  
      /**
       * Remove uploads of an account
 -     * @param accountName       Name of an OC account
 +     *
 +     * @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);
      }
  }
@@@ -31,7 -31,7 +31,7 @@@ import java.util.Iterator
  import java.util.Set;
  import java.util.concurrent.atomic.AtomicBoolean;
  
 -import org.apache.commons.httpclient.methods.PutMethod;
 +import org.apache.commons.httpclient.HttpStatus;
  import org.apache.commons.httpclient.methods.RequestEntity;
  
  import android.accounts.Account;
@@@ -39,7 -39,6 +39,7 @@@ import android.content.Context
  import android.net.Uri;
  
  import com.owncloud.android.MainApp;
 +import com.owncloud.android.datamodel.FileDataStorageManager;
  import com.owncloud.android.datamodel.OCFile;
  import com.owncloud.android.files.services.FileUploader;
  import com.owncloud.android.lib.common.OwnCloudClient;
@@@ -76,6 -75,7 +76,6 @@@ public class UploadFileOperation extend
      private boolean mWasRenamed = false;
      private String mOriginalFileName = null;
      private String mOriginalStoragePath = null;
 -    PutMethod mPutMethod = null;
      private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
      private AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
      private Context mContext;
                      (new File(mFile.getStoragePath())).length() >
                              ChunkedUploadRemoteFileOperation.CHUNK_SIZE ) {
                  mUploadOperation = new ChunkedUploadRemoteFileOperation(mFile.getStoragePath(),
 -                        mFile.getRemotePath(), mFile.getMimetype());
 +                        mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict());
              } else {
                  mUploadOperation = new UploadRemoteFileOperation(mFile.getStoragePath(),
 -                        mFile.getRemotePath(), mFile.getMimetype());
 +                        mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict());
              }
              Iterator <OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
              while (listener.hasNext()) {
                  mUploadOperation.addDatatransferProgressListener(listener.next());
              }
 -            if (!mCancellationRequested.get()) {
 -                result = mUploadOperation.execute(client);
 -
 -                /// move local temporal file or original file to its corresponding
 -                // location in the ownCloud local folder
 -                if (result.isSuccess()) {
 -                    if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) {
 -                        mFile.setStoragePath(null);
 -
 -                    } else {
 -                        mFile.setStoragePath(expectedPath);
 -                        File fileToMove = null;
 -                        if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
 -                            // ; see where temporalFile was
 -                            // set
 -                            fileToMove = temporalFile;
 -                        } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
 -                            fileToMove = originalFile;
 -                        }
 -                        if (!expectedFile.equals(fileToMove)) {
 -                            File expectedFolder = expectedFile.getParentFile();
 -                            expectedFolder.mkdirs();
 -                            if (!expectedFolder.isDirectory() || !fileToMove.renameTo(expectedFile)) {
 -                                mFile.setStoragePath(null); // forget the local file
 -                                // by now, treat this as a success; the file was
 -                                // uploaded; the user won't like that the local file
 -                                // is not linked, but this should be a very rare
 -                                // fail;
 -                                // the best option could be show a warning message
 -                                // (but not a fail)
 -                                // result = new
 -                                // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
 -                                // return result;
 -                            }
 +            if (mCancellationRequested.get()) {
 +                throw new OperationCancelledException();
 +            }
 +
 +            result = mUploadOperation.execute(client);
 +
 +            /// move local temporal file or original file to its corresponding
 +            // location in the ownCloud local folder
 +            if (result.isSuccess()) {
 +                if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) {
 +                    mFile.setStoragePath(null);
 +
 +                } else {
 +                    mFile.setStoragePath(expectedPath);
 +                    File fileToMove = null;
 +                    if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
 +                        // ; see where temporalFile was
 +                        // set
 +                        fileToMove = temporalFile;
 +                    } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
 +                        fileToMove = originalFile;
 +                    }
 +                    if (!expectedFile.equals(fileToMove)) {
 +                        File expectedFolder = expectedFile.getParentFile();
 +                        expectedFolder.mkdirs();
 +                        if (!expectedFolder.isDirectory() || !fileToMove.renameTo(expectedFile)) {
 +                            mFile.setStoragePath(null); // forget the local file
 +                            // by now, treat this as a success; the file was
 +                            // uploaded; the user won't like that the local file
 +                            // is not linked, but this should be a very rare
 +                            // fail;
 +                            // the best option could be show a warning message
 +                            // (but not a fail)
 +                            // result = new
 +                            // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
 +                            // return result;
                          }
                      }
 +                    FileDataStorageManager.triggerMediaScan(originalFile.getAbsolutePath());
 +                    FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
                  }
 +
 +            } else if (result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED ) {
 +                result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
              }
  
          } catch (Exception e) {
 -            // TODO something cleaner with cancellations
 -            if (mCancellationRequested.get()) {
 -                result = new RemoteOperationResult(new OperationCancelledException());
 -            } else {
 -                result = new RemoteOperationResult(e);
 -            }
 +            result = new RemoteOperationResult(e);
  
          } finally {
              if (temporalFile != null && !originalFile.equals(temporalFile)) {
                  temporalFile.delete();
              }
+             if (result == null){
+                 return new RemoteOperationResult(false, 404, null);
+             }
              if (result.isSuccess()) {
                  Log_OC.i(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " +
                          result.getLogMessage());
          newFile.setModificationTimestamp(mFile.getModificationTimestamp());
          newFile.setModificationTimestampAtLastSyncForData(
                  mFile.getModificationTimestampAtLastSyncForData());
 -        // newFile.setEtag(mFile.getEtag())
 +        newFile.setEtag(mFile.getEtag());
          newFile.setFavorite(mFile.isFavorite());
          newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
          newFile.setLastSyncDateForData(mFile.getLastSyncDateForData());