Răsfoiți Sursa

fix cancel upload
add remove single upload from list
add retry upload

Luke Owncloud 10 ani în urmă
părinte
comite
9445692878

+ 5 - 3
res/menu/upload_actions_menu.xml

@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu 	xmlns:android="http://schemas.android.com/apk/res/android">
     
-    <item 	android:id="@+id/action_open_file_with"			android:title="@string/actionbar_open_with"			android:icon="@android:drawable/ic_menu_set_as"					android:orderInCategory="1" /> 
-	<item 	android:id="@+id/action_cancel_upload" 			android:title="@string/common_cancel_upload"		android:icon="@android:drawable/ic_menu_close_clear_cancel"		android:orderInCategory="1" />
-    <item 	android:id="@+id/action_see_details"			android:title="@string/actionbar_see_details"		android:icon="@android:drawable/ic_menu_info_details"			android:orderInCategory="1" />
+    <item 	android:id="@+id/action_open_file_with"		android:title="@string/actionbar_open_with"			android:icon="@android:drawable/ic_menu_set_as"					android:orderInCategory="1" /> 
+	<item 	android:id="@+id/action_cancel_upload" 		android:title="@string/common_cancel_upload"		android:icon="@android:drawable/ic_menu_close_clear_cancel"		android:orderInCategory="1" />
+	<item 	android:id="@+id/action_remove_upload" 		android:title="@string/common_remove_upload"		android:icon="@android:drawable/ic_menu_close_clear_cancel"		android:orderInCategory="1" />
+	<item 	android:id="@+id/action_retry_upload" 		android:title="@string/common_retry_upload"			android:icon="@android:drawable/ic_menu_close_clear_cancel"		android:orderInCategory="1" />
+	<item 	android:id="@+id/action_see_details"		android:title="@string/actionbar_see_details"		android:icon="@android:drawable/ic_menu_info_details"			android:orderInCategory="1" />
 
 </menu>

+ 2 - 0
res/values/strings.xml

@@ -81,6 +81,8 @@
     <string name="common_ok">OK</string>
     <string name="common_cancel_download">Cancel download</string>
     <string name="common_cancel_upload">Cancel upload</string>
+    <string name="common_remove_upload">Remove upload</string>
+    <string name="common_retry_upload">Retry upload</string>
     <string name="common_cancel">Cancel</string>
     <string name="common_save_exit">Save &amp; Exit</string>
     <string name="common_error">Error</string>

+ 8 - 0
src/com/owncloud/android/db/UploadDbHandler.java

@@ -297,12 +297,20 @@ public class UploadDbHandler extends Observable {
     public int removeUpload(String localPath) {
         int result = getDB().delete(TABLE_UPLOAD, "path = ?", new String[] { localPath });
         Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath);
+        if(result > 0) {
+            notifyObserversNow();
+        }
         return result;
     }
 
     public UploadDbObject[] getAllStoredUploads() {
         return getUploads(null, null);
     }
