Fixed lack of File#getUsableSpace() in Android versions previous to GINGERBREAD
[pub/Android/ownCloud.git] / src / com / owncloud / android / operations / UploadFileOperation.java
index f4e1c11..9c570ea 100644 (file)
 package com.owncloud.android.operations;
 
 import java.io.File;
 package com.owncloud.android.operations;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.commons.httpclient.HttpException;
 import org.apache.commons.httpclient.methods.PutMethod;
 import org.apache.http.HttpStatus;
 
 
 import org.apache.commons.httpclient.HttpException;
 import org.apache.commons.httpclient.methods.PutMethod;
 import org.apache.http.HttpStatus;
 
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.operations.RemoteOperation;
 import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.RemoteOperation;
 import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.utils.FileStorageUtils;
 
 import eu.alefzero.webdav.FileRequestEntity;
 import eu.alefzero.webdav.OnDatatransferProgressListener;
 import eu.alefzero.webdav.WebdavClient;
 import eu.alefzero.webdav.WebdavUtils;
 
 import eu.alefzero.webdav.FileRequestEntity;
 import eu.alefzero.webdav.OnDatatransferProgressListener;
 import eu.alefzero.webdav.WebdavClient;
 import eu.alefzero.webdav.WebdavUtils;
+import android.accounts.Account;
 import android.util.Log;
 import android.util.Log;
