2  *   ownCloud Android client application 
   4  *   @author David A. Velasco 
   5  *   Copyright (C) 2015 ownCloud Inc. 
   7  *   This program is free software: you can redistribute it and/or modify 
   8  *   it under the terms of the GNU General Public License version 2, 
   9  *   as published by the Free Software Foundation. 
  11  *   This program is distributed in the hope that it will be useful, 
  12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  14  *   GNU General Public License for more details. 
  16  *   You should have received a copy of the GNU General Public License 
  17  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  21 package com
.owncloud
.android
.operations
; 
  24 import java
.io
.FileInputStream
; 
  25 import java
.io
.FileOutputStream
; 
  26 import java
.io
.IOException
; 
  27 import java
.io
.InputStream
; 
  28 import java
.io
.OutputStream
; 
  29 import java
.nio
.channels
.FileChannel
; 
  30 import java
.util
.HashSet
; 
  31 import java
.util
.Iterator
; 
  33 import java
.util
.concurrent
.atomic
.AtomicBoolean
; 
  35 import org
.apache
.commons
.httpclient
.HttpStatus
; 
  36 import org
.apache
.commons
.httpclient
.methods
.RequestEntity
; 
  38 import android
.accounts
.Account
; 
  39 import android
.content
.Context
; 
  40 import android
.net
.Uri
; 
  42 import com
.owncloud
.android
.MainApp
; 
  43 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  44 import com
.owncloud
.android
.datamodel
.OCFile
; 
  45 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  46 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  47 import com
.owncloud
.android
.lib
.common
.network
.OnDatatransferProgressListener
; 
  48 import com
.owncloud
.android
.lib
.common
.network
.ProgressiveDataTransferer
; 
  49 import com
.owncloud
.android
.lib
.common
.operations
.OperationCancelledException
; 
  50 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  51 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  52 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  53 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  54 import com
.owncloud
.android
.lib
.resources
.files
.ChunkedUploadRemoteFileOperation
; 
  55 import com
.owncloud
.android
.lib
.resources
.files
.ExistenceCheckRemoteOperation
; 
  56 import com
.owncloud
.android
.lib
.resources
.files
.UploadRemoteFileOperation
; 
  57 import com
.owncloud
.android
.utils
.FileStorageUtils
; 
  58 import com
.owncloud
.android
.utils
.UriUtils
; 
  62  * Remote operation performing the upload of a file to an ownCloud server 
  64 public class UploadFileOperation 
