UploadFileOperation.java 56 KB

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