-import android.webkit.MimeTypeMap;
 
 /**
  * Remote operation performing the upload of a file to an ownCloud server
 
 /**
  * Remote operation performing the upload of a file to an ownCloud server
@@ -42,115 +53,268 @@ import android.webkit.MimeTypeMap;
  */
 public class UploadFileOperation extends RemoteOperation {
     
  */
 public class UploadFileOperation extends RemoteOperation {
     
-    private static final String TAG = UploadFileOperation.class.getCanonicalName();
+    private static final String TAG = UploadFileOperation.class.getSimpleName();
 
 
-    private String mLocalPath = null;
+    private Account mAccount;
+    private OCFile mFile;
+    private OCFile mOldFile;
     private String mRemotePath = null;
     private String mRemotePath = null;
-    private String mMimeType = null;
     private boolean mIsInstant = false;
     private boolean mIsInstant = false;
+    private boolean mRemoteFolderToBeCreated = false;
     private boolean mForceOverwrite = false;
     private boolean mForceOverwrite = false;
-    private OnDatatransferProgressListener mDataTransferListener = null;
+    private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
+    private boolean mWasRenamed = false;
+    PutMethod mPutMethod = null;
+    private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
+    private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
 
     
 
     
-    public String getLocalPath() {
-        return mLocalPath;
+    public UploadFileOperation( Account account,
+                                OCFile file,
+                                boolean isInstant, 
+                                boolean forceOverwrite,
+                                int localBehaviour) {
+        if (account == null)
+            throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation");
+        if (file == null)
+            throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
+        if (file.getStoragePath() == null || file.getStoragePath().length() <= 0 || !(new File(file.getStoragePath()).exists())) {
+            throw new IllegalArgumentException("Illegal file in UploadFileOperation; storage path invalid or file not found: " + file.getStoragePath());
+        }
+        
+        mAccount = account;
+        mFile = file;
+        mRemotePath = file.getRemotePath();
+        mIsInstant = isInstant;
+        mForceOverwrite = forceOverwrite;
+        mLocalBehaviour = localBehaviour;
+    }
+
+
+    public Account getAccount() {
+        return mAccount;
+    }
+    
+    public OCFile getFile() {
+        return mFile;
+    }
+    
+    public OCFile getOldFile() {
+        return mOldFile; 
+    }
+    
+    public String getStoragePath() {
+        return mFile.getStoragePath();
     }
 
     public String getRemotePath() {
     }
 
     public String getRemotePath() {
-        return mRemotePath;
+        return mFile.getRemotePath(); 
     }
 
     public String getMimeType() {
     }
 
     public String getMimeType() {
-        return mMimeType;
+        return mFile.getMimetype();
     }
     }
-
     
     public boolean isInstant() {
         return mIsInstant;
     }
     
     public boolean isInstant() {
         return mIsInstant;
     }
+
+    public boolean isRemoteFolderToBeCreated() {
+        return mRemoteFolderToBeCreated;
+    }
     
     
-    
+    public void setRemoteFolderToBeCreated() {
+        mRemoteFolderToBeCreated = true;
+    }
+
     public boolean getForceOverwrite() {
         return mForceOverwrite;
     }
     public boolean getForceOverwrite() {
         return mForceOverwrite;
     }
-
     
     
-    public OnDatatransferProgressListener getDataTransferListener() {
-        return mDataTransferListener ;
+    public boolean wasRenamed() {
+        return mWasRenamed;
     }
     }
-
     
     
-    public UploadFileOperation( String localPath, 
-                                String remotePath, 
-                                String mimeType, 
-                                boolean isInstant, 
-                                boolean forceOverwrite, 
-                                OnDatatransferProgressListener dataTransferProgressListener) {
-        mLocalPath = localPath;
-        mRemotePath = remotePath;
-        mMimeType = mimeType;
-        if (mMimeType == null) {
-            try {
-                mMimeType = MimeTypeMap.getSingleton()
-                    .getMimeTypeFromExtension(
-                            localPath.substring(localPath.lastIndexOf('.') + 1));
-            } catch (IndexOutOfBoundsException e) {
-                Log.e(TAG, "Trying to find out MIME type of a file without extension: " + localPath);
-            }
-        }
-        if (mMimeType == null) {
-            mMimeType = "application/octet-stream";
-        }
-        mIsInstant = isInstant;
-        mForceOverwrite = forceOverwrite;
-        mDataTransferListener = dataTransferProgressListener;
+    public Set<OnDatatransferProgressListener> getDataTransferListeners() {
+        return mDataTransferListeners;
+    }
+    
+    public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
+        mDataTransferListeners.add(listener);
     }
     
     @Override
     protected RemoteOperationResult run(WebdavClient client) {
         RemoteOperationResult result = null;
     }
     
     @Override
     protected RemoteOperationResult run(WebdavClient client) {
         RemoteOperationResult result = null;
-        boolean nameCheckPassed = false;
+        boolean localCopyPassed = false, nameCheckPassed = false;
+        String originalStoragePath = mFile.getStoragePath();
+        File temporalFile = null, originalFile = new File(originalStoragePath), expectedFile = null;
         try {
             /// rename the file to upload, if necessary
             if (!mForceOverwrite) {
         try {
             /// rename the file to upload, if necessary
             if (!mForceOverwrite) {
-                mRemotePath = getAvailableRemotePath(client, mRemotePath);
+                String remotePath = getAvailableRemotePath(client, mRemotePath);
+                mWasRenamed = !remotePath.equals(mRemotePath);
+                if (mWasRenamed) {
+                   createNewOCFile(remotePath);
+                }
             }
             }
+            nameCheckPassed = true;
         
         
+            String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);  /// not before getAvailableRemotePath() !!!
+            expectedFile = new File(expectedPath);
+            
+            /// check location of local file; if not the expected, copy to a temporal file before upload (if COPY is the expected behaviour)
+            if (!originalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) {
+
+                if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
+                    result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
+                    return result;  // error condition when the file should be copied
+                        
+                } else {
+                    String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
+                    mFile.setStoragePath(temporalPath);
+                    temporalFile = new File(temporalPath);
+                    if (!originalStoragePath.equals(temporalPath)) {   // preventing weird but possible situation
+                        InputStream in = null;
+                        OutputStream out = null;
+                        try {
+                            in = new FileInputStream(originalFile);
+                            out = new FileOutputStream(temporalFile);
+                            byte[] buf = new byte[1024];
+                            int len;
+                            while ((len = in.read(buf)) > 0){
+                                out.write(buf, 0, len);
+                            }
+                            
+                        } catch (Exception e) {
+                            result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
+                            return result;
+                            
+                        } finally {
+                            try {
+                                if (in != null) in.close();
+                            } catch (Exception e) {
+                                Log.d(TAG, "Weird exception while closing input stream for " + originalStoragePath + " (ignoring)", e);
+                            }
+                            try {
+                                if (out != null) out.close();
+                            } catch (Exception e) {
+                                Log.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e);
+                            }
+                        }
+                    }
+                }
+            }
+            localCopyPassed = true;
+            
             /// perform the upload
             /// perform the upload
-            nameCheckPassed = true;
+            synchronized(mCancellationRequested) {
+                if (mCancellationRequested.get()) {
+                    throw new OperationCancelledException();
+                } else {
+                    mPutMethod = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()));
+                }
+            }
             int status = uploadFile(client);
             int status = uploadFile(client);
