UploadFileOperation.java 55 KB

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