1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012-2013 ownCloud Inc. 
   4  *   This program is free software: you can redistribute it and/or modify 
   5  *   it under the terms of the GNU General Public License version 2, 
   6  *   as published by the Free Software Foundation. 
   8  *   This program is distributed in the hope that it will be useful, 
   9  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  10  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  11  *   GNU General Public License for more details. 
  13  *   You should have received a copy of the GNU General Public License 
  14  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  18 package com
.owncloud
.android
.operations
; 
  21 import java
.io
.FileInputStream
; 
  22 import java
.io
.FileOutputStream
; 
  23 import java
.io
.IOException
; 
  24 import java
.io
.InputStream
; 
  25 import java
.io
.OutputStream
; 
  26 import java
.util
.HashSet
; 
  27 import java
.util
.Iterator
; 
  29 import java
.util
.concurrent
.atomic
.AtomicBoolean
; 
  31 import org
.apache
.commons
.httpclient
.methods
.PutMethod
; 
  32 import org
.apache
.commons
.httpclient
.methods
.RequestEntity
; 
  34 import android
.accounts
.Account
; 
  35 import android
.content
.Context
; 
  36 import android
.net
.Uri
; 
  38 import com
.owncloud
.android
.MainApp
; 
  39 import com
.owncloud
.android
.datamodel
.OCFile
; 
  40 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  41 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  42 import com
.owncloud
.android
.lib
.common
.network
.OnDatatransferProgressListener
; 
  43 import com
.owncloud
.android
.lib
.common
.network
.ProgressiveDataTransferer
; 
  44 import com
.owncloud
.android
.lib
.common
.operations
.OperationCancelledException
; 
  45 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  46 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  47 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  48 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  49 import com
.owncloud
.android
.lib
.resources
.files
.ChunkedUploadRemoteFileOperation
; 
  50 import com
.owncloud
.android
.lib
.resources
.files
.ExistenceCheckRemoteOperation
; 
  51 import com
.owncloud
.android
.lib
.resources
.files
.UploadRemoteFileOperation
; 
  52 import com
.owncloud
.android
.utils
.FileStorageUtils
; 
  53 import com
.owncloud
.android
.utils
.UriUtils
; 
  57  * Remote operation performing the upload of a file to an ownCloud server 
  59  * @author David A. Velasco 
  61 public class UploadFileOperation 