extends RemoteOperation 
{ 
  66     private static final String TAG 
= UploadFileOperation
.class.getSimpleName(); 
  68     private Account mAccount
; 
  70     private OCFile mOldFile
; 
  71     private String mRemotePath 
= null
; 
  72     private boolean mChunked 
= false
; 
  73     private boolean mIsInstant 
= false
; 
  74     private boolean mRemoteFolderToBeCreated 
= false
; 
  75     private boolean mForceOverwrite 
= false
; 
  76     private int mLocalBehaviour 
= FileUploader
.LOCAL_BEHAVIOUR_COPY
; 
  77     private boolean mWasRenamed 
= false
; 
  78     private String mOriginalFileName 
= null
; 
  79     private String mOriginalStoragePath 
= null
; 
  80     private Set
<OnDatatransferProgressListener
> mDataTransferListeners 
= new HashSet
<OnDatatransferProgressListener
>(); 
  81     private AtomicBoolean mCancellationRequested 
= new AtomicBoolean(false
); 
  82     private Context mContext
; 
  84     private UploadRemoteFileOperation mUploadOperation
; 
  86     protected RequestEntity mEntity 
= null
; 
  89     public UploadFileOperation( Account account
, 
  93                                 boolean forceOverwrite
, 
  97             throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " + 
 100             throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation"); 
 101         if (file
.getStoragePath() == null 
|| file
.getStoragePath().length() <= 0) { 
 102             throw new IllegalArgumentException( 
 103                     "Illegal file in UploadFileOperation; storage path invalid: " 
 104                             + file
.getStoragePath()); 
 109         mRemotePath 
= file
.getRemotePath(); 
 111         mIsInstant 
= isInstant
; 
 112         mForceOverwrite 
= forceOverwrite
; 
 113         mLocalBehaviour 
= localBehaviour
; 
 114         mOriginalStoragePath 
= mFile
.getStoragePath(); 
 115         mOriginalFileName 
= mFile
.getFileName(); 
 119     public Account 
getAccount() { 
 123     public String 
getFileName() { 
 124         return mOriginalFileName
; 
 127     public OCFile 
getFile() { 
 131     public OCFile 
getOldFile() { 
 135     public String 
getOriginalStoragePath() { 
 136         return mOriginalStoragePath
; 
 139     public String 
getStoragePath() { 
 140         return mFile
.getStoragePath(); 
 143     public String 
getRemotePath() { 
 144         return mFile
.getRemotePath(); 
 147     public String 
getMimeType() { 
 148         return mFile
.getMimetype(); 
 151     public boolean isInstant() { 
 155     public boolean isRemoteFolderToBeCreated() { 
 156         return mRemoteFolderToBeCreated
; 
 159     public void setRemoteFolderToBeCreated() { 
 160         mRemoteFolderToBeCreated 
= true
; 
 163     public boolean getForceOverwrite() { 
 164         return mForceOverwrite
; 
 167     public boolean wasRenamed() { 
 171     public Set
<OnDatatransferProgressListener
> getDataTransferListeners() { 
 172         return mDataTransferListeners
; 
 175     public void addDatatransferProgressListener (OnDatatransferProgressListener listener
) { 
 176         synchronized (mDataTransferListeners
) { 
 177             mDataTransferListeners
.add(listener
); 
 179         if (mEntity 
!= null
) { 
 180             ((ProgressiveDataTransferer
)mEntity
).addDatatransferProgressListener(listener
); 
 184     public void removeDatatransferProgressListener(OnDatatransferProgressListener listener
) { 
 185         synchronized (mDataTransferListeners
) { 
 186             mDataTransferListeners
.remove(listener
); 
 188         if (mEntity 
!= null
) { 
 189             ((ProgressiveDataTransferer
)mEntity
).removeDatatransferProgressListener(listener
); 
 194     protected RemoteOperationResult 
run(OwnCloudClient client
) { 
 195         RemoteOperationResult result 
= null
; 
 196         boolean localCopyPassed 
= false
, nameCheckPassed 
= false
; 
 197         File temporalFile 
= null
, originalFile 
= new File(mOriginalStoragePath
), expectedFile 
= null
; 
 199             // / rename the file to upload, if necessary 
 200             if (!mForceOverwrite
) { 
 201                 String remotePath 
= getAvailableRemotePath(client
, mRemotePath
); 
 202                 mWasRenamed 
= !remotePath
.equals(mRemotePath
); 
 204                     createNewOCFile(remotePath
); 
 207             nameCheckPassed 
= true
; 
 209             String expectedPath 
= FileStorageUtils
.getDefaultSavePathFor(mAccount
.name
, mFile
); // / 
 212                                                                                                 // getAvailableRemotePath() 
 214             expectedFile 
= new File(expectedPath
); 
 216             // check location of local file; if not the expected, copy to a 
 217             // temporal file before upload (if COPY is the expected behaviour) 
 218             if (!mOriginalStoragePath
.equals(expectedPath
) && 
 219                     mLocalBehaviour 
== FileUploader
.LOCAL_BEHAVIOUR_COPY
) { 
 221                 if (FileStorageUtils
.getUsableSpace(mAccount
.name
) < originalFile
.length()) { 
 222                     result 
= new RemoteOperationResult(ResultCode
.LOCAL_STORAGE_FULL
); 
 223                     return result
; // error condition when the file should be 
 228                     String temporalPath 
= FileStorageUtils
.getTemporalPath(mAccount
.name
) + 
 229                             mFile
.getRemotePath(); 
 230                     mFile
.setStoragePath(temporalPath
); 
 231                     temporalFile 
= new File(temporalPath
); 
 233                     File temporalParent 
= temporalFile
.getParentFile(); 
 234                     temporalParent
.mkdirs(); 
 235                     if (!temporalParent
.isDirectory()) { 
 236                         throw new IOException("Unexpected error: parent directory could not be created"); 
 238                     temporalFile
.createNewFile(); 
 239                     if (!temporalFile
.isFile()) { 
 240                         throw new IOException("Unexpected error: target file could not be created"); 
 243                     InputStream 
in = null
; 
 244                     OutputStream out 
= null
; 
 248                         // In case document provider schema as 'content://' 
 249                         if (mOriginalStoragePath
.startsWith(UriUtils
.URI_CONTENT_SCHEME
)) { 
 251                             Uri uri 
= Uri
.parse(mOriginalStoragePath
); 
 253                             in = MainApp
.getAppContext().getContentResolver().openInputStream(uri
); 
 254                             out 
= new FileOutputStream(temporalFile
); 
 257                             byte[] data 
= new byte[16384]; 
 259                             while (!mCancellationRequested
.get() && 
 260                                     (nRead 
= in.read(data
, 0, data
.length
)) != -1) { 
 261                                 out
.write(data
, 0, nRead
); 
 266                             if (!mOriginalStoragePath
.equals(temporalPath
)) { // preventing 
 272                                 in = new FileInputStream(originalFile
); 
 273                                 out 
= new FileOutputStream(temporalFile
); 
 274                                 byte[] buf 
= new byte[1024]; 
 276                                 while (!mCancellationRequested
.get() && (len 
= in.read(buf
)) > 0) { 
 277                                     out
.write(buf
, 0, len
); 
 282                         if (mCancellationRequested
.get()) { 
 283                             result 
= new RemoteOperationResult(new OperationCancelledException()); 
 287                     } catch (Exception e
) { 
 288                         result 
= new RemoteOperationResult(ResultCode
.LOCAL_STORAGE_NOT_COPIED
); 
 295                         } catch (Exception e
) { 
 296                             Log_OC
.d(TAG
, "Weird exception while closing input stream for " + 
 297                                     mOriginalStoragePath 
+ " (ignoring)", e
); 
 302                         } catch (Exception e
) { 
 303                             Log_OC
.d(TAG
, "Weird exception while closing output stream for " + 
 304                                     expectedPath 
+ " (ignoring)", e
); 
 309             localCopyPassed 
= (result 
== null
); 
 311             /// perform the upload 
 313                     (new File(mFile
.getStoragePath())).length() > 
 314                             ChunkedUploadRemoteFileOperation
.CHUNK_SIZE 
) { 
 315                 mUploadOperation 
= new ChunkedUploadRemoteFileOperation(mFile
.getStoragePath(), 
 316                         mFile
.getRemotePath(), mFile
.getMimetype(), mFile
.getEtagInConflict()); 
 318                 mUploadOperation 
= new UploadRemoteFileOperation(mFile
.getStoragePath(), 
 319                         mFile
.getRemotePath(), mFile
.getMimetype(), mFile
.getEtagInConflict()); 
 321             Iterator 
<OnDatatransferProgressListener
> listener 
= mDataTransferListeners
.iterator(); 
 322             while (listener
.hasNext()) { 
 323                 mUploadOperation
.addDatatransferProgressListener(listener
.next()); 
 325             if (mCancellationRequested
.get()) { 
 326                 throw new OperationCancelledException(); 
 329             result 
= mUploadOperation
.execute(client
); 
 331             /// move local temporal file or original file to its corresponding 
 332             // location in the ownCloud local folder 
 333             if (result
.isSuccess()) { 
 334                 if (mLocalBehaviour 
== FileUploader
.LOCAL_BEHAVIOUR_FORGET
) { 
 335                     mFile
.setStoragePath(null
); 
 336                 } else if (mLocalBehaviour 
== FileUploader
.LOCAL_BEHAVIOUR_REMOVE
){ 
 337                     mFile
.setStoragePath(null
); 
 338                     originalFile
.delete(); 
 340                     mFile
.setStoragePath(expectedPath
); 
 341                     File fileToMove 
= null
; 
 342                     if (temporalFile 
!= null
) { // FileUploader.LOCAL_BEHAVIOUR_COPY 
 343                         // ; see where temporalFile was 
 345                         fileToMove 
= temporalFile
; 
 346                     } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE 
 347                         fileToMove 
= originalFile
; 
 349                     if (!expectedFile
.equals(fileToMove
)) { 
 350                         File expectedFolder 
= expectedFile
.getParentFile(); 
 351                         expectedFolder
.mkdirs(); 
 353                         if (expectedFolder
.isDirectory()){ 
 354                             if (!fileToMove
.renameTo(expectedFile
)){ 
 355                                 // try to copy and then delete 
 356                                 expectedFile
.createNewFile(); 
 357                                 FileChannel inChannel 
= new FileInputStream(fileToMove
).getChannel(); 
 358                                 FileChannel outChannel 
= new FileOutputStream(expectedFile
).getChannel(); 
 361                                     inChannel
.transferTo(0, inChannel
.size(), outChannel
); 
 363                                 } catch (Exception e
){ 
 364                                     mFile
.setStoragePath(null
); // forget the local file 
 365                                     // by now, treat this as a success; the file was 
 366                                     // uploaded; the user won't like that the local file 
 367                                     // is not linked, but this should be a very rare 
 369                                     // the best option could be show a warning message 
 372                                     // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED); 
 376                                     if (inChannel 
!= null
) inChannel
.close(); 
 377                                     if (outChannel 
!= null
) outChannel
.close(); 
 382                             mFile
.setStoragePath(null
); 
 386                 FileDataStorageManager
.triggerMediaScan(originalFile
.getAbsolutePath()); 
 387                 FileDataStorageManager
.triggerMediaScan(expectedFile
.getAbsolutePath()); 
 388             } else if (result
.getHttpCode() == HttpStatus
.SC_PRECONDITION_FAILED 
) { 
 389                 result 
= new RemoteOperationResult(ResultCode
.SYNC_CONFLICT
); 
 392         } catch (Exception e
) { 
 393             result 
= new RemoteOperationResult(e
); 
 396             if (temporalFile 
!= null 
&& !originalFile
.equals(temporalFile
)) { 
 397                 temporalFile
.delete(); 
 400                 return new RemoteOperationResult(false
, 404, null
); 
 402             if (result
.isSuccess()) { 
 403                 Log_OC
.i(TAG
, "Upload of " + mOriginalStoragePath 
+ " to " + mRemotePath 
+ ": " + 
 404                         result
.getLogMessage()); 
 406                 if (result
.getException() != null
) { 
 407                     String complement 
= ""; 
 408                     if (!nameCheckPassed
) { 
 409                         complement 
= " (while checking file existence in server)"; 
 410                     } else if (!localCopyPassed
) { 
 411                         complement 
= " (while copying local file to " + 
 412                                 FileStorageUtils
.getSavePath(mAccount
.name
) 
 415                     Log_OC
.e(TAG
, "Upload of " + mOriginalStoragePath 
+ " to " + mRemotePath 
+ 
 416                             ": " + result
.getLogMessage() + complement
, result
.getException()); 
 418                     Log_OC
.e(TAG
, "Upload of " + mOriginalStoragePath 
+ " to " + mRemotePath 
+ 
 419                             ": " + result
.getLogMessage()); 
 427     private void createNewOCFile(String newRemotePath
) { 
 428         // a new OCFile instance must be created for a new remote path 
 429         OCFile newFile 
= new OCFile(newRemotePath
); 
 430         newFile
.setCreationTimestamp(mFile
.getCreationTimestamp()); 
 431         newFile
.setFileLength(mFile
.getFileLength()); 
 432         newFile
.setMimetype(mFile
.getMimetype()); 
 433         newFile
.setModificationTimestamp(mFile
.getModificationTimestamp()); 
 434         newFile
.setModificationTimestampAtLastSyncForData( 
 435                 mFile
.getModificationTimestampAtLastSyncForData()); 
 436         newFile
.setEtag(mFile
.getEtag()); 
 437         newFile
.setFavorite(mFile
.isFavorite()); 
 438         newFile
.setLastSyncDateForProperties(mFile
.getLastSyncDateForProperties()); 
 439         newFile
.setLastSyncDateForData(mFile
.getLastSyncDateForData()); 
 440         newFile
.setStoragePath(mFile
.getStoragePath()); 
 441         newFile
.setParentId(mFile
.getParentId()); 
 447      * Checks if remotePath does not exist in the server and returns it, or adds 
 448      * a suffix to it in order to avoid the server file is overwritten. 
 454     private String 
getAvailableRemotePath(OwnCloudClient wc
, String remotePath
) throws Exception 
{ 
 455         boolean check 
= existsFile(wc
, remotePath
); 
 460         int pos 
= remotePath
.lastIndexOf("."); 
 462         String extension 
= ""; 
 464             extension 
= remotePath
.substring(pos 
+ 1); 
 465             remotePath 
= remotePath
.substring(0, pos
); 
 469             suffix 
= " (" + count 
+ ")"; 
 471                 check 
= existsFile(wc
, remotePath 
+ suffix 
+ "." + extension
); 
 474                 check 
= existsFile(wc
, remotePath 
+ suffix
); 
 480             return remotePath 
+ suffix 
+ "." + extension
; 
 482             return remotePath 
+ suffix
; 
 486     private boolean existsFile(OwnCloudClient client
, String remotePath
){ 
 487         ExistenceCheckRemoteOperation existsOperation 
= 
 488                 new ExistenceCheckRemoteOperation(remotePath
, mContext
, false
); 
 489         RemoteOperationResult result 
= existsOperation
.execute(client
); 
 490         return result
.isSuccess(); 
 493     public void cancel() { 
 494         mCancellationRequested 
= new AtomicBoolean(true
); 
 495         if (mUploadOperation 
!= null
) { 
 496             mUploadOperation
.cancel();