UploadFileOperation.java 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363
  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. package com.owncloud.android.operations;
  20. import android.accounts.Account;
  21. import android.annotation.SuppressLint;
  22. import android.content.Context;
  23. import android.net.Uri;
  24. import android.os.Build;
  25. import android.support.annotation.RequiresApi;
  26. import com.evernote.android.job.JobRequest;
  27. import com.evernote.android.job.util.Device;
  28. import com.google.gson.reflect.TypeToken;
  29. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  30. import com.owncloud.android.datamodel.DecryptedFolderMetadata;
  31. import com.owncloud.android.datamodel.EncryptedFolderMetadata;
  32. import com.owncloud.android.datamodel.FileDataStorageManager;
  33. import com.owncloud.android.datamodel.OCFile;
  34. import com.owncloud.android.datamodel.ThumbnailsCacheManager;
  35. import com.owncloud.android.datamodel.UploadsStorageManager;
  36. import com.owncloud.android.db.OCUpload;
  37. import com.owncloud.android.files.services.FileUploader;
  38. import com.owncloud.android.lib.common.OwnCloudClient;
  39. import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
  40. import com.owncloud.android.lib.common.network.ProgressiveDataTransferer;
  41. import com.owncloud.android.lib.common.operations.OperationCancelledException;
  42. import com.owncloud.android.lib.common.operations.RemoteOperation;
  43. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  44. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  45. import com.owncloud.android.lib.common.utils.Log_OC;
  46. import com.owncloud.android.lib.resources.files.ChunkedUploadRemoteFileOperation;
  47. import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
  48. import com.owncloud.android.lib.resources.files.GetMetadataOperation;
  49. import com.owncloud.android.lib.resources.files.LockFileOperation;
  50. import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
  51. import com.owncloud.android.lib.resources.files.RemoteFile;
  52. import com.owncloud.android.lib.resources.files.StoreMetadataOperation;
  53. import com.owncloud.android.lib.resources.files.UnlockFileOperation;
  54. import com.owncloud.android.lib.resources.files.UpdateMetadataOperation;
  55. import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
  56. import com.owncloud.android.operations.common.SyncOperation;
  57. import com.owncloud.android.utils.ConnectivityUtils;
  58. import com.owncloud.android.utils.EncryptionUtils;
  59. import com.owncloud.android.utils.FileStorageUtils;
  60. import com.owncloud.android.utils.MimeType;
  61. import com.owncloud.android.utils.MimeTypeUtil;
  62. import com.owncloud.android.utils.PowerUtils;
  63. import com.owncloud.android.utils.UriUtils;
  64. import org.apache.commons.httpclient.HttpStatus;
  65. import org.apache.commons.httpclient.methods.RequestEntity;
  66. import org.lukhnos.nnio.file.Files;
  67. import org.lukhnos.nnio.file.Paths;
  68. import java.io.File;
  69. import java.io.FileInputStream;
  70. import java.io.FileNotFoundException;
  71. import java.io.FileOutputStream;
  72. import java.io.IOException;
  73. import java.io.InputStream;
  74. import java.io.OutputStream;
  75. import java.io.RandomAccessFile;
  76. import java.nio.channels.FileChannel;
  77. import java.nio.channels.FileLock;
  78. import java.nio.channels.OverlappingFileLockException;
  79. import java.util.HashMap;
  80. import java.util.HashSet;
  81. import java.util.Iterator;
  82. import java.util.Set;
  83. import java.util.UUID;
  84. import java.util.concurrent.atomic.AtomicBoolean;
  85. /**
  86. * Operation performing the update in the ownCloud server
  87. * of a file that was modified locally.
  88. */
  89. public class UploadFileOperation extends SyncOperation {
  90. private static final String TAG = UploadFileOperation.class.getSimpleName();
  91. public static final int CREATED_BY_USER = 0;
  92. public static final int CREATED_AS_INSTANT_PICTURE = 1;
  93. public static final int CREATED_AS_INSTANT_VIDEO = 2;
  94. /**
  95. * OCFile which is to be uploaded.
  96. */
  97. private OCFile mFile;
  98. /**
  99. * Original OCFile which is to be uploaded in case file had to be renamed
  100. * (if forceOverwrite==false and remote file already exists).
  101. */
  102. private OCFile mOldFile;
  103. private String mRemotePath;
  104. private String mFolderUnlockToken;
  105. private boolean mRemoteFolderToBeCreated;
  106. private boolean mForceOverwrite;
  107. private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
  108. private int mCreatedBy = CREATED_BY_USER;
  109. private boolean mOnWifiOnly;
  110. private boolean mWhileChargingOnly;
  111. private boolean mIgnoringPowerSaveMode;
  112. private boolean mWasRenamed;
  113. private long mOCUploadId;
  114. /**
  115. * Local path to file which is to be uploaded (before any possible renaming or moving).
  116. */
  117. private String mOriginalStoragePath = null;
  118. private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
  119. private OnRenameListener mRenameUploadListener;
  120. private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
  121. private final AtomicBoolean mUploadStarted = new AtomicBoolean(false);
  122. private Context mContext;
  123. private UploadRemoteFileOperation mUploadOperation;
  124. protected RequestEntity mEntity = null;
  125. private Account mAccount;
  126. private OCUpload mUpload;
  127. private UploadsStorageManager uploadsStorageManager;
  128. private boolean encryptedAncestor;
  129. public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) {
  130. // MIME type
  131. if (mimeType == null || mimeType.length() <= 0) {
  132. mimeType = MimeTypeUtil.getBestMimeTypeByFilename(localPath);
  133. }
  134. OCFile newFile = new OCFile(remotePath);
  135. newFile.setStoragePath(localPath);
  136. newFile.setLastSyncDateForProperties(0);
  137. newFile.setLastSyncDateForData(0);
  138. // size
  139. if (localPath != null && localPath.length() > 0) {
  140. File localFile = new File(localPath);
  141. newFile.setFileLength(localFile.length());
  142. newFile.setLastSyncDateForData(localFile.lastModified());
  143. } // don't worry about not assigning size, the problems with localPath
  144. // are checked when the UploadFileOperation instance is created
  145. newFile.setMimetype(mimeType);
  146. return newFile;
  147. }
  148. public UploadFileOperation(Account account,
  149. OCFile file,
  150. OCUpload upload,
  151. boolean forceOverwrite,
  152. int localBehaviour,
  153. Context context,
  154. boolean onWifiOnly,
  155. boolean whileChargingOnly
  156. ) {
  157. if (account == null) {
  158. throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " + "creation");
  159. }
  160. if (upload == null) {
  161. throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
  162. }
  163. if (upload.getLocalPath() == null || upload.getLocalPath().length() <= 0) {
  164. throw new IllegalArgumentException(
  165. "Illegal file in UploadFileOperation; storage path invalid: "
  166. + upload.getLocalPath());
  167. }
  168. mAccount = account;
  169. mUpload = upload;
  170. if (file == null) {
  171. mFile = obtainNewOCFileToUpload(
  172. upload.getRemotePath(),
  173. upload.getLocalPath(),
  174. upload.getMimeType()
  175. );
  176. } else {
  177. mFile = file;
  178. }
  179. mOnWifiOnly = onWifiOnly;
  180. mWhileChargingOnly = whileChargingOnly;
  181. mRemotePath = upload.getRemotePath();
  182. mForceOverwrite = forceOverwrite;
  183. mLocalBehaviour = localBehaviour;
  184. mOriginalStoragePath = mFile.getStoragePath();
  185. mContext = context;
  186. mOCUploadId = upload.getUploadId();
  187. mCreatedBy = upload.getCreatedBy();
  188. mRemoteFolderToBeCreated = upload.isCreateRemoteFolder();
  189. // Ignore power save mode only if user explicitly created this upload
  190. mIgnoringPowerSaveMode = (mCreatedBy == CREATED_BY_USER);
  191. mFolderUnlockToken = upload.getFolderUnlockToken();
  192. }
  193. public boolean getIsWifiRequired() {
  194. return mOnWifiOnly;
  195. }
  196. public boolean getIsChargingRequired() {
  197. return mWhileChargingOnly;
  198. }
  199. public boolean getIsIgnoringPowerSaveMode() { return mIgnoringPowerSaveMode; }
  200. public Account getAccount() {
  201. return mAccount;
  202. }
  203. public String getFileName() {
  204. return (mFile != null) ? mFile.getFileName() : null;
  205. }
  206. public OCFile getFile() {
  207. return mFile;
  208. }
  209. /**
  210. * If remote file was renamed, return original OCFile which was uploaded. Is
  211. * null is file was not renamed.
  212. */
  213. public OCFile getOldFile() {
  214. return mOldFile;
  215. }
  216. public String getOriginalStoragePath() {
  217. return mOriginalStoragePath;
  218. }
  219. public String getStoragePath() {
  220. return mFile.getStoragePath();
  221. }
  222. public String getRemotePath() {
  223. return mFile.getRemotePath();
  224. }
  225. public String getDecryptedRemotePath() {
  226. return mFile.getDecryptedRemotePath();
  227. }
  228. public String getMimeType() {
  229. return mFile.getMimeType();
  230. }
  231. public int getLocalBehaviour() {
  232. return mLocalBehaviour;
  233. }
  234. public void setRemoteFolderToBeCreated() {
  235. mRemoteFolderToBeCreated = true;
  236. }
  237. public boolean wasRenamed() {
  238. return mWasRenamed;
  239. }
  240. public void setCreatedBy(int createdBy) {
  241. mCreatedBy = createdBy;
  242. if (createdBy < CREATED_BY_USER || CREATED_AS_INSTANT_VIDEO < createdBy) {
  243. mCreatedBy = CREATED_BY_USER;
  244. }
  245. }
  246. public int getCreatedBy() {
  247. return mCreatedBy;
  248. }
  249. public boolean isInstantPicture() {
  250. return mCreatedBy == CREATED_AS_INSTANT_PICTURE;
  251. }
  252. public boolean isInstantVideo() {
  253. return mCreatedBy == CREATED_AS_INSTANT_VIDEO;
  254. }
  255. public void setOCUploadId(long id) {
  256. mOCUploadId = id;
  257. }
  258. public long getOCUploadId() {
  259. return mOCUploadId;
  260. }
  261. public Set<OnDatatransferProgressListener> getDataTransferListeners() {
  262. return mDataTransferListeners;
  263. }
  264. public void addDataTransferProgressListener(OnDatatransferProgressListener listener) {
  265. synchronized (mDataTransferListeners) {
  266. mDataTransferListeners.add(listener);
  267. }
  268. if (mEntity != null) {
  269. ((ProgressiveDataTransferer) mEntity).addDatatransferProgressListener(listener);
  270. }
  271. if (mUploadOperation != null) {
  272. mUploadOperation.addDatatransferProgressListener(listener);
  273. }
  274. }
  275. public void removeDataTransferProgressListener(OnDatatransferProgressListener listener) {
  276. synchronized (mDataTransferListeners) {
  277. mDataTransferListeners.remove(listener);
  278. }
  279. if (mEntity != null) {
  280. ((ProgressiveDataTransferer) mEntity).removeDatatransferProgressListener(listener);
  281. }
  282. if (mUploadOperation != null) {
  283. mUploadOperation.removeDatatransferProgressListener(listener);
  284. }
  285. }
  286. public void addRenameUploadListener(OnRenameListener listener) {
  287. mRenameUploadListener = listener;
  288. }
  289. public Context getContext() {
  290. return mContext;
  291. }
  292. @Override
  293. @SuppressWarnings("PMD.AvoidDuplicateLiterals")
  294. protected RemoteOperationResult run(OwnCloudClient client) {
  295. mCancellationRequested.set(false);
  296. mUploadStarted.set(true);
  297. uploadsStorageManager = new UploadsStorageManager(mContext.getContentResolver(), mContext);
  298. for (OCUpload ocUpload : uploadsStorageManager.getAllStoredUploads()) {
  299. if (ocUpload.getUploadId() == getOCUploadId()) {
  300. ocUpload.setFileSize(0);
  301. uploadsStorageManager.updateUpload(ocUpload);
  302. break;
  303. }
  304. }
  305. String remoteParentPath = new File(getRemotePath()).getParent();
  306. remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
  307. remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
  308. OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
  309. // in case of a fresh upload with subfolder, where parent does not exist yet
  310. if (parent == null && (mFolderUnlockToken == null || mFolderUnlockToken.isEmpty())) {
  311. // try to create folder
  312. RemoteOperationResult result = grantFolderExistence(remoteParentPath, client);
  313. if (!result.isSuccess()) {
  314. return result;
  315. }
  316. parent = getStorageManager().getFileByPath(remoteParentPath);
  317. if (parent == null) {
  318. return new RemoteOperationResult(false, "Parent folder not found", HttpStatus.SC_NOT_FOUND);
  319. }
  320. }
  321. // parent file is not null anymore:
  322. // - it was created on fresh upload or
  323. // - resume of encrypted upload, then parent file exists already as unlock is only for direct parent
  324. mFile.setParentId(parent.getFileId());
  325. // try to unlock folder with stored token, e.g. when upload needs to be resumed or app crashed
  326. // the parent folder should exist as it is a resume of a broken upload
  327. if (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()) {
  328. UnlockFileOperation unlockFileOperation = new UnlockFileOperation(parent.getLocalId(), mFolderUnlockToken);
  329. RemoteOperationResult unlockFileOperationResult = unlockFileOperation.execute(client, true);
  330. if (!unlockFileOperationResult.isSuccess()) {
  331. return unlockFileOperationResult;
  332. }
  333. }
  334. // check if any parent is encrypted
  335. encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
  336. mFile.setEncrypted(encryptedAncestor);
  337. if (encryptedAncestor) {
  338. Log_OC.d(TAG, "encrypted upload");
  339. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  340. return encryptedUpload(client, parent);
  341. } else {
  342. Log_OC.e(TAG, "Encrypted upload on old Android API");
  343. return new RemoteOperationResult(ResultCode.OLD_ANDROID_API);
  344. }
  345. } else {
  346. Log_OC.d(TAG, "normal upload");
  347. return normalUpload(client);
  348. }
  349. }
  350. @SuppressLint("AndroidLintUseSparseArrays") // gson cannot handle sparse arrays easily, therefore use hashmap
  351. @RequiresApi(api = Build.VERSION_CODES.KITKAT)
  352. private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile parentFile) {
  353. RemoteOperationResult result = null;
  354. File temporalFile = null;
  355. File originalFile = new File(mOriginalStoragePath);
  356. File expectedFile = null;
  357. FileLock fileLock = null;
  358. long size;
  359. boolean metadataExists = false;
  360. String token = null;
  361. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContext().getContentResolver());
  362. String privateKey = arbitraryDataProvider.getValue(getAccount().name, EncryptionUtils.PRIVATE_KEY);
  363. String publicKey = arbitraryDataProvider.getValue(getAccount().name, EncryptionUtils.PUBLIC_KEY);
  364. try {
  365. // check conditions
  366. result = checkConditions(originalFile);
  367. if (result != null) {
  368. return result;
  369. }
  370. /***** E2E *****/
  371. // Lock folder
  372. LockFileOperation lockFileOperation = new LockFileOperation(parentFile.getLocalId());
  373. RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client, true);
  374. if (lockFileOperationResult.isSuccess()) {
  375. token = (String) lockFileOperationResult.getData().get(0);
  376. // immediately store it
  377. mUpload.setFolderUnlockToken(token);
  378. uploadsStorageManager.updateUpload(mUpload);
  379. } else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
  380. throw new Exception("Forbidden! Please try again later.)");
  381. } else {
  382. throw new Exception("Unknown error!");
  383. }
  384. // Update metadata
  385. GetMetadataOperation getMetadataOperation = new GetMetadataOperation(parentFile.getLocalId());
  386. RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client, true);
  387. DecryptedFolderMetadata metadata;
  388. if (getMetadataOperationResult.isSuccess()) {
  389. metadataExists = true;
  390. // decrypt metadata
  391. String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
  392. EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
  393. serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
  394. });
  395. metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
  396. } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
  397. // new metadata
  398. metadata = new DecryptedFolderMetadata();
  399. metadata.setMetadata(new DecryptedFolderMetadata.Metadata());
  400. metadata.getMetadata().setMetadataKeys(new HashMap<>());
  401. String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
  402. String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
  403. metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey);
  404. } else {
  405. // TODO error
  406. throw new Exception("something wrong");
  407. }
  408. /***** E2E *****/
  409. // check name collision
  410. checkNameCollision(client, metadata, parentFile.isEncrypted());
  411. String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
  412. expectedFile = new File(expectedPath);
  413. result = copyFile(originalFile, expectedPath);
  414. if (result != null) {
  415. return result;
  416. }
  417. // Get the last modification date of the file from the file system
  418. Long timeStampLong = originalFile.lastModified() / 1000;
  419. String timeStamp = timeStampLong.toString();
  420. /***** E2E *****/
  421. // Key, always generate new one
  422. byte[] key = EncryptionUtils.generateKey();
  423. // IV, always generate new one
  424. byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
  425. EncryptionUtils.EncryptedFile encryptedFile = EncryptionUtils.encryptFile(mFile, key, iv);
  426. // new random file name, check if it exists in metadata
  427. String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
  428. while (metadata.getFiles().get(encryptedFileName) != null) {
  429. encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
  430. }
  431. mFile.setEncryptedFileName(encryptedFileName);
  432. File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
  433. FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
  434. fileOutputStream.write(encryptedFile.encryptedBytes);
  435. fileOutputStream.close();
  436. /***** E2E *****/
  437. FileChannel channel = null;
  438. try {
  439. channel = new RandomAccessFile(mFile.getStoragePath(), "rw").getChannel();
  440. fileLock = channel.tryLock();
  441. } catch (FileNotFoundException e) {
  442. // this basically means that the file is on SD card
  443. // try to copy file to temporary dir if it doesn't exist
  444. String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
  445. mFile.setStoragePath(temporalPath);
  446. temporalFile = new File(temporalPath);
  447. Files.deleteIfExists(Paths.get(temporalPath));
  448. result = copy(originalFile, temporalFile);
  449. if (result == null) {
  450. if (temporalFile.length() == originalFile.length()) {
  451. channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
  452. fileLock = channel.tryLock();
  453. } else {
  454. result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
  455. }
  456. }
  457. }
  458. try {
  459. size = channel.size();
  460. } catch (IOException e1) {
  461. size = new File(mFile.getStoragePath()).length();
  462. }
  463. for (OCUpload ocUpload : uploadsStorageManager.getAllStoredUploads()) {
  464. if (ocUpload.getUploadId() == getOCUploadId()) {
  465. ocUpload.setFileSize(size);
  466. uploadsStorageManager.updateUpload(ocUpload);
  467. break;
  468. }
  469. }
  470. /// perform the upload
  471. if (size > ChunkedUploadRemoteFileOperation.CHUNK_SIZE) {
  472. mUploadOperation = new ChunkedUploadRemoteFileOperation(mContext, encryptedTempFile.getAbsolutePath(),
  473. mFile.getParentRemotePath() + encryptedFileName, mFile.getMimeType(),
  474. mFile.getEtagInConflict(), timeStamp);
  475. } else {
  476. mUploadOperation = new UploadRemoteFileOperation(encryptedTempFile.getAbsolutePath(),
  477. mFile.getParentRemotePath() + encryptedFileName, mFile.getMimeType(),
  478. mFile.getEtagInConflict(), timeStamp);
  479. }
  480. Iterator<OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
  481. while (listener.hasNext()) {
  482. mUploadOperation.addDatatransferProgressListener(listener.next());
  483. }
  484. if (mCancellationRequested.get()) {
  485. throw new OperationCancelledException();
  486. }
  487. result = mUploadOperation.execute(client, true);
  488. /// move local temporal file or original file to its corresponding
  489. // location in the Nextcloud local folder
  490. if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
  491. result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  492. }
  493. if (result.isSuccess()) {
  494. // upload metadata
  495. DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
  496. DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
  497. data.setFilename(mFile.getFileName());
  498. data.setMimetype(mFile.getMimeType());
  499. data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
  500. decryptedFile.setEncrypted(data);
  501. decryptedFile.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
  502. decryptedFile.setAuthenticationTag(encryptedFile.authenticationTag);
  503. metadata.getFiles().put(encryptedFileName, decryptedFile);
  504. EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
  505. privateKey);
  506. String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
  507. // upload metadata
  508. RemoteOperationResult uploadMetadataOperationResult;
  509. if (metadataExists) {
  510. // update metadata
  511. UpdateMetadataOperation storeMetadataOperation = new UpdateMetadataOperation(parentFile.getLocalId(),
  512. serializedFolderMetadata, token);
  513. uploadMetadataOperationResult = storeMetadataOperation.execute(client, true);
  514. } else {
  515. // store metadata
  516. StoreMetadataOperation storeMetadataOperation = new StoreMetadataOperation(parentFile.getLocalId(),
  517. serializedFolderMetadata);
  518. uploadMetadataOperationResult = storeMetadataOperation.execute(client, true);
  519. }
  520. if (!uploadMetadataOperationResult.isSuccess()) {
  521. throw new Exception();
  522. }
  523. }
  524. } catch (FileNotFoundException e) {
  525. Log_OC.d(TAG, mFile.getStoragePath() + " not exists anymore");
  526. result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
  527. } catch (OverlappingFileLockException e) {
  528. Log_OC.d(TAG, "Overlapping file lock exception");
  529. result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
  530. } catch (Exception e) {
  531. result = new RemoteOperationResult(e);
  532. } finally {
  533. mUploadStarted.set(false);
  534. if (fileLock != null) {
  535. try {
  536. fileLock.release();
  537. } catch (IOException e) {
  538. Log_OC.e(TAG, "Failed to unlock file with path " + mFile.getStoragePath());
  539. }
  540. }
  541. if (temporalFile != null && !originalFile.equals(temporalFile)) {
  542. temporalFile.delete();
  543. }
  544. if (result == null) {
  545. result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
  546. }
  547. if (result.isSuccess()) {
  548. Log_OC.i(TAG, "Upload of " + mFile.getStoragePath() + " to " + mFile.getRemotePath() + ": " +
  549. result.getLogMessage());
  550. } else {
  551. if (result.getException() != null) {
  552. if (result.isCancelled()) {
  553. Log_OC.w(TAG, "Upload of " + mFile.getStoragePath() + " to " + mFile.getRemotePath() +
  554. ": " + result.getLogMessage());
  555. } else {
  556. Log_OC.e(TAG, "Upload of " + mFile.getStoragePath() + " to " + mFile.getRemotePath() +
  557. ": " + result.getLogMessage(), result.getException());
  558. }
  559. } else {
  560. Log_OC.e(TAG, "Upload of " + mFile.getStoragePath() + " to " + mFile.getRemotePath() +
  561. ": " + result.getLogMessage());
  562. }
  563. }
  564. }
  565. if (result.isSuccess()) {
  566. handleSuccessfulUpload(temporalFile, expectedFile, originalFile, client);
  567. RemoteOperationResult unlockFolderResult = unlockFolder(parentFile, client, token);
  568. if (!unlockFolderResult.isSuccess()) {
  569. return unlockFolderResult;
  570. }
  571. } else if (result.getCode() == ResultCode.SYNC_CONFLICT) {
  572. getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
  573. }
  574. return result;
  575. }
  576. private RemoteOperationResult unlockFolder(OCFile parentFolder, OwnCloudClient client, String token) {
  577. if (token != null) {
  578. UnlockFileOperation unlockFileOperation = new UnlockFileOperation(parentFolder.getLocalId(), token);
  579. RemoteOperationResult unlockFileOperationResult = unlockFileOperation.execute(client, true);
  580. return unlockFileOperationResult;
  581. } else
  582. return new RemoteOperationResult(new Exception("No token available"));
  583. }
  584. private RemoteOperationResult checkConditions(File originalFile) {
  585. RemoteOperationResult remoteOperationResult = null;
  586. // check that internet is not behind walled garden
  587. if (Device.getNetworkType(mContext).equals(JobRequest.NetworkType.ANY) ||
  588. ConnectivityUtils.isInternetWalled(mContext)) {
  589. remoteOperationResult = new RemoteOperationResult(ResultCode.NO_NETWORK_CONNECTION);
  590. }
  591. // check that connectivity conditions are met and delays the upload otherwise
  592. if (mOnWifiOnly && !Device.getNetworkType(mContext).equals(JobRequest.NetworkType.UNMETERED)) {
  593. Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
  594. remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
  595. }
  596. // check if charging conditions are met and delays the upload otherwise
  597. if (mWhileChargingOnly && (!Device.getBatteryStatus(mContext).isCharging() && Device.getBatteryStatus(mContext)
  598. .getBatteryPercent() < 1)) {
  599. Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath());
  600. remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
  601. }
  602. // check that device is not in power save mode
  603. if (!mIgnoringPowerSaveMode && PowerUtils.isPowerSaveMode(mContext)) {
  604. Log_OC.d(TAG, "Upload delayed because device is in power save mode: " + getRemotePath());
  605. remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_IN_POWER_SAVE_MODE);
  606. }
  607. // check if the file continues existing before schedule the operation
  608. if (!originalFile.exists()) {
  609. Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore");
  610. remoteOperationResult = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
  611. }
  612. return remoteOperationResult;
  613. }
  614. private RemoteOperationResult normalUpload(OwnCloudClient client) {
  615. RemoteOperationResult result = null;
  616. File temporalFile = null;
  617. File originalFile = new File(mOriginalStoragePath);
  618. File expectedFile = null;
  619. FileLock fileLock = null;
  620. long size = 0;
  621. try {
  622. // check conditions
  623. result = checkConditions(originalFile);
  624. if (result != null) {
  625. return result;
  626. }
  627. // check name collision
  628. checkNameCollision(client, null, false);
  629. String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
  630. expectedFile = new File(expectedPath);
  631. result = copyFile(originalFile, expectedPath);
  632. if (result != null) {
  633. return result;
  634. }
  635. // Get the last modification date of the file from the file system
  636. Long timeStampLong = originalFile.lastModified() / 1000;
  637. String timeStamp = timeStampLong.toString();
  638. FileChannel channel = null;
  639. try {
  640. channel = new RandomAccessFile(mFile.getStoragePath(), "rw").getChannel();
  641. fileLock = channel.tryLock();
  642. } catch (FileNotFoundException e) {
  643. // this basically means that the file is on SD card
  644. // try to copy file to temporary dir if it doesn't exist
  645. String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
  646. mFile.setStoragePath(temporalPath);
  647. temporalFile = new File(temporalPath);
  648. Files.deleteIfExists(Paths.get(temporalPath));
  649. result = copy(originalFile, temporalFile);
  650. if (result == null) {
  651. if (temporalFile.length() == originalFile.length()) {
  652. channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
  653. fileLock = channel.tryLock();
  654. } else {
  655. result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
  656. }
  657. }
  658. }
  659. try {
  660. size = channel.size();
  661. } catch (IOException e1) {
  662. size = new File(mFile.getStoragePath()).length();
  663. }
  664. for (OCUpload ocUpload : uploadsStorageManager.getAllStoredUploads()) {
  665. if (ocUpload.getUploadId() == getOCUploadId()) {
  666. ocUpload.setFileSize(size);
  667. uploadsStorageManager.updateUpload(ocUpload);
  668. break;
  669. }
  670. }
  671. // perform the upload
  672. if (size > ChunkedUploadRemoteFileOperation.CHUNK_SIZE) {
  673. mUploadOperation = new ChunkedUploadRemoteFileOperation(mContext, mFile.getStoragePath(),
  674. mFile.getRemotePath(), mFile.getMimeType(), mFile.getEtagInConflict(), timeStamp);
  675. } else {
  676. mUploadOperation = new UploadRemoteFileOperation(mFile.getStoragePath(),
  677. mFile.getRemotePath(), mFile.getMimeType(), mFile.getEtagInConflict(), timeStamp);
  678. }
  679. Iterator<OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
  680. while (listener.hasNext()) {
  681. mUploadOperation.addDatatransferProgressListener(listener.next());
  682. }
  683. if (mCancellationRequested.get()) {
  684. throw new OperationCancelledException();
  685. }
  686. if (result == null || result.isSuccess() && mUploadOperation != null) {
  687. result = mUploadOperation.execute(client, mFile.isEncrypted());
  688. /// move local temporal file or original file to its corresponding
  689. // location in the Nextcloud local folder
  690. if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
  691. result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  692. }
  693. }
  694. } catch (FileNotFoundException e) {
  695. Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore");
  696. result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
  697. } catch (OverlappingFileLockException e) {
  698. Log_OC.d(TAG, "Overlapping file lock exception");
  699. result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
  700. } catch (Exception e) {
  701. result = new RemoteOperationResult(e);
  702. } finally {
  703. mUploadStarted.set(false);
  704. if (fileLock != null) {
  705. try {
  706. fileLock.release();
  707. } catch (IOException e) {
  708. Log_OC.e(TAG, "Failed to unlock file with path " + mOriginalStoragePath);
  709. }
  710. }
  711. if (temporalFile != null && !originalFile.equals(temporalFile)) {
  712. temporalFile.delete();
  713. }
  714. if (result == null) {
  715. result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
  716. }
  717. if (result.isSuccess()) {
  718. Log_OC.i(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " +
  719. result.getLogMessage());
  720. } else {
  721. if (result.getException() != null) {
  722. if (result.isCancelled()) {
  723. Log_OC.w(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
  724. ": " + result.getLogMessage());
  725. } else {
  726. Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
  727. ": " + result.getLogMessage(), result.getException());
  728. }
  729. } else {
  730. Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
  731. ": " + result.getLogMessage());
  732. }
  733. }
  734. }
  735. if (result.isSuccess()) {
  736. handleSuccessfulUpload(temporalFile, expectedFile, originalFile, client);
  737. } else if (result.getCode() == ResultCode.SYNC_CONFLICT) {
  738. getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
  739. }
  740. return result;
  741. }
  742. private RemoteOperationResult copyFile(File originalFile, String expectedPath) throws OperationCancelledException,
  743. IOException {
  744. RemoteOperationResult result = null;
  745. if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY && !mOriginalStoragePath.equals(expectedPath)) {
  746. String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
  747. mFile.setStoragePath(temporalPath);
  748. File temporalFile = new File(temporalPath);
  749. result = copy(originalFile, temporalFile);
  750. }
  751. if (mCancellationRequested.get()) {
  752. throw new OperationCancelledException();
  753. }
  754. return result;
  755. }
  756. private void checkNameCollision(OwnCloudClient client, DecryptedFolderMetadata metadata, boolean encrypted)
  757. throws OperationCancelledException {
  758. /// automatic rename of file to upload in case of name collision in server
  759. Log_OC.d(TAG, "Checking name collision in server");
  760. if (!mForceOverwrite) {
  761. String remotePath = getAvailableRemotePath(client, mRemotePath, metadata, encrypted);
  762. mWasRenamed = !remotePath.equals(mRemotePath);
  763. if (mWasRenamed) {
  764. createNewOCFile(remotePath);
  765. Log_OC.d(TAG, "File renamed as " + remotePath);
  766. }
  767. mRemotePath = remotePath;
  768. mRenameUploadListener.onRenameUpload();
  769. }
  770. if (mCancellationRequested.get()) {
  771. throw new OperationCancelledException();
  772. }
  773. }
  774. private void handleSuccessfulUpload(File temporalFile, File expectedFile, File originalFile,
  775. OwnCloudClient client) {
  776. switch (mLocalBehaviour) {
  777. case FileUploader.LOCAL_BEHAVIOUR_FORGET:
  778. default:
  779. String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
  780. if (mOriginalStoragePath.equals(temporalPath)) {
  781. // delete local file is was pre-copied in temporary folder (see .ui.helpers.UriUploader)
  782. temporalFile = new File(temporalPath);
  783. temporalFile.delete();
  784. }
  785. mFile.setStoragePath("");
  786. saveUploadedFile(client);
  787. break;
  788. case FileUploader.LOCAL_BEHAVIOUR_DELETE:
  789. Log_OC.d(TAG, "Delete source file");
  790. originalFile.delete();
  791. getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
  792. saveUploadedFile(client);
  793. break;
  794. case FileUploader.LOCAL_BEHAVIOUR_COPY:
  795. if (temporalFile != null) {
  796. try {
  797. move(temporalFile, expectedFile);
  798. } catch (IOException e) {
  799. Log_OC.e(TAG, e.getMessage());
  800. }
  801. }
  802. mFile.setStoragePath(expectedFile.getAbsolutePath());
  803. saveUploadedFile(client);
  804. FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
  805. break;
  806. case FileUploader.LOCAL_BEHAVIOUR_MOVE:
  807. String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
  808. File newFile = new File(expectedPath);
  809. try {
  810. move(originalFile, newFile);
  811. } catch (IOException e) {
  812. e.printStackTrace();
  813. }
  814. getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
  815. mFile.setStoragePath(newFile.getAbsolutePath());
  816. saveUploadedFile(client);
  817. FileDataStorageManager.triggerMediaScan(newFile.getAbsolutePath());
  818. break;
  819. }
  820. }
  821. /**
  822. * Checks the existence of the folder where the current file will be uploaded both
  823. * in the remote server and in the local database.
  824. * <p/>
  825. * If the upload is set to enforce the creation of the folder, the method tries to
  826. * create it both remote and locally.
  827. *
  828. * @param pathToGrant Full remote path whose existence will be granted.
  829. * @return An {@link OCFile} instance corresponding to the folder where the file
  830. * will be uploaded.
  831. */
  832. private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
  833. RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, mContext, false);
  834. RemoteOperationResult result = operation.execute(client, true);
  835. if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
  836. SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true);
  837. result = syncOp.execute(client, getStorageManager());
  838. }
  839. if (result.isSuccess()) {
  840. OCFile parentDir = getStorageManager().getFileByPath(pathToGrant);
  841. if (parentDir == null) {
  842. parentDir = createLocalFolder(pathToGrant);
  843. }
  844. if (parentDir != null) {
  845. result = new RemoteOperationResult(ResultCode.OK);
  846. } else {
  847. result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
  848. }
  849. }
  850. return result;
  851. }
  852. private OCFile createLocalFolder(String remotePath) {
  853. String parentPath = new File(remotePath).getParent();
  854. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
  855. parentPath : parentPath + OCFile.PATH_SEPARATOR;
  856. OCFile parent = getStorageManager().getFileByPath(parentPath);
  857. if (parent == null) {
  858. parent = createLocalFolder(parentPath);
  859. }
  860. if (parent != null) {
  861. OCFile createdFolder = new OCFile(remotePath);
  862. createdFolder.setMimetype(MimeType.DIRECTORY);
  863. createdFolder.setParentId(parent.getFileId());
  864. getStorageManager().saveFile(createdFolder);
  865. return createdFolder;
  866. }
  867. return null;
  868. }
  869. /**
  870. * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false.
  871. * New file is stored as mFile, original as mOldFile.
  872. *
  873. * @param newRemotePath new remote path
  874. */
  875. private void createNewOCFile(String newRemotePath) {
  876. // a new OCFile instance must be created for a new remote path
  877. OCFile newFile = new OCFile(newRemotePath);
  878. newFile.setCreationTimestamp(mFile.getCreationTimestamp());
  879. newFile.setFileLength(mFile.getFileLength());
  880. newFile.setMimetype(mFile.getMimeType());
  881. newFile.setModificationTimestamp(mFile.getModificationTimestamp());
  882. newFile.setModificationTimestampAtLastSyncForData(
  883. mFile.getModificationTimestampAtLastSyncForData()
  884. );
  885. newFile.setEtag(mFile.getEtag());
  886. newFile.setAvailableOffline(mFile.isAvailableOffline());
  887. newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
  888. newFile.setLastSyncDateForData(mFile.getLastSyncDateForData());
  889. newFile.setStoragePath(mFile.getStoragePath());
  890. newFile.setParentId(mFile.getParentId());
  891. mOldFile = mFile;
  892. mFile = newFile;
  893. }
  894. /**
  895. * Checks if remotePath does not exist in the server and returns it, or adds
  896. * a suffix to it in order to avoid the server file is overwritten.
  897. *
  898. * @param client OwnCloud client
  899. * @param remotePath remote path of the file
  900. * @param metadata metadata of encrypted folder
  901. * @return new remote path
  902. */
  903. private String getAvailableRemotePath(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
  904. boolean encrypted) {
  905. boolean check = existsFile(client, remotePath, metadata, encrypted);
  906. if (!check) {
  907. return remotePath;
  908. }
  909. int pos = remotePath.lastIndexOf('.');
  910. String suffix;
  911. String extension = "";
  912. String remotePathWithoutExtension = "";
  913. if (pos >= 0) {
  914. extension = remotePath.substring(pos + 1);
  915. remotePathWithoutExtension = remotePath.substring(0, pos);
  916. }
  917. int count = 2;
  918. do {
  919. suffix = " (" + count + ")";
  920. if (pos >= 0) {
  921. check = existsFile(client, remotePathWithoutExtension + suffix + "." + extension, metadata, encrypted);
  922. } else {
  923. check = existsFile(client, remotePath + suffix, metadata, encrypted);
  924. }
  925. count++;
  926. } while (check);
  927. if (pos >= 0) {
  928. return remotePathWithoutExtension + suffix + "." + extension;
  929. } else {
  930. return remotePath + suffix;
  931. }
  932. }
  933. private boolean existsFile(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
  934. boolean encrypted) {
  935. if (encrypted) {
  936. String fileName = new File(remotePath).getName();
  937. for (DecryptedFolderMetadata.DecryptedFile file : metadata.getFiles().values()) {
  938. if (file.getEncrypted().getFilename().equalsIgnoreCase(fileName)) {
  939. return true;
  940. }
  941. }
  942. return false;
  943. } else {
  944. ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, mContext,
  945. false);
  946. RemoteOperationResult result = existsOperation.execute(client);
  947. return result.isSuccess();
  948. }
  949. }
  950. /**
  951. * Allows to cancel the actual upload operation. If actual upload operating
  952. * is in progress it is cancelled, if upload preparation is being performed
  953. * upload will not take place.
  954. */
  955. public void cancel() {
  956. if (mUploadOperation == null) {
  957. if (mUploadStarted.get()) {
  958. Log_OC.d(TAG, "Cancelling upload during upload preparations.");
  959. mCancellationRequested.set(true);
  960. } else {
  961. Log_OC.e(TAG, "No upload in progress. This should not happen.");
  962. }
  963. } else {
  964. Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
  965. mUploadOperation.cancel();
  966. }
  967. }
  968. /**
  969. * As soon as this method return true, upload can be cancel via cancel().
  970. */
  971. public boolean isUploadInProgress() {
  972. return mUploadStarted.get();
  973. }
  974. /**
  975. * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
  976. * TODO use Exceptions instead
  977. *
  978. * @param sourceFile Source file to copy.
  979. * @param targetFile Target location to copy the file.
  980. * @return {@link RemoteOperationResult}
  981. * @throws IOException exception if file cannot be accessed
  982. */
  983. private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException {
  984. Log_OC.d(TAG, "Copying local file");
  985. RemoteOperationResult result = null;
  986. if (FileStorageUtils.getUsableSpace() < sourceFile.length()) {
  987. result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
  988. return result; // error condition when the file should be copied
  989. } else {
  990. Log_OC.d(TAG, "Creating temporal folder");
  991. File temporalParent = targetFile.getParentFile();
  992. temporalParent.mkdirs();
  993. if (!temporalParent.isDirectory()) {
  994. throw new IOException(
  995. "Unexpected error: parent directory could not be created");
  996. }
  997. Log_OC.d(TAG, "Creating temporal file");
  998. targetFile.createNewFile();
  999. if (!targetFile.isFile()) {
  1000. throw new IOException(
  1001. "Unexpected error: target file could not be created");
  1002. }
  1003. Log_OC.d(TAG, "Copying file contents");
  1004. InputStream in = null;
  1005. OutputStream out = null;
  1006. try {
  1007. if (!mOriginalStoragePath.equals(targetFile.getAbsolutePath())) {
  1008. // In case document provider schema as 'content://'
  1009. if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
  1010. Uri uri = Uri.parse(mOriginalStoragePath);
  1011. in = mContext.getContentResolver().openInputStream(uri);
  1012. } else {
  1013. in = new FileInputStream(sourceFile);
  1014. }
  1015. out = new FileOutputStream(targetFile);
  1016. int nRead;
  1017. byte[] buf = new byte[4096];
  1018. while (!mCancellationRequested.get() &&
  1019. (nRead = in.read(buf)) > -1) {
  1020. out.write(buf, 0, nRead);
  1021. }
  1022. out.flush();
  1023. } // else: weird but possible situation, nothing to copy
  1024. if (mCancellationRequested.get()) {
  1025. result = new RemoteOperationResult(new OperationCancelledException());
  1026. return result;
  1027. }
  1028. } catch (Exception e) {
  1029. result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
  1030. return result;
  1031. } finally {
  1032. try {
  1033. if (in != null) {
  1034. in.close();
  1035. }
  1036. } catch (Exception e) {
  1037. Log_OC.d(TAG, "Weird exception while closing input stream for " +
  1038. mOriginalStoragePath + " (ignoring)", e);
  1039. }
  1040. try {
  1041. if (out != null) {
  1042. out.close();
  1043. }
  1044. } catch (Exception e) {
  1045. Log_OC.d(TAG, "Weird exception while closing output stream for " +
  1046. targetFile.getAbsolutePath() + " (ignoring)", e);
  1047. }
  1048. }
  1049. }
  1050. return result;
  1051. }
  1052. /**
  1053. * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
  1054. * TODO use Exceptions instead
  1055. * <p>
  1056. * TODO refactor both this and 'copy' in a single method
  1057. *
  1058. * @param sourceFile Source file to move.
  1059. * @param targetFile Target location to move the file.
  1060. * @return {@link RemoteOperationResult} result from remote operation
  1061. * @throws IOException exception if file cannot be read/wrote
  1062. */
  1063. private void move(File sourceFile, File targetFile) throws IOException {
  1064. if (!targetFile.equals(sourceFile)) {
  1065. File expectedFolder = targetFile.getParentFile();
  1066. expectedFolder.mkdirs();
  1067. if (expectedFolder.isDirectory()) {
  1068. if (!sourceFile.renameTo(targetFile)) {
  1069. // try to copy and then delete
  1070. targetFile.createNewFile();
  1071. FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
  1072. FileChannel outChannel = new FileOutputStream(targetFile).getChannel();
  1073. try {
  1074. inChannel.transferTo(0, inChannel.size(), outChannel);
  1075. sourceFile.delete();
  1076. } catch (Exception e) {
  1077. mFile.setStoragePath(""); // forget the local file
  1078. // by now, treat this as a success; the file was uploaded
  1079. // the best option could be show a warning message
  1080. } finally {
  1081. if (inChannel != null) {
  1082. inChannel.close();
  1083. }
  1084. if (outChannel != null) {
  1085. outChannel.close();
  1086. }
  1087. }
  1088. }
  1089. } else {
  1090. mFile.setStoragePath("");
  1091. }
  1092. }
  1093. }
  1094. /**
  1095. * Saves a OC File after a successful upload.
  1096. * <p>
  1097. * A PROPFIND is necessary to keep the props in the local database
  1098. * synchronized with the server, specially the modification time and Etag
  1099. * (where available)
  1100. */
  1101. private void saveUploadedFile(OwnCloudClient client) {
  1102. OCFile file = mFile;
  1103. if (file.fileExists()) {
  1104. file = getStorageManager().getFileById(file.getFileId());
  1105. }
  1106. long syncDate = System.currentTimeMillis();
  1107. file.setLastSyncDateForData(syncDate);
  1108. // new PROPFIND to keep data consistent with server
  1109. // in theory, should return the same we already have
  1110. // TODO from the appropriate OC server version, get data from last PUT response headers, instead
  1111. // TODO of a new PROPFIND; the latter may fail, specially for chunked uploads
  1112. String path;
  1113. if (encryptedAncestor) {
  1114. path = file.getParentRemotePath() + mFile.getEncryptedFileName();
  1115. } else {
  1116. path = getRemotePath();
  1117. }
  1118. ReadRemoteFileOperation operation = new ReadRemoteFileOperation(path);
  1119. RemoteOperationResult result = operation.execute(client, mFile.isEncrypted());
  1120. if (result.isSuccess()) {
  1121. updateOCFile(file, (RemoteFile) result.getData().get(0));
  1122. file.setLastSyncDateForProperties(syncDate);
  1123. } else {
  1124. Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt...");
  1125. }
  1126. if (mWasRenamed) {
  1127. OCFile oldFile = getStorageManager().getFileByPath(mOldFile.getRemotePath());
  1128. if (oldFile != null) {
  1129. oldFile.setStoragePath(null);
  1130. getStorageManager().saveFile(oldFile);
  1131. getStorageManager().saveConflict(oldFile, null);
  1132. }
  1133. // else: it was just an automatic renaming due to a name
  1134. // coincidence; nothing else is needed, the storagePath is right
  1135. // in the instance returned by mCurrentUpload.getFile()
  1136. }
  1137. file.setNeedsUpdateThumbnail(true);
  1138. getStorageManager().saveFile(file);
  1139. getStorageManager().saveConflict(file, null);
  1140. FileDataStorageManager.triggerMediaScan(file.getStoragePath());
  1141. // generate new Thumbnail
  1142. final ThumbnailsCacheManager.ThumbnailGenerationTask task =
  1143. new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), mAccount);
  1144. task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, file.getRemoteId()));
  1145. }
  1146. private void updateOCFile(OCFile file, RemoteFile remoteFile) {
  1147. file.setCreationTimestamp(remoteFile.getCreationTimestamp());
  1148. file.setFileLength(remoteFile.getLength());
  1149. file.setMimetype(remoteFile.getMimeType());
  1150. file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
  1151. file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
  1152. file.setEtag(remoteFile.getEtag());
  1153. file.setRemoteId(remoteFile.getRemoteId());
  1154. }
  1155. public interface OnRenameListener {
  1156. void onRenameUpload();
  1157. }
  1158. }