extends RemoteOperation 
{ 
  63     private static final String TAG 
= UploadFileOperation
.class.getSimpleName(); 
  65     private Account mAccount
; 
  67     private OCFile mOldFile
; 
  68     private String mRemotePath 
= null
; 
  69     private boolean mChunked 
= false
; 
  70     private boolean mIsInstant 
= false
; 
  71     private boolean mRemoteFolderToBeCreated 
= false
; 
  72     private boolean mForceOverwrite 
= false
; 
  73     private int mLocalBehaviour 
= FileUploader
.LOCAL_BEHAVIOUR_COPY
; 
  74     private boolean mWasRenamed 
= false
; 
  75     private String mOriginalFileName 
= null
; 
  76     private String mOriginalStoragePath 
= null
; 
  77     PutMethod mPutMethod 
= null
; 
  78     private Set
<OnDatatransferProgressListener
> mDataTransferListeners 
= new HashSet
<OnDatatransferProgressListener
>(); 
  79     private final AtomicBoolean mCancellationRequested 
= new AtomicBoolean(false
); 
  80     private Context mContext
; 
  82     private UploadRemoteFileOperation mUploadOperation
; 
  84     protected RequestEntity mEntity 
= null
; 
  87     public UploadFileOperation( Account account
, 
  91                                 boolean forceOverwrite
, 
  95             throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation"); 
  97             throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation"); 
  98         if (file
.getStoragePath() == null 
|| file
.getStoragePath().length() <= 0) { 
  99             throw new IllegalArgumentException( 
 100                     "Illegal file in UploadFileOperation; storage path invalid: " 
 101                             + file
.getStoragePath()); 
 106         mRemotePath 
= file
.getRemotePath(); 
 108         mIsInstant 
= isInstant
; 
 109         mForceOverwrite 
= forceOverwrite
; 
 110         mLocalBehaviour 
= localBehaviour
; 
 111         mOriginalStoragePath 
= mFile
.getStoragePath(); 
 112         mOriginalFileName 
= mFile
.getFileName(); 
 116     public Account 
getAccount() { 
 120     public String 
getFileName() { 
 121         return mOriginalFileName
; 
 124     public OCFile 
getFile() { 
 128     public OCFile 
getOldFile() { 
 132     public String 
getOriginalStoragePath() { 
 133         return mOriginalStoragePath
; 
 136     public String 
getStoragePath() { 
 137         return mFile
.getStoragePath(); 
 140     public String 
getRemotePath() { 
 141         return mFile
.getRemotePath(); 
 144     public String 
getMimeType() { 
 145         return mFile
.getMimetype(); 
 148     public boolean isInstant() { 
 152     public boolean isRemoteFolderToBeCreated() { 
 153         return mRemoteFolderToBeCreated
; 
 156     public void setRemoteFolderToBeCreated() { 
 157         mRemoteFolderToBeCreated 
= true
; 
 160     public boolean getForceOverwrite() { 
 161         return mForceOverwrite
; 
 164     public boolean wasRenamed() { 
 168     public Set
<OnDatatransferProgressListener
> getDataTransferListeners() { 
 169         return mDataTransferListeners
; 
 172     public void addDatatransferProgressListener (OnDatatransferProgressListener listener
) { 
 173         synchronized (mDataTransferListeners
) { 
 174             mDataTransferListeners
.add(listener
); 
 176         if (mEntity 
!= null
) { 
 177             ((ProgressiveDataTransferer
)mEntity
).addDatatransferProgressListener(listener
); 
 181     public void removeDatatransferProgressListener(OnDatatransferProgressListener listener
) { 
 182         synchronized (mDataTransferListeners
) { 
 183             mDataTransferListeners
.remove(listener
); 
 185         if (mEntity 
!= null
) { 
 186             ((ProgressiveDataTransferer
)mEntity
).removeDatatransferProgressListener(listener
); 
 191     protected RemoteOperationResult 
run(OwnCloudClient client
) { 
 192         RemoteOperationResult result 
= null
; 
 193         boolean localCopyPassed 
= false
, nameCheckPassed 
= false
; 
 194         File temporalFile 
= null
, originalFile 
= new File(mOriginalStoragePath
), expectedFile 
= null
; 
 196             // / rename the file to upload, if necessary 
 197             if (!mForceOverwrite
) { 
 198                 String remotePath 
= getAvailableRemotePath(client
, mRemotePath
); 
 199                 mWasRenamed 
= !remotePath
.equals(mRemotePath
); 
 201                     createNewOCFile(remotePath
); 
 204             nameCheckPassed 
= true
; 
 206             String expectedPath 
= FileStorageUtils
.getDefaultSavePathFor(mAccount
.name
, mFile
); // / 
 209                                                                                                 // getAvailableRemotePath() 
 211             expectedFile 
= new File(expectedPath
); 
 213             // check location of local file; if not the expected, copy to a 
 214             // temporal file before upload (if COPY is the expected behaviour) 
 215             if (!mOriginalStoragePath
.equals(expectedPath
) && mLocalBehaviour 
== FileUploader
.LOCAL_BEHAVIOUR_COPY
) { 
 217                 if (FileStorageUtils
.getUsableSpace(mAccount
.name
) < originalFile
.length()) { 
 218                     result 
= new RemoteOperationResult(ResultCode
.LOCAL_STORAGE_FULL
); 
 219                     return result
; // error condition when the file should be 
 224                     String temporalPath 
= FileStorageUtils
.getTemporalPath(mAccount
.name
) + mFile
.getRemotePath(); 
 225                     mFile
.setStoragePath(temporalPath
); 
 226                     temporalFile 
= new File(temporalPath
); 
 228                     File temporalParent 
= temporalFile
.getParentFile(); 
 229                     temporalParent
.mkdirs(); 
 230                     if (!temporalParent
.isDirectory()) { 
 231                         throw new IOException("Unexpected error: parent directory could not be created"); 
 233                     temporalFile
.createNewFile(); 
 234                     if (!temporalFile
.isFile()) { 
 235                         throw new IOException("Unexpected error: target file could not be created"); 
 238                     InputStream 
in = null
; 
 239                     OutputStream out 
= null
; 
 243                         // In case document provider schema as 'content://' 
 244                         if (mOriginalStoragePath
.startsWith(UriUtils
.URI_CONTENT_SCHEME
)) { 
 246                             Uri uri 
= Uri
.parse(mOriginalStoragePath
); 
 248                             in = MainApp
.getAppContext().getContentResolver().openInputStream(uri
); 
 249                             out 
= new FileOutputStream(temporalFile
); 
 252                             byte[] data 
= new byte[16384]; 
 254                             while ((nRead 
= in.read(data
, 0, data
.length
)) != -1) { 
 255                                 out
.write(data
, 0, nRead
); 
 261                             if (!mOriginalStoragePath
.equals(temporalPath
)) { // preventing 
 267                                 in = new FileInputStream(originalFile
); 
 268                                 out 
= new FileOutputStream(temporalFile
); 
 269                                 byte[] buf 
= new byte[1024]; 
 271                                 while ((len 
= in.read(buf
)) > 0) { 
 272                                     out
.write(buf
, 0, len
); 
 277                     } catch (Exception e
) { 
 278                         result 
= new RemoteOperationResult(ResultCode
.LOCAL_STORAGE_NOT_COPIED
); 
 285                         } catch (Exception e
) { 
 286                             Log_OC
.d(TAG
, "Weird exception while closing input stream for " + mOriginalStoragePath 
+ " (ignoring)", e
); 
 291                         } catch (Exception e
) { 
 292                             Log_OC
.d(TAG
, "Weird exception while closing output stream for " + expectedPath 
+ " (ignoring)", e
); 
 297             localCopyPassed 
= true
; 
 299             /// perform the upload 
 300             if ( mChunked 
&& (new File(mFile
.getStoragePath())).length() > ChunkedUploadRemoteFileOperation
.CHUNK_SIZE 
) { 
 301                 mUploadOperation 
= new ChunkedUploadRemoteFileOperation(mFile
.getStoragePath(), mFile
.getRemotePath(),  
 302                         mFile
.getMimetype()); 
 304                 mUploadOperation 
= new UploadRemoteFileOperation(mFile
.getStoragePath(), mFile
.getRemotePath(),  
 305                         mFile
.getMimetype()); 
 307             Iterator 
<OnDatatransferProgressListener
> listener 
= mDataTransferListeners
.iterator(); 
 308             while (listener
.hasNext()) { 
 309                 mUploadOperation
.addDatatransferProgressListener(listener
.next()); 
 311             result 
= mUploadOperation
.execute(client
); 
 313             /// move local temporal file or original file to its corresponding 
 314             // location in the ownCloud local folder 
 315             if (result
.isSuccess()) { 
 316                 if (mLocalBehaviour 
== FileUploader
.LOCAL_BEHAVIOUR_FORGET
) { 
 317                     mFile
.setStoragePath(null
); 
 320                     mFile
.setStoragePath(expectedPath
); 
 321                     File fileToMove 
= null
; 
 322                     if (temporalFile 
!= null
) { // FileUploader.LOCAL_BEHAVIOUR_COPY 
 323                                                 // ; see where temporalFile was 
 325                         fileToMove 
= temporalFile
; 
 326                     } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE 
 327                         fileToMove 
= originalFile
; 
 329                     if (!expectedFile
.equals(fileToMove
)) { 
 330                         File expectedFolder 
= expectedFile
.getParentFile(); 
 331                         expectedFolder
.mkdirs(); 
 332                         if (!expectedFolder
.isDirectory() || !fileToMove
.renameTo(expectedFile
)) { 
 333                             mFile
.setStoragePath(null
); // forget the local file 
 334                             // by now, treat this as a success; the file was 
 335                             // uploaded; the user won't like that the local file 
 336                             // is not linked, but this should be a very rare 
 338                             // the best option could be show a warning message 
 341                             // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED); 
 348         } catch (Exception e
) { 
 349             // TODO something cleaner with cancellations 
 350             if (mCancellationRequested
.get()) { 
 351                 result 
= new RemoteOperationResult(new OperationCancelledException()); 
 353                 result 
= new RemoteOperationResult(e
); 
 357             if (temporalFile 
!= null 
&& !originalFile
.equals(temporalFile
)) { 
 358                 temporalFile
.delete(); 
 360             if (result
.isSuccess()) { 
 361                 Log_OC
.i(TAG
, "Upload of " + mOriginalStoragePath 
+ " to " + mRemotePath 
+ ": " + result
.getLogMessage()); 
 363                 if (result
.getException() != null
) { 
 364                     String complement 
= ""; 
 365                     if (!nameCheckPassed
) { 
 366                         complement 
= " (while checking file existence in server)"; 
 367                     } else if (!localCopyPassed
) { 
 368                         complement 
= " (while copying local file to " + FileStorageUtils
.getSavePath(mAccount
.name
) 
 371                     Log_OC
.e(TAG
, "Upload of " + mOriginalStoragePath 
+ " to " + mRemotePath 
+ ": " + result
.getLogMessage() + complement
, result
.getException()); 
 373                     Log_OC
.e(TAG
, "Upload of " + mOriginalStoragePath 
+ " to " + mRemotePath 
+ ": " + result
.getLogMessage()); 
 381     private void createNewOCFile(String newRemotePath
) { 
 382         // a new OCFile instance must be created for a new remote path 
 383         OCFile newFile 
= new OCFile(newRemotePath
); 
 384         newFile
.setCreationTimestamp(mFile
.getCreationTimestamp()); 
 385         newFile
.setFileLength(mFile
.getFileLength()); 
 386         newFile
.setMimetype(mFile
.getMimetype()); 
 387         newFile
.setModificationTimestamp(mFile
.getModificationTimestamp()); 
 388         newFile
.setModificationTimestampAtLastSyncForData(mFile
.getModificationTimestampAtLastSyncForData()); 
 389         // newFile.setEtag(mFile.getEtag()) 
 390         newFile
.setKeepInSync(mFile
.keepInSync()); 
 391         newFile
.setLastSyncDateForProperties(mFile
.getLastSyncDateForProperties()); 
 392         newFile
.setLastSyncDateForData(mFile
.getLastSyncDateForData()); 
 393         newFile
.setStoragePath(mFile
.getStoragePath()); 
 394         newFile
.setParentId(mFile
.getParentId()); 
 400      * Checks if remotePath does not exist in the server and returns it, or adds 
 401      * a suffix to it in order to avoid the server file is overwritten. 
 406     private String 
getAvailableRemotePath(OwnCloudClient wc
, String remotePath
) throws Exception 
{ 
 407         boolean check 
= existsFile(wc
, remotePath
); 
 412         int pos 
= remotePath
.lastIndexOf("."); 
 414         String extension 
= ""; 
 416             extension 
= remotePath
.substring(pos 
+ 1); 
 417             remotePath 
= remotePath
.substring(0, pos
); 
 421             suffix 
= " (" + count 
+ ")"; 
 423                 check 
= existsFile(wc
, remotePath 
+ suffix 
+ "." + extension
); 
 426                 check 
= existsFile(wc
, remotePath 
+ suffix
); 
 432             return remotePath 
+ suffix 
+ "." + extension
; 
 434             return remotePath 
+ suffix
; 
 438     private boolean existsFile(OwnCloudClient client
, String remotePath
){ 
 439         ExistenceCheckRemoteOperation existsOperation 
= new ExistenceCheckRemoteOperation(remotePath
, mContext
, false
); 
 440         RemoteOperationResult result 
= existsOperation
.execute(client
); 
 441         return result
.isSuccess(); 
 444     public void cancel() { 
 445         mUploadOperation
.cancel();