1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012-2015 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
.CancellationException
; 
  30 import java
.util
.concurrent
.atomic
.AtomicBoolean
; 
  32 import org
.apache
.commons
.httpclient
.methods
.PutMethod
; 
  33 import org
.apache
.commons
.httpclient
.methods
.RequestEntity
; 
  35 import android
.accounts
.Account
; 
  36 import android
.content
.Context
; 
  37 import android
.net
.Uri
; 
  39 import com
.owncloud
.android
.MainApp
; 
  40 import com
.owncloud
.android
.datamodel
.OCFile
; 
  41 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  42 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  43 import com
.owncloud
.android
.lib
.common
.network
.OnDatatransferProgressListener
; 
  44 import com
.owncloud
.android
.lib
.common
.network
.ProgressiveDataTransferer
; 
  45 import com
.owncloud
.android
.lib
.common
.operations
.OperationCancelledException
; 
  46 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  47 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  48 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  49 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  50 import com
.owncloud
.android
.lib
.resources
.files
.ChunkedUploadRemoteFileOperation
; 
  51 import com
.owncloud
.android
.lib
.resources
.files
.ExistenceCheckRemoteOperation
; 
  52 import com
.owncloud
.android
.lib
.resources
.files
.UploadRemoteFileOperation
; 
  53 import com
.owncloud
.android
.utils
.FileStorageUtils
; 
  54 import com
.owncloud
.android
.utils
.UriUtils
; 
  58  * Remote operation performing the upload of a file to an ownCloud server 
  60  * @author David A. Velasco 
  62 public class UploadFileOperation 