+            
+            
+            /// move local temporal file or original file to its corresponding location in the ownCloud local folder
+            if (isSuccess(status)) {
+                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;
+                    }
+                    expectedFile = new File(mFile.getStoragePath());
+                    if (!expectedFile.equals(fileToMove) && !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 veeery 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;
+                    }
+                } 
+            }
+            
             result = new RemoteOperationResult(isSuccess(status), status);
             result = new RemoteOperationResult(isSuccess(status), status);
-            Log.i(TAG, "Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage());
+            
             
         } catch (Exception e) {
             
         } catch (Exception e) {
-            result = new RemoteOperationResult(e);
-            Log.e(TAG, "Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage() + (nameCheckPassed?"":" (while checking file existence in server)"), e);
+            // TODO something cleaner with cancellations
+            if (mCancellationRequested.get()) {
+                result = new RemoteOperationResult(new OperationCancelledException());
+            } else {
+                result = new RemoteOperationResult(e);
+            }
+            
             
             
+        } finally {
+            if (temporalFile != null && !originalFile.equals(temporalFile)) {
+                temporalFile.delete();
+            }
+            if (result.isSuccess()) {
+                Log.i(TAG, "Upload of " + originalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
+                    
+            } else {
+                if (result.getException() != null) {
+                    String complement = "";
+                    if (!nameCheckPassed) {
+                        complement = " (while checking file existence in server)";
+                    } else if (!localCopyPassed) {
+                        complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name) + ")";
+                    }
+                    Log.e(TAG, "Upload of " + originalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage() + complement, result.getException());
+                } else {
+                    Log.e(TAG, "Upload of " + originalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
+                }
+            }
         }
         
         return result;
     }
 
     
         }
         
         return result;
     }
 
     
+    private void createNewOCFile(String newRemotePath) {
+        // a new OCFile instance must be created for a new remote path
+        OCFile newFile = new OCFile(newRemotePath);
+        newFile.setCreationTimestamp(mFile.getCreationTimestamp());
+        newFile.setFileLength(mFile.getFileLength());
+        newFile.setMimetype(mFile.getMimetype());
+        newFile.setModificationTimestamp(mFile.getModificationTimestamp());
+        // newFile.setEtag(mFile.getEtag())
+        newFile.setKeepInSync(mFile.keepInSync());
+        newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
+        newFile.setLastSyncDateForData(mFile.getLastSyncDateForData());
+        newFile.setStoragePath(mFile.getStoragePath());
+        newFile.setParentId(mFile.getParentId());
+        mOldFile = mFile;
+        mFile = newFile;
+    }
+
+
     public boolean isSuccess(int status) {
         return ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT));
     }
     
     
     public boolean isSuccess(int status) {
         return ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT));
     }
     
     
-    protected int uploadFile(WebdavClient client) throws HttpException, IOException {
+    protected int uploadFile(WebdavClient client) throws HttpException, IOException, OperationCancelledException {
         int status = -1;
         int status = -1;
-        PutMethod put = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath));
-        
         try {
         try {
-            File f = new File(mLocalPath);
-            FileRequestEntity entity = new FileRequestEntity(f, mMimeType);
-            entity.addOnDatatransferProgressListener(mDataTransferListener);
-            put.setRequestEntity(entity);
-            status = client.executeMethod(put);
-            client.exhaustResponse(put.getResponseBodyAsStream());
+            File f = new File(mFile.getStoragePath());
+            FileRequestEntity entity = new FileRequestEntity(f, getMimeType());
+            entity.addOnDatatransferProgressListeners(mDataTransferListeners);
+            mPutMethod.setRequestEntity(entity);
+            status = client.executeMethod(mPutMethod);
+            client.exhaustResponse(mPutMethod.getResponseBodyAsStream());
             
         } finally {
             
         } finally {
-            put.releaseConnection();    // let the connection available for other methods
+            mPutMethod.releaseConnection();    // let the connection available for other methods
         }
         return status;
     }
         }
         return status;
     }
@@ -192,4 +356,14 @@ public class UploadFileOperation extends RemoteOperation {
         }
     }
 
         }
     }
 
+
+    public void cancel() {
+        synchronized(mCancellationRequested) {
+            mCancellationRequested.set(true);
+            if (mPutMethod != null)
+                mPutMethod.abort();
+        }
+    }
+
+
 }
 }