UploadFileOperation.java 56 KB

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