extends RemoteOperation 
{ 
  64     private static final String TAG 
= UploadFileOperation
.class.getSimpleName(); 
  66     private Account mAccount
; 
  68     private OCFile mOldFile
; 
  69     private String mRemotePath 
= null
; 
  70     private boolean mChunked 
= false
; 
  71     private boolean mIsInstant 
= false
; 
  72     private boolean mRemoteFolderToBeCreated 
= false
; 
  73     private boolean mForceOverwrite 
= false
; 
  74     private int mLocalBehaviour 
= FileUploader
.LOCAL_BEHAVIOUR_COPY
; 
  75     private boolean mWasRenamed 
= false
; 
  76     private String mOriginalFileName 
= null
; 
  77     private String mOriginalStoragePath 
= null
; 
  78     PutMethod mPutMethod 
= null
; 
  79     private Set
<OnDatatransferProgressListener
> mDataTransferListeners 
= new HashSet
<OnDatatransferProgressListener
>(); 
  80     private AtomicBoolean mCancellationRequested 
= new AtomicBoolean(false
); 
  81     private Context mContext
; 
  83     private UploadRemoteFileOperation mUploadOperation
; 
  85     protected RequestEntity mEntity 
= null
; 
  88     public UploadFileOperation( Account account
, 
  92                                 boolean forceOverwrite
, 
  96             throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation"); 
  98             throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation"); 
  99         if (file
.getStoragePath() == null 
|| file
.getStoragePath().length() <= 0) { 
 100             throw new IllegalArgumentException( 
 101                     "Illegal file in UploadFileOperation; storage path invalid: " 
 102                             + file
.getStoragePath()); 
 107         mRemotePath 
= file
.getRemotePath(); 
 109         mIsInstant 
= isInstant
; 
 110         mForceOverwrite 
= forceOverwrite
; 
 111         mLocalBehaviour 
= localBehaviour
; 
 112         mOriginalStoragePath 
= mFile
.getStoragePath(); 
 113         mOriginalFileName 
= mFile
.getFileName(); 
 117     public Account 
getAccount() { 
 121     public String 
getFileName() { 
 122         return mOriginalFileName
; 
 125     public OCFile 
getFile() { 
 129     public OCFile 
getOldFile() { 
 133     public String 
getOriginalStoragePath() { 
 134         return mOriginalStoragePath
; 
 137     public String 
getStoragePath() { 
 138         return mFile
.getStoragePath(); 
 141     public String 
getRemotePath() { 
 142         return mFile
.getRemotePath(); 
 145     public String 
getMimeType() { 
 146         return mFile
.getMimetype(); 
 149     public boolean isInstant() { 
 153     public boolean isRemoteFolderToBeCreated() { 
 154         return mRemoteFolderToBeCreated
; 
 157     public void setRemoteFolderToBeCreated() { 
 158         mRemoteFolderToBeCreated 
= true
; 
 161     public boolean getForceOverwrite() { 
 162         return mForceOverwrite
; 
 165     public boolean wasRenamed() { 
 169     public Set
<OnDatatransferProgressListener
> getDataTransferListeners() { 
 170         return mDataTransferListeners
; 
 173     public void addDatatransferProgressListener (OnDatatransferProgressListener listener
) { 
 174         synchronized (mDataTransferListeners
) { 
 175             mDataTransferListeners
.add(listener
); 
 177         if (mEntity 
!= null
) { 
 178             ((ProgressiveDataTransferer
)mEntity
).addDatatransferProgressListener(listener
); 
 182     public void removeDatatransferProgressListener(OnDatatransferProgressListener listener
) { 
 183         synchronized (mDataTransferListeners
) { 
 184             mDataTransferListeners
.remove(listener
); 
 186         if (mEntity 
!= null
) { 
 187             ((ProgressiveDataTransferer
)mEntity
).removeDatatransferProgressListener(listener
); 
 192     protected RemoteOperationResult 
run(OwnCloudClient client
) { 
 193         RemoteOperationResult result 
= null
; 
 194         boolean localCopyPassed 
= false
, nameCheckPassed 
= false
; 
 195         File temporalFile 
= null
, originalFile 
= new File(mOriginalStoragePath
), expectedFile 
= null
; 
 197             // / rename the file to upload, if necessary 
 198             if (!mForceOverwrite
) { 
 199                 String remotePath 
= getAvailableRemotePath(client
, mRemotePath
); 
 200                 mWasRenamed 
= !remotePath
.equals(mRemotePath
); 
 202                     createNewOCFile(remotePath
); 
 205             nameCheckPassed 
= true
; 
 207             String expectedPath 
= FileStorageUtils
.getDefaultSavePathFor(mAccount
.name
, mFile
); // / 
 210                                                                                                 // getAvailableRemotePath() 
 212             expectedFile 
= new File(expectedPath
); 
 214             // check location of local file; if not the expected, copy to a 
 215             // temporal file before upload (if COPY is the expected behaviour) 
 216             if (!mOriginalStoragePath
.equals(expectedPath
) && 
 217                     mLocalBehaviour 
== FileUploader
.LOCAL_BEHAVIOUR_COPY
) { 
 219                 if (FileStorageUtils
.getUsableSpace(mAccount
.name
) < originalFile
.length()) { 
 220                     result 
= new RemoteOperationResult(ResultCode
.LOCAL_STORAGE_FULL
); 
 221                     return result
; // error condition when the file should be 
 226                     String temporalPath 
= FileStorageUtils
.getTemporalPath(mAccount
.name
) + 
 227                             mFile
.getRemotePath(); 
 228                     mFile
.setStoragePath(temporalPath
); 
 229                     temporalFile 
= new File(temporalPath
); 
 231                     File temporalParent 
= temporalFile
.getParentFile(); 
 232                     temporalParent
.mkdirs(); 
 233                     if (!temporalParent
.isDirectory()) { 
 234                         throw new IOException("Unexpected error: parent directory could not be created"); 
 236                     temporalFile
.createNewFile(); 
 237                     if (!temporalFile
.isFile()) { 
 238                         throw new IOException("Unexpected error: target file could not be created"); 
 241                     InputStream 
in = null
; 
 242                     OutputStream out 
= null
; 
 246                         // In case document provider schema as 'content://' 
 247                         if (mOriginalStoragePath
.startsWith(UriUtils
.URI_CONTENT_SCHEME
)) { 
 249                             Uri uri 
= Uri
.parse(mOriginalStoragePath
); 
 251                             in = MainApp
.getAppContext().getContentResolver().openInputStream(uri
); 
 252                             out 
= new FileOutputStream(temporalFile
); 
 255                             byte[] data 
= new byte[16384]; 
 257                             while (!mCancellationRequested
.get() && 
 258                                     (nRead 
= in.read(data
, 0, data
.length
)) != -1) { 
 259                                 out
.write(data
, 0, nRead
); 
 265                             if (!mOriginalStoragePath
.equals(temporalPath
)) { // preventing 
 271                                 in = new FileInputStream(originalFile
); 
 272                                 out 
= new FileOutputStream(temporalFile
); 
 273                                 byte[] buf 
= new byte[1024]; 
 275                                 while (!mCancellationRequested
.get() && (len 
= in.read(buf
)) > 0) { 
 276                                     out
.write(buf
, 0, len
); 
 281                     } catch (Exception e
) { 
 282                         result 
= new RemoteOperationResult(ResultCode
.LOCAL_STORAGE_NOT_COPIED
); 
 289                         } catch (Exception e
) { 
 290                             Log_OC
.d(TAG
, "Weird exception while closing input stream for " + 
 291                                     mOriginalStoragePath 
+ " (ignoring)", e
); 
 296                         } catch (Exception e
) { 
 297                             Log_OC
.d(TAG
, "Weird exception while closing output stream for " + 
 298                                     expectedPath 
+ " (ignoring)", e
); 
 303             localCopyPassed 
= true
; 
 305             /// perform the upload 
 307                     (new File(mFile
.getStoragePath())).length() > 
 308                             ChunkedUploadRemoteFileOperation
.CHUNK_SIZE 
) { 
 309                 mUploadOperation 
= new ChunkedUploadRemoteFileOperation(mFile
.getStoragePath(), 
 310                         mFile
.getRemotePath(), mFile
.getMimetype()); 
 312                 mUploadOperation 
= new UploadRemoteFileOperation(mFile
.getStoragePath(), 
 313                         mFile
.getRemotePath(), mFile
.getMimetype()); 
 315             Iterator 
<OnDatatransferProgressListener
> listener 
= mDataTransferListeners
.iterator(); 
 316             while (listener
.hasNext()) { 
 317                 mUploadOperation
.addDatatransferProgressListener(listener
.next()); 
 319             if (!mCancellationRequested
.get()) { 
 320                 result 
= mUploadOperation
.execute(client
); 
 322             /// move local temporal file or original file to its corresponding 
 323             // location in the ownCloud local folder 
 324             if (result
.isSuccess()) { 
 325                 if (mLocalBehaviour 
== FileUploader
.LOCAL_BEHAVIOUR_FORGET
) { 
 326                     mFile
.setStoragePath(null
); 
 329                     mFile
.setStoragePath(expectedPath
); 
 330                     File fileToMove 
= null
; 
 331                     if (temporalFile 
!= null
) { // FileUploader.LOCAL_BEHAVIOUR_COPY 
 332                                                 // ; see where temporalFile was 
 334                         fileToMove 
= temporalFile
; 
 335                     } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE 
 336                         fileToMove 
= originalFile
; 
 338                     if (!expectedFile
.equals(fileToMove
)) { 
 339                         File expectedFolder 
= expectedFile
.getParentFile(); 
 340                         expectedFolder
.mkdirs(); 
 341                         if (!expectedFolder
.isDirectory() || !fileToMove
.renameTo(expectedFile
)) { 
 342                             mFile
.setStoragePath(null
); // forget the local file 
 343                             // by now, treat this as a success; the file was 
 344                             // uploaded; the user won't like that the local file 
 345                             // is not linked, but this should be a very rare 
 347                             // the best option could be show a warning message 
 350                             // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED); 
 357         } catch (Exception e
) { 
 358             // TODO something cleaner with cancellations 
 359             if (mCancellationRequested
.get()) { 
 360                 mUploadOperation
.cancel(); 
 361                 result 
= new RemoteOperationResult(new OperationCancelledException()); 
 363                 result 
= new RemoteOperationResult(e
); 
 367             if (temporalFile 
!= null 
&& !originalFile
.equals(temporalFile
)) { 
 368                 temporalFile
.delete(); 
 370             if (result
.isSuccess()) { 
 371                 Log_OC
.i(TAG
, "Upload of " + mOriginalStoragePath 
+ " to " + mRemotePath 
+ ": " + 
 372                         result
.getLogMessage()); 
 374                 if (result
.getException() != null
) { 
 375                     String complement 
= ""; 
 376                     if (!nameCheckPassed
) { 
 377                         complement 
= " (while checking file existence in server)"; 
 378                     } else if (!localCopyPassed
) { 
 379                         complement 
= " (while copying local file to " + 
 380                                 FileStorageUtils
.getSavePath(mAccount
.name
) 
 383                     Log_OC
.e(TAG
, "Upload of " + mOriginalStoragePath 
+ " to " + mRemotePath 
+ 
 384                             ": " + result
.getLogMessage() + complement
, result
.getException()); 
 386                     Log_OC
.e(TAG
, "Upload of " + mOriginalStoragePath 
+ " to " + mRemotePath 
+ 
 387                             ": " + result
.getLogMessage()); 
 395     private void createNewOCFile(String newRemotePath
) { 
 396         // a new OCFile instance must be created for a new remote path 
 397         OCFile newFile 
= new OCFile(newRemotePath
); 
 398         newFile
.setCreationTimestamp(mFile
.getCreationTimestamp()); 
 399         newFile
.setFileLength(mFile
.getFileLength()); 
 400         newFile
.setMimetype(mFile
.getMimetype()); 
 401         newFile
.setModificationTimestamp(mFile
.getModificationTimestamp()); 
 402         newFile
.setModificationTimestampAtLastSyncForData( 
 403                 mFile
.getModificationTimestampAtLastSyncForData()); 
 404         // newFile.setEtag(mFile.getEtag()) 
 405         newFile
.setKeepInSync(mFile
.keepInSync()); 
 406         newFile
.setLastSyncDateForProperties(mFile
.getLastSyncDateForProperties()); 
 407         newFile
.setLastSyncDateForData(mFile
.getLastSyncDateForData()); 
 408         newFile
.setStoragePath(mFile
.getStoragePath()); 
 409         newFile
.setParentId(mFile
.getParentId()); 
 415      * Checks if remotePath does not exist in the server and returns it, or adds 
 416      * a suffix to it in order to avoid the server file is overwritten. 
 422     private String 
getAvailableRemotePath(OwnCloudClient wc
, String remotePath
) throws Exception 
{ 
 423         boolean check 
= existsFile(wc
, remotePath
); 
 428         int pos 
= remotePath
.lastIndexOf("."); 
 430         String extension 
= ""; 
 432             extension 
= remotePath
.substring(pos 
+ 1); 
 433             remotePath 
= remotePath
.substring(0, pos
); 
 437             suffix 
= " (" + count 
+ ")"; 
 439                 check 
= existsFile(wc
, remotePath 
+ suffix 
+ "." + extension
); 
 442                 check 
= existsFile(wc
, remotePath 
+ suffix
); 
 448             return remotePath 
+ suffix 
+ "." + extension
; 
 450             return remotePath 
+ suffix
; 
 454     private boolean existsFile(OwnCloudClient client
, String remotePath
){ 
 455         ExistenceCheckRemoteOperation existsOperation 
= 
 456                 new ExistenceCheckRemoteOperation(remotePath
, mContext
, false
); 
 457         RemoteOperationResult result 
= existsOperation
.execute(client
); 
 458         return result
.isSuccess(); 
 461     public void cancel() { 
 462         mCancellationRequested 
= new AtomicBoolean(true
); 
 463         if (mUploadOperation 
!= null
) { 
 464             mUploadOperation
.cancel();