+    
+    public UploadDbObject[] getUploadByLocalPath(String localPath) {
+        return getUploads("path = ?", new String[] { localPath });
+    }
+
 
     private UploadDbObject[] getUploads(String selection, String[] selectionArgs) {
         Cursor c = getDB().query(TABLE_UPLOAD, null, selection, selectionArgs, null, null, null);

+ 25 - 1
src/com/owncloud/android/files/FileOperationsHelper.java

@@ -247,7 +247,31 @@ public class FileOperationsHelper {
         mFileActivity.showLoadingDialog();
     }
 
+    /**
+     * Retry downloading a failed or cancelled upload
+     */
+    public void retryUpload(OCFile file) {
+        Account account = mFileActivity.getAccount();
+        FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
+        if (uploaderBinder != null) {
+            uploaderBinder.retry(account, file);            
+        }  else {
+            Log_OC.w(TAG, "uploaderBinder not set. Cannot remove " + file);            
+        }
+    }
     
+    /**
+     * Remove upload from upload list.
+     */
+    public void removeUploadFromList(OCFile file) {
+        Account account = mFileActivity.getAccount();
+        FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
+        if (uploaderBinder != null) {
+            uploaderBinder.remove(account, file);            
+        }  else {
+            Log_OC.w(TAG, "uploaderBinder not set. Cannot remove " + file);            
+        }
+    }
     public void cancelTransference(OCFile file) {
         Account account = mFileActivity.getAccount();
         FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
@@ -263,7 +287,7 @@ public class FileOperationsHelper {
 
                 downloaderBinder.cancel(account, file);
             } else {
-                Log_OC.d(TAG, "Download for " + file + " not in progress. Cannot cancel.");
+                Log_OC.d(TAG, "Download for " + file + " not in progress. Cannot cancel " + file);
             }
         } 
         if (uploaderBinder != null) {

+ 80 - 4
src/com/owncloud/android/files/services/FileUploadService.java

@@ -20,6 +20,7 @@ package com.owncloud.android.files.services;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.ConcurrentModificationException;
 import java.util.Date;
 import java.util.HashMap;
@@ -30,6 +31,7 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -62,6 +64,7 @@ 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;
@@ -121,6 +124,11 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
      * Call this Service with only this Intent key if all pending uploads are to be retried.
      */
     private static final String KEY_RETRY = "KEY_RETRY";
+    /**
+     * Call this Service with KEY_RETRY and KEY_RETRY_REMOTE_PATH to retry
+     * download of file identified by KEY_RETRY_REMOTE_PATH.
+     */
+    private static final String KEY_RETRY_REMOTE_PATH = "KEY_RETRY_REMOTE_PATH";    
     /**
      * {@link Account} to which file is to be uploaded.
      */
@@ -208,6 +216,9 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
     private FileDataStorageManager mStorageManager;
     //since there can be only one instance of an Android service, there also just one db connection.
     private UploadDbHandler mDb = null;
+    
+    private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+    private final AtomicBoolean mCancellationPossible = new AtomicBoolean(false);
 
     /**
      * List of uploads that are currently pending. Maps from remotePath to where file
@@ -450,16 +461,33 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
 
         Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before uploading.");
 
-        Iterator<String> it = mPendingUploads.keySet().iterator();
+        Iterator<String> it;
+        if (intent != null && intent.getStringExtra(KEY_RETRY_REMOTE_PATH) != null) {
+            ArrayList<String> list = new ArrayList<String>(1);
+            String remotePath = intent.getStringExtra(KEY_RETRY_REMOTE_PATH);
+            list.add(remotePath);
+            it = list.iterator();
+            Log_OC.d(TAG, "Start uploading " + remotePath);
+        } else {
+            it = mPendingUploads.keySet().iterator();
+        }
+
         while (it.hasNext()) {
             String upload = it.next();
             UploadDbObject uploadDbObject = mPendingUploads.get(upload);
+            
+            if(uploadDbObject == null) {
+                Log_OC.e(TAG, "Cannot upload null. Fix that!");
+                continue;
+            }
 
             switch (canUploadFileNow(uploadDbObject)) {
             case NOW:
                 Log_OC.d(TAG, "Calling uploadFile for " + upload);
                 RemoteOperationResult uploadResult = uploadFile(uploadDbObject);
                 
+                //TODO check if cancelled by user
+                
                 updateDataseUploadResult(uploadResult, mCurrentUpload);
                 notifyUploadResult(uploadResult, mCurrentUpload);
                 sendFinalBroadcast(uploadResult, mCurrentUpload);                
@@ -538,10 +566,13 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
             // upload) is handled by updateDataseUploadResult() which is called
             // after upload finishes. Following cancel call makes sure that is
             // does finish right now.
-            if (mCurrentUpload != null) {
+            if (mCurrentUpload != null && mCurrentUpload.isUploadInProgress()) {
                 mCurrentUpload.cancel();
+            } else if(mCancellationPossible.get()){
+                mCancellationRequested.set(true);
             } else {
-                Log_OC.e(TAG, "mCurrentUpload == null. Cannot cancel upload. Fix that!");
+                Log_OC.e(TAG,
+                        "Cannot cancel upload because not in progress. Instead remove from pending upload list. This should not happen.");
 
                 // in this case we have to update the db here. this should never
                 // happen though!
@@ -553,6 +584,29 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
                 mDb.updateUpload(upload);
             }
         }
+        
+        public void remove(Account account, OCFile file) {
+            UploadDbObject upload = mPendingUploads.remove(buildRemoteName(account, file));
+            if(upload == null) {
+                Log_OC.e(TAG, "Could not delete upload "+file+" from mPendingUploads.");
+            }
+            int d = mDb.removeUpload(file.getStoragePath());
+            if(d == 0) {
+                Log_OC.e(TAG, "Could not delete upload "+file.getStoragePath()+" from database.");
+            }            
+        }
+        
+        public void retry(Account account, OCFile file) {
+            //get upload from db and add to mPendingUploads. Then start upload.
+            UploadDbObject[] list = mDb.getUploadByLocalPath(file.getStoragePath());
+            if(list.length == 1) {
+                String uploadKey = buildRemoteName(list[0]);
+                mPendingUploads.putIfAbsent(uploadKey, list[0]);
+                FileUploadService.retry(getApplicationContext(), uploadKey);
+            } else {
+                Log_OC.e(TAG, "Upload file " + file + " not found. Cannot upload.");
+            }  
+        }
 
         public void clearListeners() {
             mBoundListeners.clear();
@@ -640,6 +694,8 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
                 boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, localFileName);
             }
         }
+
+        
     }
     
     enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR};
@@ -733,6 +789,8 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
         mCurrentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder);
         mCurrentUpload.addDatatransferProgressListener(this);
         
+        mCancellationRequested.set(false);
+        mCancellationPossible.set(true);
         notifyUploadStart(mCurrentUpload);        
 
         RemoteOperationResult uploadResult = null, grantResult = null;
@@ -751,9 +809,15 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
             String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent();
             remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath
                     + OCFile.PATH_SEPARATOR;
+            //TODO this might take a moment and should thus be cancelable, for now: cancel afterwards.
             grantResult = grantFolderExistence(mCurrentUpload, remoteParentPath);
+            
+            if(mCancellationRequested.get()) {
+                throw new OperationCancelledException();
+            }
+            mCancellationPossible.set(false);
 
-            // / perform the upload
+            // perform the upload
             if (grantResult.isSuccess()) {
                 OCFile parent = mStorageManager.getFileByPath(remoteParentPath);
                 mCurrentUpload.getFile().setParentId(parent.getFileId());
@@ -778,6 +842,8 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
             Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
             uploadResult = new RemoteOperationResult(e);
 
+        } catch (OperationCancelledException e) {
+            uploadResult = new RemoteOperationResult(e);
         } finally {
             Log_OC.d(TAG, "Remove CurrentUploadItem from pending upload Item Map.");
             if (uploadResult.isException()) {
@@ -1137,9 +1203,19 @@ public class FileUploadService extends IntentService implements OnDatatransferPr
      * Call if all pending uploads are to be retried.
      */
     public static void retry(Context context) {
+        retry(context, null);    
+    }
+
+    /**
+     * Call to retry upload identified by remotePath
+     */
+    private static void retry(Context context, String remotePath) {
         Log_OC.d(TAG, "FileUploadService.retry()");
         Intent i = new Intent(context, FileUploadService.class);
         i.putExtra(FileUploadService.KEY_RETRY, true);
+        if(remotePath != null) {
+            i.putExtra(FileUploadService.KEY_RETRY_REMOTE_PATH, remotePath);
+        }
         context.startService(i);        
     }
 

+ 33 - 4
src/com/owncloud/android/operations/UploadFileOperation.java

@@ -90,13 +90,13 @@ public class UploadFileOperation extends RemoteOperation {
     PutMethod mPutMethod = null;
     private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
     private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+    private final AtomicBoolean mUploadStarted = new AtomicBoolean(false);
     private Context mContext;
     
     private UploadRemoteFileOperation mUploadOperation;
 
     protected RequestEntity mEntity = null;
 
-    
     public UploadFileOperation( Account account,
                                 OCFile file,
                                 boolean chunked,
@@ -200,6 +200,8 @@ public class UploadFileOperation extends RemoteOperation {
 
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
+        mCancellationRequested.set(false);
+        mUploadStarted.set(true);
         RemoteOperationResult result = null;
         boolean localCopyPassed = false, nameCheckPassed = false;
         File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null;
@@ -220,6 +222,10 @@ public class UploadFileOperation extends RemoteOperation {
                                                                                                 // getAvailableRemotePath()
                                                                                                 // !!!
             expectedFile = new File(expectedPath);
+            
+            if (mCancellationRequested.get()) {
+                throw new OperationCancelledException();
+            }
 
             // check location of local file; if not the expected, copy to a
             // temporal file before upload (if COPY is the expected behaviour)
@@ -307,6 +313,10 @@ public class UploadFileOperation extends RemoteOperation {
                 }
             }
             localCopyPassed = true;
+            
+            if (mCancellationRequested.get()) {
+                throw new OperationCancelledException();
+            }
 
             /// perform the upload
             if ( mChunked && (new File(mFile.getStoragePath())).length() > ChunkedUploadRemoteFileOperation.CHUNK_SIZE ) {
@@ -366,6 +376,7 @@ public class UploadFileOperation extends RemoteOperation {
             }
 
         } finally {
+            mUploadStarted.set(false);
             if (temporalFile != null && !originalFile.equals(temporalFile)) {
                 temporalFile.delete();
             }
@@ -462,11 +473,29 @@ public class UploadFileOperation extends RemoteOperation {
         return result.isSuccess();
     }
     
+    /**
+     * Allows to cancel the actual upload operation. If actual upload operating
+     * is in progress it is cancelled, if upload preparation is being performed
+     * upload will not take place.
+     */
     public void cancel() {
         if (mUploadOperation == null) {
-            Log_OC.e(TAG, "UploadFileOperation.cancel(): mUploadOperation == null for file: " + mFile + ". Fix that.");
-            return;
+            if (mUploadStarted.get()) {
+                Log_OC.d(TAG, "Cancelling upload during upload preparations.");
+                mCancellationRequested.set(true);
+            } else {
+                Log_OC.e(TAG, "No upload in progress. This should not happen.");
+            }
+        } else {
+            Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
+            mUploadOperation.cancel();
         }
-        mUploadOperation.cancel();
+    }
+    
+    /**
+     * As soon as this method return true, upload can be cancel via cancel().
+     */
+    public boolean isUploadInProgress() {
+        return mUploadStarted.get();
     }
 }

+ 32 - 4
src/com/owncloud/android/ui/activity/UploadListActivity.java

@@ -1,7 +1,10 @@
 package com.owncloud.android.ui.activity;
 
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Parcelable;
 
 import com.actionbarsherlock.view.Menu;
@@ -10,9 +13,9 @@ import com.actionbarsherlock.view.MenuItem;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.db.UploadDbHandler;
-import com.owncloud.android.db.UploadDbHandler.UploadStatus;
 import com.owncloud.android.db.UploadDbObject;
 import com.owncloud.android.files.services.FileUploadService;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.ui.errorhandling.ExceptionHandler;
 import com.owncloud.android.ui.fragment.UploadListFragment;
@@ -39,9 +42,6 @@ public class UploadListActivity extends FileActivity implements UploadListFragme
     // ////////////////////////////////////////
     // UploadListFragment.ContainerActivity
     // ////////////////////////////////////////
-    /**
-     * TODO Without a menu this is a little un-intuitive.
-     */
     @Override
     public boolean onUploadItemClick(UploadDbObject file) {
         OCFile ocFile = file.getOCFile();
@@ -78,6 +78,34 @@ public class UploadListActivity extends FileActivity implements UploadListFragme
         inflater.inflate(R.menu.upload_list_menu, menu);
         return true;
     }
+    
+    @Override
+    protected ServiceConnection newTransferenceServiceConnection() {
+        return new UploadListServiceConnection();
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private class UploadListServiceConnection implements ServiceConnection {
 
+        @Override
+        public void onServiceConnected(ComponentName component, IBinder service) {
+                
+            if (component.equals(new ComponentName(UploadListActivity.this, FileUploadService.class))) {
+                Log_OC.d(TAG, "UploadListActivty connected to Upload service");
+                mUploaderBinder = (FileUploaderBinder) service;
+            } else {
+                return;
+            }
+            
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName component) {
+            if (component.equals(new ComponentName(UploadListActivity.this, FileUploadService.class))) {
+                Log_OC.d(TAG, "UploadListActivty suddenly disconnected from Upload service");
+                mUploaderBinder = null;
+            }
+        }
+    };  
 
 }

+ 23 - 7
src/com/owncloud/android/ui/fragment/UploadListFragment.java

@@ -121,14 +121,26 @@ public class UploadListFragment extends ExpandableListFragment {
         int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
         int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
         UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition);
-        if (uploadFile.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED
-                || uploadFile.getUploadStatus() == UploadStatus.UPLOAD_FAILED_GIVE_UP) {
+        if (uploadFile.getUploadStatus() != UploadStatus.UPLOAD_IN_PROGRESS) {
             MenuItem item = menu.findItem(R.id.action_cancel_upload);
             if (item != null) {
                 item.setVisible(false);
                 item.setEnabled(false);
             }
-        } 
+        } else {
+            MenuItem item = menu.findItem(R.id.action_remove_upload);
+            if (item != null) {
+                item.setVisible(false);
+                item.setEnabled(false);
+            }
+        }
+        if (!(uploadFile.getUploadStatus() == UploadStatus.UPLOAD_CANCELLED || uploadFile.getUploadStatus() == UploadStatus.UPLOAD_FAILED_RETRY)) {
+            MenuItem item = menu.findItem(R.id.action_retry_upload);
+            if (item != null) {
+                item.setVisible(false);
+                item.setEnabled(false);
+            }
+        }
     }
     
     @Override
@@ -138,12 +150,16 @@ public class UploadListFragment extends ExpandableListFragment {
         int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
         UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition);
         switch (item.getItemId()) {
-        case R.id.action_cancel_upload: {
-            //TODO OCFile does not have UploadBinder. :(
+        case R.id.action_cancel_upload:
             ((FileActivity) getActivity()).getFileOperationsHelper().cancelTransference(uploadFile.getOCFile());
             return true;
-        }
-        case R.id.action_see_details: {
+        case R.id.action_remove_upload: {
+            ((FileActivity) getActivity()).getFileOperationsHelper().removeUploadFromList(uploadFile.getOCFile());
+            return true;
+        }case R.id.action_retry_upload: {
+            ((FileActivity) getActivity()).getFileOperationsHelper().retryUpload(uploadFile.getOCFile());
+            return true;
+        }case R.id.action_see_details: {
             Intent showDetailsIntent = new Intent(getActivity(), FileDisplayActivity.class);
             showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable) uploadFile.getOCFile());
             showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, uploadFile.getAccount(getActivity()));