UploadFileOperation.java 33 KB

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