UploadFileOperation.java 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author David A. Velasco
  5. * Copyright (C) 2016 ownCloud Inc.
  6. *
  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.
  10. *
  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.
  15. *
  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/>.
  18. *
  19. */
  20. package com.owncloud.android.operations;
  21. import android.accounts.Account;
  22. import android.content.Context;
  23. import android.net.Uri;
  24. import com.owncloud.android.datamodel.FileDataStorageManager;
  25. import com.owncloud.android.datamodel.OCFile;
  26. import com.owncloud.android.datamodel.ThumbnailsCacheManager;
  27. import com.owncloud.android.db.OCUpload;
  28. import com.owncloud.android.db.PreferenceManager;
  29. import com.owncloud.android.files.services.FileUploader;
  30. import com.owncloud.android.lib.common.OwnCloudClient;
  31. import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
  32. import com.owncloud.android.lib.common.network.ProgressiveDataTransferer;
  33. import com.owncloud.android.lib.common.operations.OperationCancelledException;
  34. import com.owncloud.android.lib.common.operations.RemoteOperation;
  35. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  36. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  37. import com.owncloud.android.lib.common.utils.Log_OC;
  38. import com.owncloud.android.lib.resources.files.ChunkedUploadRemoteFileOperation;
  39. import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
  40. import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
  41. import com.owncloud.android.lib.resources.files.RemoteFile;
  42. import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
  43. import com.owncloud.android.operations.common.SyncOperation;
  44. import com.owncloud.android.utils.ConnectivityUtils;
  45. import com.owncloud.android.utils.FileStorageUtils;
  46. import com.owncloud.android.utils.MimeType;
  47. import com.owncloud.android.utils.MimetypeIconUtil;
  48. import com.owncloud.android.utils.UriUtils;
  49. import org.apache.commons.httpclient.HttpStatus;
  50. import org.apache.commons.httpclient.methods.RequestEntity;
  51. import java.io.File;
  52. import java.io.FileInputStream;
  53. import java.io.FileOutputStream;
  54. import java.io.IOException;
  55. import java.io.InputStream;
  56. import java.io.OutputStream;
  57. import java.nio.channels.FileChannel;
  58. import java.util.HashSet;
  59. import java.util.Iterator;
  60. import java.util.Set;
  61. import java.util.concurrent.atomic.AtomicBoolean;
  62. /**
  63. * Operation performing the update in the ownCloud server
  64. * of a file that was modified locally.
  65. */
  66. public class UploadFileOperation extends SyncOperation {
  67. public static final int CREATED_BY_USER = 0;
  68. public static final int CREATED_AS_INSTANT_PICTURE = 1;
  69. public static final int CREATED_AS_INSTANT_VIDEO = 2;
  70. public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) {
  71. // MIME type
  72. if (mimeType == null || mimeType.length() <= 0) {
  73. mimeType = MimetypeIconUtil.getBestMimeTypeByFilename(localPath);
  74. }
  75. OCFile newFile = new OCFile(remotePath);
  76. newFile.setStoragePath(localPath);
  77. newFile.setLastSyncDateForProperties(0);
  78. newFile.setLastSyncDateForData(0);
  79. // size
  80. if (localPath != null && localPath.length() > 0) {
  81. File localFile = new File(localPath);
  82. newFile.setFileLength(localFile.length());
  83. newFile.setLastSyncDateForData(localFile.lastModified());
  84. } // don't worry about not assigning size, the problems with localPath
  85. // are checked when the UploadFileOperation instance is created
  86. newFile.setMimetype(mimeType);
  87. return newFile;
  88. }
  89. private static final String TAG = UploadFileOperation.class.getSimpleName();
  90. private Account mAccount;
  91. /**
  92. * OCFile which is to be uploaded.
  93. */
  94. private OCFile mFile;
  95. /**
  96. * Original OCFile which is to be uploaded in case file had to be renamed
  97. * (if forceOverwrite==false and remote file already exists).
  98. */
  99. private OCFile mOldFile;
  100. private String mRemotePath = null;
  101. private boolean mChunked = false;
  102. private boolean mRemoteFolderToBeCreated = false;
  103. private boolean mForceOverwrite = false;
  104. private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
  105. private int mCreatedBy = CREATED_BY_USER;
  106. private boolean mWasRenamed = false;
  107. private long mOCUploadId = -1;
  108. /**
  109. * Local path to file which is to be uploaded (before any possible renaming or moving).
  110. */
  111. private String mOriginalStoragePath = null;
  112. private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
  113. private OnRenameListener mRenameUploadListener;
  114. private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
  115. private final AtomicBoolean mUploadStarted = new AtomicBoolean(false);
  116. private Context mContext;
  117. private UploadRemoteFileOperation mUploadOperation;
  118. protected RequestEntity mEntity = null;
  119. public UploadFileOperation(Account account,
  120. OCFile file,
  121. OCUpload upload,
  122. boolean chunked,
  123. boolean forceOverwrite,
  124. int localBehaviour,
  125. Context context
  126. ) {
  127. if (account == null)
  128. throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " +
  129. "creation");
  130. if (upload == null)
  131. throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
  132. if (upload.getLocalPath() == null || upload.getLocalPath().length() <= 0) {
  133. throw new IllegalArgumentException(
  134. "Illegal file in UploadFileOperation; storage path invalid: "
  135. + upload.getLocalPath());
  136. }
  137. mAccount = account;
  138. if (file == null) {
  139. mFile = obtainNewOCFileToUpload(
  140. upload.getRemotePath(),
  141. upload.getLocalPath(),
  142. upload.getMimeType()
  143. );
  144. } else {
  145. mFile = file;
  146. }
  147. mRemotePath = upload.getRemotePath();
  148. mChunked = chunked;
  149. mForceOverwrite = forceOverwrite;
  150. mLocalBehaviour = localBehaviour;
  151. mOriginalStoragePath = mFile.getStoragePath();
  152. mContext = context;
  153. mOCUploadId = upload.getUploadId();
  154. mCreatedBy = upload.getCreadtedBy();
  155. mRemoteFolderToBeCreated = upload.isCreateRemoteFolder();
  156. }
  157. public Account getAccount() {
  158. return mAccount;
  159. }
  160. public String getFileName() {
  161. return (mFile != null) ? mFile.getFileName() : null;
  162. }
  163. public OCFile getFile() {
  164. return mFile;
  165. }
  166. /**
  167. * If remote file was renamed, return original OCFile which was uploaded. Is
  168. * null is file was not renamed.
  169. */
  170. public OCFile getOldFile() {
  171. return mOldFile;
  172. }
  173. public String getOriginalStoragePath() {
  174. return mOriginalStoragePath;
  175. }
  176. public String getStoragePath() {
  177. return mFile.getStoragePath();
  178. }
  179. public String getRemotePath() {
  180. return mFile.getRemotePath();
  181. }
  182. public String getMimeType() {
  183. return mFile.getMimetype();
  184. }
  185. public int getLocalBehaviour() {
  186. return mLocalBehaviour;
  187. }
  188. public void setRemoteFolderToBeCreated() {
  189. mRemoteFolderToBeCreated = true;
  190. }
  191. public boolean wasRenamed() {
  192. return mWasRenamed;
  193. }
  194. public void setCreatedBy(int createdBy) {
  195. mCreatedBy = createdBy;
  196. if (createdBy < CREATED_BY_USER || CREATED_AS_INSTANT_VIDEO < createdBy) {
  197. mCreatedBy = CREATED_BY_USER;
  198. }
  199. }
  200. public int getCreatedBy () {
  201. return mCreatedBy;
  202. }
  203. public boolean isInstantPicture() {
  204. return mCreatedBy == CREATED_AS_INSTANT_PICTURE;
  205. }
  206. public boolean isInstantVideo() {
  207. return mCreatedBy == CREATED_AS_INSTANT_VIDEO;
  208. }
  209. public void setOCUploadId(long id){
  210. mOCUploadId = id;
  211. }
  212. public long getOCUploadId() {
  213. return mOCUploadId;
  214. }
  215. public Set<OnDatatransferProgressListener> getDataTransferListeners() {
  216. return mDataTransferListeners;
  217. }
  218. public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
  219. synchronized (mDataTransferListeners) {
  220. mDataTransferListeners.add(listener);
  221. }
  222. if (mEntity != null) {
  223. ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener);
  224. }
  225. if(mUploadOperation != null){
  226. mUploadOperation.addDatatransferProgressListener(listener);
  227. }
  228. }
  229. public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
  230. synchronized (mDataTransferListeners) {
  231. mDataTransferListeners.remove(listener);
  232. }
  233. if (mEntity != null) {
  234. ((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener);
  235. }
  236. if(mUploadOperation != null){
  237. mUploadOperation.removeDatatransferProgressListener(listener);
  238. }
  239. }
  240. public void addRenameUploadListener (OnRenameListener listener) {
  241. mRenameUploadListener = listener;
  242. }
  243. @Override
  244. protected RemoteOperationResult run(OwnCloudClient client) {
  245. mCancellationRequested.set(false);
  246. mUploadStarted.set(true);
  247. RemoteOperationResult result = null;
  248. File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null;
  249. try {
  250. /// Check that connectivity conditions are met and delays the upload otherwise
  251. if (delayForWifi()) {
  252. Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
  253. return new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
  254. }
  255. // Check if charging conditions are met and delays the upload otherwise
  256. if (delayForCharging()){
  257. Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath());
  258. return new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
  259. }
  260. /// check if the file continues existing before schedule the operation
  261. if (!originalFile.exists()) {
  262. Log_OC.d(TAG, mOriginalStoragePath.toString() + " not exists anymore");
  263. return new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
  264. }
  265. /// check the existence of the parent folder for the file to upload
  266. String remoteParentPath = new File(getRemotePath()).getParent();
  267. remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
  268. remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
  269. result = grantFolderExistence(remoteParentPath, client);
  270. if (!result.isSuccess()) {
  271. return result;
  272. }
  273. /// set parent local id in uploading file
  274. OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
  275. mFile.setParentId(parent.getFileId());
  276. /// automatic rename of file to upload in case of name collision in server
  277. Log_OC.d(TAG, "Checking name collision in server");
  278. if (!mForceOverwrite) {
  279. String remotePath = getAvailableRemotePath(client, mRemotePath);
  280. mWasRenamed = !remotePath.equals(mRemotePath);
  281. if (mWasRenamed) {
  282. createNewOCFile(remotePath);
  283. Log_OC.d(TAG, "File renamed as " + remotePath);
  284. }
  285. mRemotePath = remotePath;
  286. mRenameUploadListener.onRenameUpload();
  287. }
  288. if (mCancellationRequested.get()) {
  289. throw new OperationCancelledException();
  290. }
  291. String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
  292. expectedFile = new File(expectedPath);
  293. /// copy the file locally before uploading
  294. if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY &&
  295. !mOriginalStoragePath.equals(expectedPath)) {
  296. String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
  297. mFile.setStoragePath(temporalPath);
  298. temporalFile = new File(temporalPath);
  299. result = copy(originalFile, temporalFile);
  300. if (result != null) {
  301. return result;
  302. }
  303. }
  304. if (mCancellationRequested.get()) {
  305. throw new OperationCancelledException();
  306. }
  307. /// perform the upload
  308. if ( mChunked &&
  309. (new File(mFile.getStoragePath())).length() >
  310. ChunkedUploadRemoteFileOperation.CHUNK_SIZE ) {
  311. mUploadOperation = new ChunkedUploadRemoteFileOperation(mContext, mFile.getStoragePath(),
  312. mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict());
  313. } else {
  314. mUploadOperation = new UploadRemoteFileOperation(mFile.getStoragePath(),
  315. mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict());
  316. }
  317. Iterator <OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
  318. while (listener.hasNext()) {
  319. mUploadOperation.addDatatransferProgressListener(listener.next());
  320. }
  321. if (mCancellationRequested.get()) {
  322. throw new OperationCancelledException();
  323. }
  324. result = mUploadOperation.execute(client);
  325. /// move local temporal file or original file to its corresponding
  326. // location in the ownCloud local folder
  327. if (result.isSuccess()) {
  328. if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) {
  329. String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
  330. if (mOriginalStoragePath.equals(temporalPath)) {
  331. // delete local file is was pre-copied in temporary folder (see .ui.helpers.UriUploader)
  332. temporalFile = new File(temporalPath);
  333. temporalFile.delete();
  334. }
  335. mFile.setStoragePath("");
  336. } else {
  337. mFile.setStoragePath(expectedPath);
  338. if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
  339. move(temporalFile, expectedFile);
  340. } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
  341. move(originalFile, expectedFile);
  342. getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
  343. }
  344. FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
  345. }
  346. } else if (result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED ) {
  347. result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  348. }
  349. } catch (Exception e) {
  350. result = new RemoteOperationResult(e);
  351. } finally {
  352. mUploadStarted.set(false);
  353. if (temporalFile != null && !originalFile.equals(temporalFile)) {
  354. temporalFile.delete();
  355. }
  356. if (result == null){
  357. result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
  358. }
  359. if (result.isSuccess()) {
  360. Log_OC.i(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " +
  361. result.getLogMessage());
  362. } else {
  363. if (result.getException() != null) {
  364. if(result.isCancelled()){
  365. Log_OC.w(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
  366. ": " + result.getLogMessage());
  367. } else {
  368. Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
  369. ": " + result.getLogMessage(), result.getException());
  370. }
  371. } else {
  372. Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
  373. ": " + result.getLogMessage());
  374. }
  375. }
  376. }
  377. if (result.isSuccess()) {
  378. saveUploadedFile(client);
  379. } else if (result.getCode() == ResultCode.SYNC_CONFLICT) {
  380. getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
  381. }
  382. return result;
  383. }
  384. /**
  385. * Checks origin of current upload and network type to decide if should be delayed, according to
  386. * current user preferences.
  387. *
  388. * @return 'True' if the upload was delayed until WiFi connectivity is available, 'false' otherwise.
  389. */
  390. private boolean delayForWifi() {
  391. boolean delayInstantPicture = (
  392. isInstantPicture() && PreferenceManager.instantPictureUploadViaWiFiOnly(mContext)
  393. );
  394. boolean delayInstantVideo = (
  395. isInstantVideo() && PreferenceManager.instantVideoUploadViaWiFiOnly(mContext)
  396. );
  397. return (
  398. (delayInstantPicture || delayInstantVideo) &&
  399. !ConnectivityUtils.isAppConnectedViaWiFi(mContext)
  400. );
  401. }
  402. /**
  403. * Check if upload should be delayed due to not charging
  404. *
  405. * @return 'True' if the upload was delayed until device is charging, 'false' otherwise.
  406. */
  407. private boolean delayForCharging() {
  408. boolean delayInstantPicture = isInstantPicture() &&
  409. PreferenceManager.instantPictureUploadWhenChargingOnly(mContext);
  410. boolean delayInstantVideo = isInstantVideo() &&
  411. PreferenceManager.instantVideoUploadViaWiFiOnly(mContext);
  412. return ((delayInstantPicture || delayInstantVideo)
  413. && !ConnectivityUtils.isCharging(mContext));
  414. }
  415. /**
  416. * Checks the existence of the folder where the current file will be uploaded both
  417. * in the remote server and in the local database.
  418. * <p/>
  419. * If the upload is set to enforce the creation of the folder, the method tries to
  420. * create it both remote and locally.
  421. *
  422. * @param pathToGrant Full remote path whose existence will be granted.
  423. * @return An {@link OCFile} instance corresponding to the folder where the file
  424. * will be uploaded.
  425. */
  426. private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
  427. RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, mContext, false);
  428. RemoteOperationResult result = operation.execute(client);
  429. if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
  430. SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true);
  431. result = syncOp.execute(client, getStorageManager());
  432. }
  433. if (result.isSuccess()) {
  434. OCFile parentDir = getStorageManager().getFileByPath(pathToGrant);
  435. if (parentDir == null) {
  436. parentDir = createLocalFolder(pathToGrant);
  437. }
  438. if (parentDir != null) {
  439. result = new RemoteOperationResult(ResultCode.OK);
  440. } else {
  441. result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
  442. }
  443. }
  444. return result;
  445. }
  446. private OCFile createLocalFolder(String remotePath) {
  447. String parentPath = new File(remotePath).getParent();
  448. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
  449. parentPath : parentPath + OCFile.PATH_SEPARATOR;
  450. OCFile parent = getStorageManager().getFileByPath(parentPath);
  451. if (parent == null) {
  452. parent = createLocalFolder(parentPath);
  453. }
  454. if (parent != null) {
  455. OCFile createdFolder = new OCFile(remotePath);
  456. createdFolder.setMimetype(MimeType.DIRECTORY);
  457. createdFolder.setParentId(parent.getFileId());
  458. getStorageManager().saveFile(createdFolder);
  459. return createdFolder;
  460. }
  461. return null;
  462. }
  463. /**
  464. * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false.
  465. * New file is stored as mFile, original as mOldFile.
  466. * @param newRemotePath new remote path
  467. */
  468. private void createNewOCFile(String newRemotePath) {
  469. // a new OCFile instance must be created for a new remote path
  470. OCFile newFile = new OCFile(newRemotePath);
  471. newFile.setCreationTimestamp(mFile.getCreationTimestamp());
  472. newFile.setFileLength(mFile.getFileLength());
  473. newFile.setMimetype(mFile.getMimetype());
  474. newFile.setModificationTimestamp(mFile.getModificationTimestamp());
  475. newFile.setModificationTimestampAtLastSyncForData(
  476. mFile.getModificationTimestampAtLastSyncForData()
  477. );
  478. newFile.setEtag(mFile.getEtag());
  479. newFile.setFavorite(mFile.isFavorite());
  480. newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
  481. newFile.setLastSyncDateForData(mFile.getLastSyncDateForData());
  482. newFile.setStoragePath(mFile.getStoragePath());
  483. newFile.setParentId(mFile.getParentId());
  484. mOldFile = mFile;
  485. mFile = newFile;
  486. }
  487. /**
  488. * Checks if remotePath does not exist in the server and returns it, or adds
  489. * a suffix to it in order to avoid the server file is overwritten.
  490. *
  491. * @param wc
  492. * @param remotePath
  493. * @return
  494. */
  495. private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) {
  496. boolean check = existsFile(wc, remotePath);
  497. if (!check) {
  498. return remotePath;
  499. }
  500. int pos = remotePath.lastIndexOf(".");
  501. String suffix = "";
  502. String extension = "";
  503. if (pos >= 0) {
  504. extension = remotePath.substring(pos + 1);
  505. remotePath = remotePath.substring(0, pos);
  506. }
  507. int count = 2;
  508. do {
  509. suffix = " (" + count + ")";
  510. if (pos >= 0) {
  511. check = existsFile(wc, remotePath + suffix + "." + extension);
  512. }
  513. else {
  514. check = existsFile(wc, remotePath + suffix);
  515. }
  516. count++;
  517. } while (check);
  518. if (pos >= 0) {
  519. return remotePath + suffix + "." + extension;
  520. } else {
  521. return remotePath + suffix;
  522. }
  523. }
  524. private boolean existsFile(OwnCloudClient client, String remotePath){
  525. ExistenceCheckRemoteOperation existsOperation =
  526. new ExistenceCheckRemoteOperation(remotePath, mContext, false);
  527. RemoteOperationResult result = existsOperation.execute(client);
  528. return result.isSuccess();
  529. }
  530. /**
  531. * Allows to cancel the actual upload operation. If actual upload operating
  532. * is in progress it is cancelled, if upload preparation is being performed
  533. * upload will not take place.
  534. */
  535. public void cancel() {
  536. if (mUploadOperation == null) {
  537. if (mUploadStarted.get()) {
  538. Log_OC.d(TAG, "Cancelling upload during upload preparations.");
  539. mCancellationRequested.set(true);
  540. } else {
  541. Log_OC.e(TAG, "No upload in progress. This should not happen.");
  542. }
  543. } else {
  544. Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
  545. mUploadOperation.cancel();
  546. }
  547. }
  548. /**
  549. * As soon as this method return true, upload can be cancel via cancel().
  550. */
  551. public boolean isUploadInProgress() {
  552. return mUploadStarted.get();
  553. }
  554. /**
  555. * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
  556. * TODO use Exceptions instead
  557. *
  558. * @param sourceFile Source file to copy.
  559. * @param targetFile Target location to copy the file.
  560. * @return {@link RemoteOperationResult}
  561. * @throws IOException
  562. */
  563. private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException {
  564. Log_OC.d(TAG, "Copying local file");
  565. RemoteOperationResult result = null;
  566. if (FileStorageUtils.getUsableSpace(mAccount.name) < sourceFile.length()) {
  567. result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
  568. return result; // error condition when the file should be copied
  569. } else {
  570. Log_OC.d(TAG, "Creating temporal folder");
  571. File temporalParent = targetFile.getParentFile();
  572. temporalParent.mkdirs();
  573. if (!temporalParent.isDirectory()) {
  574. throw new IOException(
  575. "Unexpected error: parent directory could not be created");
  576. }
  577. Log_OC.d(TAG, "Creating temporal file");
  578. targetFile.createNewFile();
  579. if (!targetFile.isFile()) {
  580. throw new IOException(
  581. "Unexpected error: target file could not be created");
  582. }
  583. Log_OC.d(TAG, "Copying file contents");
  584. InputStream in = null;
  585. OutputStream out = null;
  586. try {
  587. if (!mOriginalStoragePath.equals(targetFile.getAbsolutePath())) {
  588. // In case document provider schema as 'content://'
  589. if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
  590. Uri uri = Uri.parse(mOriginalStoragePath);
  591. in = mContext.getContentResolver().openInputStream(uri);
  592. } else {
  593. in = new FileInputStream(sourceFile);
  594. }
  595. out = new FileOutputStream(targetFile);
  596. int nRead;
  597. byte[] buf = new byte[4096];
  598. while (!mCancellationRequested.get() &&
  599. (nRead = in.read(buf)) > -1) {
  600. out.write(buf, 0, nRead);
  601. }
  602. out.flush();
  603. } // else: weird but possible situation, nothing to copy
  604. if (mCancellationRequested.get()) {
  605. result = new RemoteOperationResult(new OperationCancelledException());
  606. return result;
  607. }
  608. } catch (Exception e) {
  609. result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
  610. return result;
  611. } finally {
  612. try {
  613. if (in != null)
  614. in.close();
  615. } catch (Exception e) {
  616. Log_OC.d(TAG, "Weird exception while closing input stream for " +
  617. mOriginalStoragePath + " (ignoring)", e);
  618. }
  619. try {
  620. if (out != null)
  621. out.close();
  622. } catch (Exception e) {
  623. Log_OC.d(TAG, "Weird exception while closing output stream for " +
  624. targetFile.getAbsolutePath() + " (ignoring)", e);
  625. }
  626. }
  627. }
  628. return result;
  629. }
  630. /**
  631. * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
  632. * TODO use Exceptions instead
  633. *
  634. * TODO refactor both this and 'copy' in a single method
  635. *
  636. * @param sourceFile Source file to move.
  637. * @param targetFile Target location to move the file.
  638. * @return {@link RemoteOperationResult}
  639. * @throws IOException
  640. */
  641. private void move(File sourceFile, File targetFile) throws IOException {
  642. if (!targetFile.equals(sourceFile)) {
  643. File expectedFolder = targetFile.getParentFile();
  644. expectedFolder.mkdirs();
  645. if (expectedFolder.isDirectory()){
  646. if (!sourceFile.renameTo(targetFile)){
  647. // try to copy and then delete
  648. targetFile.createNewFile();
  649. FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
  650. FileChannel outChannel = new FileOutputStream(targetFile).getChannel();
  651. try {
  652. inChannel.transferTo(0, inChannel.size(), outChannel);
  653. sourceFile.delete();
  654. } catch (Exception e){
  655. mFile.setStoragePath(""); // forget the local file
  656. // by now, treat this as a success; the file was uploaded
  657. // the best option could be show a warning message
  658. }
  659. finally {
  660. if (inChannel != null) inChannel.close();
  661. if (outChannel != null) outChannel.close();
  662. }
  663. }
  664. } else {
  665. mFile.setStoragePath("");
  666. }
  667. }
  668. }
  669. /**
  670. * Saves a OC File after a successful upload.
  671. * <p/>
  672. * A PROPFIND is necessary to keep the props in the local database
  673. * synchronized with the server, specially the modification time and Etag
  674. * (where available)
  675. * <p/>
  676. */
  677. private void saveUploadedFile(OwnCloudClient client) {
  678. OCFile file = mFile;
  679. if (file.fileExists()) {
  680. file = getStorageManager().getFileById(file.getFileId());
  681. }
  682. long syncDate = System.currentTimeMillis();
  683. file.setLastSyncDateForData(syncDate);
  684. // new PROPFIND to keep data consistent with server
  685. // in theory, should return the same we already have
  686. // TODO from the appropriate OC server version, get data from last PUT response headers, instead
  687. // TODO of a new PROPFIND; the latter may fail, specially for chunked uploads
  688. ReadRemoteFileOperation operation = new ReadRemoteFileOperation(getRemotePath());
  689. RemoteOperationResult result = operation.execute(client);
  690. if (result.isSuccess()) {
  691. updateOCFile(file, (RemoteFile) result.getData().get(0));
  692. file.setLastSyncDateForProperties(syncDate);
  693. } else {
  694. Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt...");
  695. }
  696. if (mWasRenamed) {
  697. OCFile oldFile = getStorageManager().getFileByPath(mOldFile.getRemotePath());
  698. if (oldFile != null) {
  699. oldFile.setStoragePath(null);
  700. getStorageManager().saveFile(oldFile);
  701. getStorageManager().saveConflict(oldFile, null);
  702. }
  703. // else: it was just an automatic renaming due to a name
  704. // coincidence; nothing else is needed, the storagePath is right
  705. // in the instance returned by mCurrentUpload.getFile()
  706. }
  707. file.setNeedsUpdateThumbnail(true);
  708. getStorageManager().saveFile(file);
  709. getStorageManager().saveConflict(file, null);
  710. FileDataStorageManager.triggerMediaScan(file.getStoragePath());
  711. // generate new Thumbnail
  712. final ThumbnailsCacheManager.ThumbnailGenerationTask task =
  713. new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), mAccount);
  714. task.execute(file);
  715. }
  716. private void updateOCFile(OCFile file, RemoteFile remoteFile) {
  717. file.setCreationTimestamp(remoteFile.getCreationTimestamp());
  718. file.setFileLength(remoteFile.getLength());
  719. file.setMimetype(remoteFile.getMimeType());
  720. file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
  721. file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
  722. file.setEtag(remoteFile.getEtag());
  723. file.setRemoteId(remoteFile.getRemoteId());
  724. }
  725. public interface OnRenameListener {
  726. void onRenameUpload();
  727. }
  728. }