UploadFileOperation.java 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621
  1. /*
  2. * Nextcloud - Android Client
  3. *
  4. * SPDX-FileCopyrightText: 2020-2023 Tobias Kaminsky <tobias@kaminsky.me>
  5. * SPDX-FileCopyrightText: 2021 Chris Narkiewicz <hello@ezaquarii.com>
  6. * SPDX-FileCopyrightText: 2017-2018 Andy Scherzinger <info@andy-scherzinger.de>
  7. * SPDX-FileCopyrightText: 2016 ownCloud Inc.
  8. * SPDX-FileCopyrightText: 2012 David A. Velasco <dvelasco@solidgear.es>
  9. * SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
  10. */
  11. package com.owncloud.android.operations;
  12. import android.annotation.SuppressLint;
  13. import android.content.Context;
  14. import android.content.Intent;
  15. import android.net.Uri;
  16. import android.text.TextUtils;
  17. import com.nextcloud.client.account.User;
  18. import com.nextcloud.client.device.BatteryStatus;
  19. import com.nextcloud.client.device.PowerManagementService;
  20. import com.nextcloud.client.jobs.upload.FileUploadHelper;
  21. import com.nextcloud.client.jobs.upload.FileUploadWorker;
  22. import com.nextcloud.client.network.Connectivity;
  23. import com.nextcloud.client.network.ConnectivityService;
  24. import com.nextcloud.utils.autoRename.AutoRename;
  25. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  26. import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
  27. import com.owncloud.android.datamodel.FileDataStorageManager;
  28. import com.owncloud.android.datamodel.OCFile;
  29. import com.owncloud.android.datamodel.ThumbnailsCacheManager;
  30. import com.owncloud.android.datamodel.UploadsStorageManager;
  31. import com.owncloud.android.datamodel.e2e.v1.decrypted.Data;
  32. import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile;
  33. import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
  34. import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedMetadata;
  35. import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFile;
  36. import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1;
  37. import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
  38. import com.owncloud.android.db.OCUpload;
  39. import com.owncloud.android.files.services.NameCollisionPolicy;
  40. import com.owncloud.android.lib.common.OwnCloudClient;
  41. import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
  42. import com.owncloud.android.lib.common.network.ProgressiveDataTransfer;
  43. import com.owncloud.android.lib.common.operations.OperationCancelledException;
  44. import com.owncloud.android.lib.common.operations.RemoteOperation;
  45. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  46. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  47. import com.owncloud.android.lib.common.utils.Log_OC;
  48. import com.owncloud.android.lib.resources.files.ChunkedFileUploadRemoteOperation;
  49. import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
  50. import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
  51. import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
  52. import com.owncloud.android.lib.resources.files.model.RemoteFile;
  53. import com.owncloud.android.lib.resources.status.E2EVersion;
  54. import com.owncloud.android.lib.resources.status.OCCapability;
  55. import com.owncloud.android.operations.common.SyncOperation;
  56. import com.owncloud.android.operations.e2e.E2EClientData;
  57. import com.owncloud.android.operations.e2e.E2EData;
  58. import com.owncloud.android.operations.e2e.E2EFiles;
  59. import com.owncloud.android.utils.EncryptionUtils;
  60. import com.owncloud.android.utils.EncryptionUtilsV2;
  61. import com.owncloud.android.utils.FileStorageUtils;
  62. import com.owncloud.android.utils.FileUtil;
  63. import com.owncloud.android.utils.MimeType;
  64. import com.owncloud.android.utils.MimeTypeUtil;
  65. import com.owncloud.android.utils.UriUtils;
  66. import com.owncloud.android.utils.theme.CapabilityUtils;
  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.security.InvalidAlgorithmParameterException;
  83. import java.security.InvalidKeyException;
  84. import java.security.NoSuchAlgorithmException;
  85. import java.security.cert.CertificateException;
  86. import java.security.spec.InvalidParameterSpecException;
  87. import java.util.ArrayList;
  88. import java.util.HashMap;
  89. import java.util.HashSet;
  90. import java.util.List;
  91. import java.util.Map;
  92. import java.util.Set;
  93. import java.util.concurrent.atomic.AtomicBoolean;
  94. import javax.crypto.BadPaddingException;
  95. import javax.crypto.Cipher;
  96. import javax.crypto.IllegalBlockSizeException;
  97. import javax.crypto.NoSuchPaddingException;
  98. import androidx.annotation.CheckResult;
  99. import androidx.annotation.Nullable;
  100. import androidx.localbroadcastmanager.content.LocalBroadcastManager;
  101. import kotlin.Triple;
  102. import static com.owncloud.android.ui.activity.FileDisplayActivity.REFRESH_FOLDER_EVENT_RECEIVER;
  103. /**
  104. * Operation performing the update in the ownCloud server of a file that was modified locally.
  105. */
  106. public class UploadFileOperation extends SyncOperation {
  107. private static final String TAG = UploadFileOperation.class.getSimpleName();
  108. public static final int CREATED_BY_USER = 0;
  109. public static final int CREATED_AS_INSTANT_PICTURE = 1;
  110. public static final int CREATED_AS_INSTANT_VIDEO = 2;
  111. /**
  112. * OCFile which is to be uploaded.
  113. */
  114. private OCFile mFile;
  115. /**
  116. * Original OCFile which is to be uploaded in case file had to be renamed (if nameCollisionPolicy==RENAME and remote
  117. * file already exists).
  118. */
  119. private OCFile mOldFile;
  120. private String mRemotePath;
  121. private String mFolderUnlockToken;
  122. private boolean mRemoteFolderToBeCreated;
  123. private NameCollisionPolicy mNameCollisionPolicy;
  124. private int mLocalBehaviour;
  125. private int mCreatedBy;
  126. private boolean mOnWifiOnly;
  127. private boolean mWhileChargingOnly;
  128. private boolean mIgnoringPowerSaveMode;
  129. private final boolean mDisableRetries;
  130. private boolean mWasRenamed;
  131. private long mOCUploadId;
  132. /**
  133. * Local path to file which is to be uploaded (before any possible renaming or moving).
  134. */
  135. private String mOriginalStoragePath;
  136. private final Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
  137. private OnRenameListener mRenameUploadListener;
  138. private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
  139. private final AtomicBoolean mUploadStarted = new AtomicBoolean(false);
  140. private Context mContext;
  141. private UploadFileRemoteOperation mUploadOperation;
  142. private RequestEntity mEntity;
  143. private final User user;
  144. private final OCUpload mUpload;
  145. private final UploadsStorageManager uploadsStorageManager;
  146. private final ConnectivityService connectivityService;
  147. private final PowerManagementService powerManagementService;
  148. private boolean encryptedAncestor;
  149. private OCFile duplicatedEncryptedFile;
  150. public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) {
  151. OCFile newFile = new OCFile(remotePath);
  152. newFile.setStoragePath(localPath);
  153. newFile.setLastSyncDateForProperties(0);
  154. newFile.setLastSyncDateForData(0);
  155. // size
  156. if (!TextUtils.isEmpty(localPath)) {
  157. File localFile = new File(localPath);
  158. newFile.setFileLength(localFile.length());
  159. newFile.setLastSyncDateForData(localFile.lastModified());
  160. } // don't worry about not assigning size, the problems with localPath
  161. // are checked when the UploadFileOperation instance is created
  162. // MIME type
  163. if (TextUtils.isEmpty(mimeType)) {
  164. newFile.setMimeType(MimeTypeUtil.getBestMimeTypeByFilename(localPath));
  165. } else {
  166. newFile.setMimeType(mimeType);
  167. }
  168. return newFile;
  169. }
  170. public UploadFileOperation(UploadsStorageManager uploadsStorageManager,
  171. ConnectivityService connectivityService,
  172. PowerManagementService powerManagementService,
  173. User user,
  174. OCFile file,
  175. OCUpload upload,
  176. NameCollisionPolicy nameCollisionPolicy,
  177. int localBehaviour,
  178. Context context,
  179. boolean onWifiOnly,
  180. boolean whileChargingOnly,
  181. FileDataStorageManager storageManager) {
  182. this(uploadsStorageManager,
  183. connectivityService,
  184. powerManagementService,
  185. user,
  186. file,
  187. upload,
  188. nameCollisionPolicy,
  189. localBehaviour,
  190. context,
  191. onWifiOnly,
  192. whileChargingOnly,
  193. true,
  194. storageManager);
  195. }
  196. public UploadFileOperation(UploadsStorageManager uploadsStorageManager,
  197. ConnectivityService connectivityService,
  198. PowerManagementService powerManagementService,
  199. User user,
  200. OCFile file,
  201. OCUpload upload,
  202. NameCollisionPolicy nameCollisionPolicy,
  203. int localBehaviour,
  204. Context context,
  205. boolean onWifiOnly,
  206. boolean whileChargingOnly,
  207. boolean disableRetries,
  208. FileDataStorageManager storageManager) {
  209. super(storageManager);
  210. if (upload == null) {
  211. throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
  212. }
  213. if (TextUtils.isEmpty(upload.getLocalPath())) {
  214. throw new IllegalArgumentException(
  215. "Illegal file in UploadFileOperation; storage path invalid: "
  216. + upload.getLocalPath());
  217. }
  218. this.uploadsStorageManager = uploadsStorageManager;
  219. this.connectivityService = connectivityService;
  220. this.powerManagementService = powerManagementService;
  221. this.user = user;
  222. mUpload = upload;
  223. if (file == null) {
  224. mFile = obtainNewOCFileToUpload(
  225. upload.getRemotePath(),
  226. upload.getLocalPath(),
  227. upload.getMimeType()
  228. );
  229. } else {
  230. mFile = file;
  231. }
  232. mOnWifiOnly = onWifiOnly;
  233. mWhileChargingOnly = whileChargingOnly;
  234. mRemotePath = upload.getRemotePath();
  235. mNameCollisionPolicy = nameCollisionPolicy;
  236. mLocalBehaviour = localBehaviour;
  237. mOriginalStoragePath = mFile.getStoragePath();
  238. mContext = context;
  239. mOCUploadId = upload.getUploadId();
  240. mCreatedBy = upload.getCreatedBy();
  241. mRemoteFolderToBeCreated = upload.isCreateRemoteFolder();
  242. // Ignore power save mode only if user explicitly created this upload
  243. mIgnoringPowerSaveMode = mCreatedBy == CREATED_BY_USER;
  244. mFolderUnlockToken = upload.getFolderUnlockToken();
  245. mDisableRetries = disableRetries;
  246. }
  247. public boolean isWifiRequired() {
  248. return mOnWifiOnly;
  249. }
  250. public boolean isChargingRequired() {
  251. return mWhileChargingOnly;
  252. }
  253. public boolean isIgnoringPowerSaveMode() {
  254. return mIgnoringPowerSaveMode;
  255. }
  256. public User getUser() {
  257. return user;
  258. }
  259. public String getFileName() {
  260. return (mFile != null) ? mFile.getFileName() : null;
  261. }
  262. public OCFile getFile() {
  263. return mFile;
  264. }
  265. /**
  266. * If remote file was renamed, return original OCFile which was uploaded. Is null is file was not renamed.
  267. */
  268. @Nullable
  269. public OCFile getOldFile() {
  270. return mOldFile;
  271. }
  272. public String getOriginalStoragePath() {
  273. return mOriginalStoragePath;
  274. }
  275. public String getStoragePath() {
  276. return mFile.getStoragePath();
  277. }
  278. public String getRemotePath() {
  279. return mFile.getRemotePath();
  280. }
  281. public String getDecryptedRemotePath() {
  282. return mFile.getDecryptedRemotePath();
  283. }
  284. public String getMimeType() {
  285. return mFile.getMimeType();
  286. }
  287. public int getLocalBehaviour() {
  288. return mLocalBehaviour;
  289. }
  290. public UploadFileOperation setRemoteFolderToBeCreated() {
  291. mRemoteFolderToBeCreated = true;
  292. return this;
  293. }
  294. public boolean wasRenamed() {
  295. return mWasRenamed;
  296. }
  297. public void setCreatedBy(int createdBy) {
  298. mCreatedBy = createdBy;
  299. if (createdBy < CREATED_BY_USER || CREATED_AS_INSTANT_VIDEO < createdBy) {
  300. mCreatedBy = CREATED_BY_USER;
  301. }
  302. }
  303. public int getCreatedBy() {
  304. return mCreatedBy;
  305. }
  306. public boolean isInstantPicture() {
  307. return mCreatedBy == CREATED_AS_INSTANT_PICTURE;
  308. }
  309. public boolean isInstantVideo() {
  310. return mCreatedBy == CREATED_AS_INSTANT_VIDEO;
  311. }
  312. public void setOCUploadId(long id) {
  313. mOCUploadId = id;
  314. }
  315. public long getOCUploadId() {
  316. return mOCUploadId;
  317. }
  318. public Set<OnDatatransferProgressListener> getDataTransferListeners() {
  319. return mDataTransferListeners;
  320. }
  321. public void addDataTransferProgressListener(OnDatatransferProgressListener listener) {
  322. synchronized (mDataTransferListeners) {
  323. mDataTransferListeners.add(listener);
  324. }
  325. if (mEntity != null) {
  326. ((ProgressiveDataTransfer) mEntity).addDataTransferProgressListener(listener);
  327. }
  328. if (mUploadOperation != null) {
  329. mUploadOperation.addDataTransferProgressListener(listener);
  330. }
  331. }
  332. public void removeDataTransferProgressListener(OnDatatransferProgressListener listener) {
  333. synchronized (mDataTransferListeners) {
  334. mDataTransferListeners.remove(listener);
  335. }
  336. if (mEntity != null) {
  337. ((ProgressiveDataTransfer) mEntity).removeDataTransferProgressListener(listener);
  338. }
  339. if (mUploadOperation != null) {
  340. mUploadOperation.removeDataTransferProgressListener(listener);
  341. }
  342. }
  343. public UploadFileOperation addRenameUploadListener(OnRenameListener listener) {
  344. mRenameUploadListener = listener;
  345. return this;
  346. }
  347. public Context getContext() {
  348. return mContext;
  349. }
  350. @Override
  351. @SuppressWarnings("PMD.AvoidDuplicateLiterals")
  352. protected RemoteOperationResult run(OwnCloudClient client) {
  353. mCancellationRequested.set(false);
  354. mUploadStarted.set(true);
  355. updateSize(0);
  356. String remoteParentPath = new File(getRemotePath()).getParent();
  357. remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
  358. remoteParentPath = AutoRename.INSTANCE.rename(remoteParentPath, getCapabilities(), true);
  359. OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
  360. // in case of a fresh upload with subfolder, where parent does not exist yet
  361. if (parent == null && (mFolderUnlockToken == null || mFolderUnlockToken.isEmpty())) {
  362. // try to create folder
  363. RemoteOperationResult result = grantFolderExistence(remoteParentPath, client);
  364. if (!result.isSuccess()) {
  365. return result;
  366. }
  367. parent = getStorageManager().getFileByPath(remoteParentPath);
  368. if (parent == null) {
  369. return new RemoteOperationResult(false, "Parent folder not found", HttpStatus.SC_NOT_FOUND);
  370. }
  371. }
  372. // parent file is not null anymore:
  373. // - it was created on fresh upload or
  374. // - resume of encrypted upload, then parent file exists already as unlock is only for direct parent
  375. mFile.setParentId(parent.getFileId());
  376. // check if any parent is encrypted
  377. encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
  378. mFile.setEncrypted(encryptedAncestor);
  379. if (encryptedAncestor) {
  380. Log_OC.d(TAG, "encrypted upload");
  381. return encryptedUpload(client, parent);
  382. } else {
  383. Log_OC.d(TAG, "normal upload");
  384. return normalUpload(client);
  385. }
  386. }
  387. // region E2E Upload
  388. @SuppressLint("AndroidLintUseSparseArrays") // gson cannot handle sparse arrays easily, therefore use hashmap
  389. private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile parentFile) {
  390. RemoteOperationResult result = null;
  391. E2EFiles e2eFiles = new E2EFiles(parentFile, null, new File(mOriginalStoragePath), null, null);
  392. FileLock fileLock = null;
  393. long size;
  394. boolean metadataExists = false;
  395. String token = null;
  396. Object object = null;
  397. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(getContext());
  398. String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
  399. try {
  400. result = checkConditions(e2eFiles.getOriginalFile());
  401. if (result != null) {
  402. return result;
  403. }
  404. long counter = getE2ECounter(parentFile);
  405. token = getFolderUnlockTokenOrLockFolder(client, parentFile, counter);
  406. // Update metadata
  407. EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
  408. object = EncryptionUtils.downloadFolderMetadata(parentFile, client, mContext, user);
  409. if (object instanceof DecryptedFolderMetadataFileV1 decrypted && decrypted.getMetadata() != null) {
  410. metadataExists = true;
  411. }
  412. if (isEndToEndVersionAtLeastV2()) {
  413. if (object == null) {
  414. return new RemoteOperationResult(new IllegalStateException("Metadata does not exist"));
  415. }
  416. } else {
  417. object = getDecryptedFolderMetadataV1(publicKey, object);
  418. }
  419. E2EClientData clientData = new E2EClientData(client, token, publicKey);
  420. List<String> fileNames = getCollidedFileNames(object);
  421. RemoteOperationResult collisionResult = checkNameCollision(parentFile, client, fileNames, parentFile.isEncrypted());
  422. if (collisionResult != null) {
  423. result = collisionResult;
  424. return collisionResult;
  425. }
  426. mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + e2eFiles.getOriginalFile().getName());
  427. String expectedPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mFile);
  428. e2eFiles.setExpectedFile(new File(expectedPath));
  429. result = copyFile(e2eFiles.getOriginalFile(), expectedPath);
  430. if (!result.isSuccess()) {
  431. return result;
  432. }
  433. long lastModifiedTimestamp = e2eFiles.getOriginalFile().lastModified() / 1000;
  434. Long creationTimestamp = FileUtil.getCreationTimestamp(e2eFiles.getOriginalFile());
  435. if (creationTimestamp == null) {
  436. throw new NullPointerException("creationTimestamp cannot be null");
  437. }
  438. E2EData e2eData = getE2EData(object);
  439. e2eFiles.setEncryptedTempFile(e2eData.getEncryptedFile().getEncryptedFile());
  440. if (e2eFiles.getEncryptedTempFile() == null) {
  441. throw new NullPointerException("encryptedTempFile cannot be null");
  442. }
  443. Triple<FileLock, RemoteOperationResult, FileChannel> channelResult = initFileChannel(result, fileLock, e2eFiles);
  444. fileLock = channelResult.getFirst();
  445. result = channelResult.getSecond();
  446. FileChannel channel = channelResult.getThird();
  447. size = getChannelSize(channel);
  448. updateSize(size);
  449. setUploadOperationForE2E(token, e2eFiles.getEncryptedTempFile(), e2eData.getEncryptedFileName(), lastModifiedTimestamp, creationTimestamp, size);
  450. result = performE2EUpload(clientData);
  451. if (result.isSuccess()) {
  452. updateMetadataForE2E(object, e2eData, clientData, e2eFiles, arbitraryDataProvider, encryptionUtilsV2, metadataExists);
  453. }
  454. } catch (FileNotFoundException e) {
  455. Log_OC.d(TAG, mFile.getStoragePath() + " does not exist anymore");
  456. result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
  457. } catch (OverlappingFileLockException e) {
  458. Log_OC.d(TAG, "Overlapping file lock exception");
  459. result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
  460. } catch (Exception e) {
  461. result = new RemoteOperationResult(e);
  462. } finally {
  463. result = cleanupE2EUpload(fileLock, e2eFiles, result, object, client, token);
  464. }
  465. completeE2EUpload(result, e2eFiles, client);
  466. return result;
  467. }
  468. private boolean isEndToEndVersionAtLeastV2() {
  469. return getE2EVersion().compareTo(E2EVersion.V2_0) >= 0;
  470. }
  471. private E2EVersion getE2EVersion() {
  472. return CapabilityUtils.getCapability(mContext).getEndToEndEncryptionApiVersion();
  473. }
  474. private long getE2ECounter(OCFile parentFile) {
  475. long counter = -1;
  476. if (isEndToEndVersionAtLeastV2()) {
  477. counter = parentFile.getE2eCounter() + 1;
  478. }
  479. return counter;
  480. }
  481. private String getFolderUnlockTokenOrLockFolder(OwnCloudClient client, OCFile parentFile, long counter) throws UploadException {
  482. if (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()) {
  483. return mFolderUnlockToken;
  484. }
  485. String token = EncryptionUtils.lockFolder(parentFile, client, counter);
  486. mUpload.setFolderUnlockToken(token);
  487. uploadsStorageManager.updateUpload(mUpload);
  488. return token;
  489. }
  490. private DecryptedFolderMetadataFileV1 getDecryptedFolderMetadataV1(String publicKey, Object object)
  491. throws NoSuchPaddingException, IllegalBlockSizeException, CertificateException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
  492. DecryptedFolderMetadataFileV1 metadata = new DecryptedFolderMetadataFileV1();
  493. metadata.setMetadata(new DecryptedMetadata());
  494. metadata.getMetadata().setVersion(1.2);
  495. metadata.getMetadata().setMetadataKeys(new HashMap<>());
  496. String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
  497. String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
  498. metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
  499. if (object instanceof DecryptedFolderMetadataFileV1) {
  500. metadata = (DecryptedFolderMetadataFileV1) object;
  501. }
  502. return metadata;
  503. }
  504. private List<String> getCollidedFileNames(Object object) {
  505. List<String> result = new ArrayList<>();
  506. if (object instanceof DecryptedFolderMetadataFileV1 metadata) {
  507. for (DecryptedFile file : metadata.getFiles().values()) {
  508. result.add(file.getEncrypted().getFilename());
  509. }
  510. } else if (object instanceof DecryptedFolderMetadataFile metadataFile) {
  511. Map<String, com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile> files = metadataFile.getMetadata().getFiles();
  512. for (com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile file : files.values()) {
  513. result.add(file.getFilename());
  514. }
  515. }
  516. return result;
  517. }
  518. private String getEncryptedFileName(Object object) {
  519. String encryptedFileName = EncryptionUtils.generateUid();
  520. if (object instanceof DecryptedFolderMetadataFileV1 metadata) {
  521. while (metadata.getFiles().get(encryptedFileName) != null) {
  522. encryptedFileName = EncryptionUtils.generateUid();
  523. }
  524. } else {
  525. while (((DecryptedFolderMetadataFile) object).getMetadata().getFiles().get(encryptedFileName) != null) {
  526. encryptedFileName = EncryptionUtils.generateUid();
  527. }
  528. }
  529. return encryptedFileName;
  530. }
  531. private void setUploadOperationForE2E(String token,
  532. File encryptedTempFile,
  533. String encryptedFileName,
  534. long lastModifiedTimestamp,
  535. long creationTimestamp,
  536. long size) {
  537. if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
  538. boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
  539. mUploadOperation = new ChunkedFileUploadRemoteOperation(encryptedTempFile.getAbsolutePath(),
  540. mFile.getParentRemotePath() + encryptedFileName,
  541. mFile.getMimeType(),
  542. mFile.getEtagInConflict(),
  543. lastModifiedTimestamp,
  544. onWifiConnection,
  545. token,
  546. creationTimestamp,
  547. mDisableRetries
  548. );
  549. } else {
  550. mUploadOperation = new UploadFileRemoteOperation(encryptedTempFile.getAbsolutePath(),
  551. mFile.getParentRemotePath() + encryptedFileName,
  552. mFile.getMimeType(),
  553. mFile.getEtagInConflict(),
  554. lastModifiedTimestamp,
  555. creationTimestamp,
  556. token,
  557. mDisableRetries
  558. );
  559. }
  560. }
  561. private Triple<FileLock, RemoteOperationResult, FileChannel> initFileChannel(RemoteOperationResult result, FileLock fileLock, E2EFiles e2eFiles) throws IOException {
  562. FileChannel channel = null;
  563. try (RandomAccessFile randomAccessFile = new RandomAccessFile(mFile.getStoragePath(), "rw")) {
  564. channel = randomAccessFile.getChannel();
  565. fileLock = channel.tryLock();
  566. } catch (IOException ioException) {
  567. Log_OC.d(TAG, "Error caught at getChannelFromFile: " + ioException);
  568. // this basically means that the file is on SD card
  569. // try to copy file to temporary dir if it doesn't exist
  570. String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) +
  571. mFile.getRemotePath();
  572. mFile.setStoragePath(temporalPath);
  573. e2eFiles.setTemporalFile(new File(temporalPath));
  574. if (e2eFiles.getTemporalFile() == null) {
  575. throw new NullPointerException("Original file cannot be null");
  576. }
  577. Files.deleteIfExists(Paths.get(temporalPath));
  578. result = copy(e2eFiles.getOriginalFile(), e2eFiles.getTemporalFile());
  579. if (result.isSuccess()) {
  580. if (e2eFiles.getTemporalFile().length() == e2eFiles.getOriginalFile().length()) {
  581. try (RandomAccessFile randomAccessFile = new RandomAccessFile(e2eFiles.getTemporalFile().getAbsolutePath(), "rw")) {
  582. channel = randomAccessFile.getChannel();
  583. fileLock = channel.tryLock();
  584. } catch (IOException e) {
  585. Log_OC.d(TAG, "Error caught at getChannelFromFile: " + e);
  586. }
  587. } else {
  588. result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
  589. }
  590. }
  591. }
  592. return new Triple<>(fileLock, result, channel);
  593. }
  594. private long getChannelSize(FileChannel channel) {
  595. try {
  596. return channel.size();
  597. } catch (IOException e1) {
  598. return new File(mFile.getStoragePath()).length();
  599. }
  600. }
  601. private RemoteOperationResult performE2EUpload(E2EClientData data) throws OperationCancelledException {
  602. for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
  603. mUploadOperation.addDataTransferProgressListener(mDataTransferListener);
  604. }
  605. if (mCancellationRequested.get()) {
  606. throw new OperationCancelledException();
  607. }
  608. RemoteOperationResult result = mUploadOperation.execute(data.getClient());
  609. /// move local temporal file or original file to its corresponding
  610. // location in the Nextcloud local folder
  611. if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
  612. result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  613. }
  614. return result;
  615. }
  616. private E2EData getE2EData(Object object) throws InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidParameterSpecException, IOException {
  617. byte[] key = EncryptionUtils.generateKey();
  618. byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
  619. Cipher cipher = EncryptionUtils.getCipher(Cipher.ENCRYPT_MODE, key, iv);
  620. File file = new File(mFile.getStoragePath());
  621. EncryptedFile encryptedFile = EncryptionUtils.encryptFile(user.getAccountName(), file, cipher);
  622. String encryptedFileName = getEncryptedFileName(object);
  623. if (key == null) {
  624. throw new NullPointerException("key cannot be null");
  625. }
  626. return new E2EData(key, iv, encryptedFile, encryptedFileName);
  627. }
  628. private void updateMetadataForE2E(Object object, E2EData e2eData, E2EClientData clientData, E2EFiles e2eFiles, ArbitraryDataProvider arbitraryDataProvider, EncryptionUtilsV2 encryptionUtilsV2, boolean metadataExists)
  629. throws InvalidAlgorithmParameterException, UploadException, NoSuchPaddingException, IllegalBlockSizeException, CertificateException,
  630. NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
  631. mFile.setDecryptedRemotePath(e2eFiles.getParentFile().getDecryptedRemotePath() + e2eFiles.getOriginalFile().getName());
  632. mFile.setRemotePath(e2eFiles.getParentFile().getRemotePath() + e2eData.getEncryptedFileName());
  633. if (object instanceof DecryptedFolderMetadataFileV1 metadata) {
  634. updateMetadataForV1(metadata,
  635. e2eData,
  636. clientData,
  637. e2eFiles.getParentFile(),
  638. arbitraryDataProvider,
  639. metadataExists);
  640. } else if (object instanceof DecryptedFolderMetadataFile metadata) {
  641. updateMetadataForV2(metadata,
  642. encryptionUtilsV2,
  643. e2eData,
  644. clientData,
  645. e2eFiles.getParentFile());
  646. }
  647. }
  648. private void updateMetadataForV1(DecryptedFolderMetadataFileV1 metadata, E2EData e2eData, E2EClientData clientData,
  649. OCFile parentFile, ArbitraryDataProvider arbitraryDataProvider, boolean metadataExists)
  650. throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException,
  651. CertificateException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, UploadException {
  652. DecryptedFile decryptedFile = new DecryptedFile();
  653. Data data = new Data();
  654. data.setFilename(mFile.getDecryptedFileName());
  655. data.setMimetype(mFile.getMimeType());
  656. data.setKey(EncryptionUtils.encodeBytesToBase64String(e2eData.getKey()));
  657. decryptedFile.setEncrypted(data);
  658. decryptedFile.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(e2eData.getIv()));
  659. decryptedFile.setAuthenticationTag(e2eData.getEncryptedFile().getAuthenticationTag());
  660. metadata.getFiles().put(e2eData.getEncryptedFileName(), decryptedFile);
  661. EncryptedFolderMetadataFileV1 encryptedFolderMetadata =
  662. EncryptionUtils.encryptFolderMetadata(metadata,
  663. clientData.getPublicKey(),
  664. parentFile.getLocalId(),
  665. user,
  666. arbitraryDataProvider
  667. );
  668. String serializedFolderMetadata;
  669. if (metadata.getMetadata().getMetadataKey() != null) {
  670. serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata, true);
  671. } else {
  672. serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
  673. }
  674. // upload metadata
  675. EncryptionUtils.uploadMetadata(parentFile,
  676. serializedFolderMetadata,
  677. clientData.getToken(),
  678. clientData.getClient(),
  679. metadataExists,
  680. E2EVersion.V1_2,
  681. "",
  682. arbitraryDataProvider,
  683. user);
  684. }
  685. private void updateMetadataForV2(DecryptedFolderMetadataFile metadata, EncryptionUtilsV2 encryptionUtilsV2, E2EData e2eData, E2EClientData clientData, OCFile parentFile) throws UploadException {
  686. encryptionUtilsV2.addFileToMetadata(
  687. e2eData.getEncryptedFileName(),
  688. mFile,
  689. e2eData.getIv(),
  690. e2eData.getEncryptedFile().getAuthenticationTag(),
  691. e2eData.getKey(),
  692. metadata,
  693. getStorageManager());
  694. // upload metadata
  695. encryptionUtilsV2.serializeAndUploadMetadata(parentFile,
  696. metadata,
  697. clientData.getToken(),
  698. clientData.getClient(),
  699. true,
  700. mContext,
  701. user,
  702. getStorageManager());
  703. }
  704. private void completeE2EUpload(RemoteOperationResult result, E2EFiles e2eFiles, OwnCloudClient client) {
  705. if (result.isSuccess()) {
  706. handleSuccessfulUpload(e2eFiles.getTemporalFile(), e2eFiles.getExpectedFile(), e2eFiles.getOriginalFile(), client);
  707. } else if (result.getCode() == ResultCode.SYNC_CONFLICT) {
  708. getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
  709. }
  710. e2eFiles.deleteTemporalFile();
  711. }
  712. private void deleteDuplicatedFileAndSendRefreshFolderEvent(OwnCloudClient client) {
  713. FileUploadHelper.Companion.instance().removeDuplicatedFile(duplicatedEncryptedFile, client, user, () -> {
  714. duplicatedEncryptedFile = null;
  715. sendRefreshFolderEventBroadcast();
  716. return null;
  717. });
  718. }
  719. private RemoteOperationResult cleanupE2EUpload(FileLock fileLock, E2EFiles e2eFiles, RemoteOperationResult result, Object object, OwnCloudClient client, String token) {
  720. mUploadStarted.set(false);
  721. if (fileLock != null) {
  722. try {
  723. fileLock.release();
  724. } catch (IOException e) {
  725. Log_OC.e(TAG, "Failed to unlock file with path " + mFile.getStoragePath());
  726. }
  727. }
  728. e2eFiles.deleteTemporalFileWithOriginalFileComparison();
  729. if (result == null) {
  730. result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
  731. }
  732. logResult(result, mFile.getStoragePath(), mFile.getRemotePath());
  733. // Unlock must be done otherwise folder stays locked and user can't upload any file
  734. RemoteOperationResult<Void> unlockFolderResult;
  735. if (object instanceof DecryptedFolderMetadataFileV1) {
  736. unlockFolderResult = EncryptionUtils.unlockFolderV1(e2eFiles.getParentFile(), client, token);
  737. } else {
  738. unlockFolderResult = EncryptionUtils.unlockFolder(e2eFiles.getParentFile(), client, token);
  739. }
  740. if (unlockFolderResult != null && !unlockFolderResult.isSuccess()) {
  741. result = unlockFolderResult;
  742. }
  743. if (unlockFolderResult != null && unlockFolderResult.isSuccess()) {
  744. Log_OC.d(TAG, "Folder successfully unlocked: " + e2eFiles.getParentFile().getFileName());
  745. if (duplicatedEncryptedFile != null) {
  746. deleteDuplicatedFileAndSendRefreshFolderEvent(client);
  747. } else {
  748. sendRefreshFolderEventBroadcast();
  749. }
  750. }
  751. e2eFiles.deleteEncryptedTempFile();
  752. return result;
  753. }
  754. // endregion
  755. private void sendRefreshFolderEventBroadcast() {
  756. Intent intent = new Intent(REFRESH_FOLDER_EVENT_RECEIVER);
  757. LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
  758. }
  759. private RemoteOperationResult checkConditions(File originalFile) {
  760. RemoteOperationResult remoteOperationResult = null;
  761. // check that connectivity conditions are met and delays the upload otherwise
  762. Connectivity connectivity = connectivityService.getConnectivity();
  763. if (mOnWifiOnly && (!connectivity.isWifi() || connectivity.isMetered())) {
  764. Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
  765. remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
  766. }
  767. // check if charging conditions are met and delays the upload otherwise
  768. final BatteryStatus battery = powerManagementService.getBattery();
  769. if (mWhileChargingOnly && !battery.isCharging()) {
  770. Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath());
  771. remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
  772. }
  773. // check that device is not in power save mode
  774. if (!mIgnoringPowerSaveMode && powerManagementService.isPowerSavingEnabled()) {
  775. Log_OC.d(TAG, "Upload delayed because device is in power save mode: " + getRemotePath());
  776. remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_IN_POWER_SAVE_MODE);
  777. }
  778. // check if the file continues existing before schedule the operation
  779. if (!originalFile.exists()) {
  780. Log_OC.d(TAG, mOriginalStoragePath + " does not exist anymore");
  781. remoteOperationResult = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
  782. }
  783. // check that internet is not behind walled garden
  784. if (!connectivityService.getConnectivity().isConnected() || connectivityService.isInternetWalled()) {
  785. remoteOperationResult = new RemoteOperationResult(ResultCode.NO_NETWORK_CONNECTION);
  786. }
  787. return remoteOperationResult;
  788. }
  789. private RemoteOperationResult normalUpload(OwnCloudClient client) {
  790. RemoteOperationResult result = null;
  791. File temporalFile = null;
  792. File originalFile = new File(mOriginalStoragePath);
  793. File expectedFile = null;
  794. FileLock fileLock = null;
  795. FileChannel channel = null;
  796. long size;
  797. try {
  798. // check conditions
  799. result = checkConditions(originalFile);
  800. if (result != null) {
  801. return result;
  802. }
  803. // check name collision
  804. RemoteOperationResult collisionResult = checkNameCollision(null, client, null, false);
  805. if (collisionResult != null) {
  806. result = collisionResult;
  807. return collisionResult;
  808. }
  809. String expectedPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mFile);
  810. expectedFile = new File(expectedPath);
  811. result = copyFile(originalFile, expectedPath);
  812. if (!result.isSuccess()) {
  813. return result;
  814. }
  815. // Get the last modification date of the file from the file system
  816. long lastModifiedTimestamp = originalFile.lastModified() / 1000;
  817. final Long creationTimestamp = FileUtil.getCreationTimestamp(originalFile);
  818. try {
  819. channel = new RandomAccessFile(mFile.getStoragePath(), "rw").getChannel();
  820. fileLock = channel.tryLock();
  821. } catch (FileNotFoundException e) {
  822. // this basically means that the file is on SD card
  823. // try to copy file to temporary dir if it doesn't exist
  824. String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) +
  825. mFile.getRemotePath();
  826. mFile.setStoragePath(temporalPath);
  827. temporalFile = new File(temporalPath);
  828. Files.deleteIfExists(Paths.get(temporalPath));
  829. result = copy(originalFile, temporalFile);
  830. if (result.isSuccess()) {
  831. if (temporalFile.length() == originalFile.length()) {
  832. channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
  833. fileLock = channel.tryLock();
  834. } else {
  835. result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
  836. }
  837. }
  838. }
  839. try {
  840. size = channel.size();
  841. } catch (Exception e1) {
  842. size = new File(mFile.getStoragePath()).length();
  843. }
  844. updateSize(size);
  845. // perform the upload
  846. if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
  847. boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
  848. mUploadOperation = new ChunkedFileUploadRemoteOperation(mFile.getStoragePath(),
  849. mFile.getRemotePath(),
  850. mFile.getMimeType(),
  851. mFile.getEtagInConflict(),
  852. lastModifiedTimestamp,
  853. creationTimestamp,
  854. onWifiConnection,
  855. mDisableRetries);
  856. } else {
  857. mUploadOperation = new UploadFileRemoteOperation(mFile.getStoragePath(),
  858. mFile.getRemotePath(),
  859. mFile.getMimeType(),
  860. mFile.getEtagInConflict(),
  861. lastModifiedTimestamp,
  862. creationTimestamp,
  863. mDisableRetries);
  864. }
  865. for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
  866. mUploadOperation.addDataTransferProgressListener(mDataTransferListener);
  867. }
  868. if (mCancellationRequested.get()) {
  869. throw new OperationCancelledException();
  870. }
  871. if (result.isSuccess() && mUploadOperation != null) {
  872. result = mUploadOperation.execute(client);
  873. /// move local temporal file or original file to its corresponding
  874. // location in the Nextcloud local folder
  875. if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
  876. result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  877. }
  878. }
  879. } catch (FileNotFoundException e) {
  880. Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore");
  881. result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
  882. } catch (OverlappingFileLockException e) {
  883. Log_OC.d(TAG, "Overlapping file lock exception");
  884. result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
  885. } catch (Exception e) {
  886. result = new RemoteOperationResult(e);
  887. } finally {
  888. mUploadStarted.set(false);
  889. if (fileLock != null) {
  890. try {
  891. fileLock.release();
  892. } catch (IOException e) {
  893. Log_OC.e(TAG, "Failed to unlock file with path " + mOriginalStoragePath);
  894. }
  895. }
  896. if (channel != null) {
  897. try {
  898. channel.close();
  899. } catch (IOException e) {
  900. Log_OC.w(TAG, "Failed to close file channel");
  901. }
  902. }
  903. if (temporalFile != null && !originalFile.equals(temporalFile)) {
  904. temporalFile.delete();
  905. }
  906. if (result == null) {
  907. result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
  908. }
  909. logResult(result, mOriginalStoragePath, mRemotePath);
  910. }
  911. if (result.isSuccess()) {
  912. handleSuccessfulUpload(temporalFile, expectedFile, originalFile, client);
  913. } else if (result.getCode() == ResultCode.SYNC_CONFLICT) {
  914. getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
  915. }
  916. // delete temporal file
  917. if (temporalFile != null && temporalFile.exists() && !temporalFile.delete()) {
  918. Log_OC.e(TAG, "Could not delete temporal file " + temporalFile.getAbsolutePath());
  919. }
  920. return result;
  921. }
  922. private void updateSize(long size) {
  923. OCUpload ocUpload = uploadsStorageManager.getUploadById(getOCUploadId());
  924. if (ocUpload != null) {
  925. ocUpload.setFileSize(size);
  926. uploadsStorageManager.updateUpload(ocUpload);
  927. }
  928. }
  929. private void logResult(RemoteOperationResult result, String sourcePath, String targetPath) {
  930. if (result.isSuccess()) {
  931. Log_OC.i(TAG, "Upload of " + sourcePath + " to " + targetPath + ": " + result.getLogMessage());
  932. } else {
  933. if (result.getException() != null) {
  934. if (result.isCancelled()) {
  935. Log_OC.w(TAG, "Upload of " + sourcePath + " to " + targetPath + ": "
  936. + result.getLogMessage());
  937. } else {
  938. Log_OC.e(TAG, "Upload of " + sourcePath + " to " + targetPath + ": "
  939. + result.getLogMessage(), result.getException());
  940. }
  941. } else {
  942. Log_OC.e(TAG, "Upload of " + sourcePath + " to " + targetPath + ": " + result.getLogMessage());
  943. }
  944. }
  945. }
  946. private RemoteOperationResult copyFile(File originalFile, String expectedPath) throws OperationCancelledException,
  947. IOException {
  948. if (mLocalBehaviour == FileUploadWorker.LOCAL_BEHAVIOUR_COPY && !mOriginalStoragePath.equals(expectedPath)) {
  949. String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) +
  950. mFile.getRemotePath();
  951. mFile.setStoragePath(temporalPath);
  952. File temporalFile = new File(temporalPath);
  953. return copy(originalFile, temporalFile);
  954. }
  955. if (mCancellationRequested.get()) {
  956. throw new OperationCancelledException();
  957. }
  958. return new RemoteOperationResult(ResultCode.OK);
  959. }
  960. @CheckResult
  961. private RemoteOperationResult checkNameCollision(OCFile parentFile,
  962. OwnCloudClient client,
  963. List<String> fileNames,
  964. boolean encrypted)
  965. throws OperationCancelledException {
  966. Log_OC.d(TAG, "Checking name collision in server");
  967. if (existsFile(client, mRemotePath, fileNames, encrypted)) {
  968. switch (mNameCollisionPolicy) {
  969. case CANCEL:
  970. Log_OC.d(TAG, "File exists; canceling");
  971. throw new OperationCancelledException();
  972. case RENAME:
  973. mRemotePath = getNewAvailableRemotePath(client, mRemotePath, fileNames, encrypted);
  974. mWasRenamed = true;
  975. createNewOCFile(mRemotePath);
  976. Log_OC.d(TAG, "File renamed as " + mRemotePath);
  977. if (mRenameUploadListener != null) {
  978. mRenameUploadListener.onRenameUpload();
  979. }
  980. break;
  981. case OVERWRITE:
  982. if (parentFile != null && encrypted) {
  983. duplicatedEncryptedFile = getStorageManager().findDuplicatedFile(parentFile, mFile);
  984. }
  985. Log_OC.d(TAG, "Overwriting file");
  986. break;
  987. case ASK_USER:
  988. Log_OC.d(TAG, "Name collision; asking the user what to do");
  989. return new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  990. }
  991. }
  992. if (mCancellationRequested.get()) {
  993. throw new OperationCancelledException();
  994. }
  995. return null;
  996. }
  997. private void handleSuccessfulUpload(File temporalFile,
  998. File expectedFile,
  999. File originalFile,
  1000. OwnCloudClient client) {
  1001. switch (mLocalBehaviour) {
  1002. case FileUploadWorker.LOCAL_BEHAVIOUR_FORGET:
  1003. default:
  1004. mFile.setStoragePath("");
  1005. saveUploadedFile(client);
  1006. break;
  1007. case FileUploadWorker.LOCAL_BEHAVIOUR_DELETE:
  1008. originalFile.delete();
  1009. mFile.setStoragePath("");
  1010. getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
  1011. saveUploadedFile(client);
  1012. break;
  1013. case FileUploadWorker.LOCAL_BEHAVIOUR_COPY:
  1014. if (temporalFile != null) {
  1015. try {
  1016. move(temporalFile, expectedFile);
  1017. } catch (IOException e) {
  1018. Log_OC.e(TAG, e.getMessage());
  1019. }
  1020. } else if (originalFile != null) {
  1021. try {
  1022. copy(originalFile, expectedFile);
  1023. } catch (IOException e) {
  1024. Log_OC.e(TAG, e.getMessage());
  1025. }
  1026. }
  1027. mFile.setStoragePath(expectedFile.getAbsolutePath());
  1028. saveUploadedFile(client);
  1029. if (MimeTypeUtil.isMedia(mFile.getMimeType())) {
  1030. FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
  1031. }
  1032. break;
  1033. case FileUploadWorker.LOCAL_BEHAVIOUR_MOVE:
  1034. String expectedPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mFile);
  1035. File newFile = new File(expectedPath);
  1036. try {
  1037. move(originalFile, newFile);
  1038. } catch (IOException e) {
  1039. Log_OC.e(TAG, "Error moving file", e);
  1040. }
  1041. getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
  1042. mFile.setStoragePath(newFile.getAbsolutePath());
  1043. saveUploadedFile(client);
  1044. if (MimeTypeUtil.isMedia(mFile.getMimeType())) {
  1045. FileDataStorageManager.triggerMediaScan(newFile.getAbsolutePath());
  1046. }
  1047. break;
  1048. }
  1049. }
  1050. private OCCapability getCapabilities() {
  1051. return CapabilityUtils.getCapability(mContext);
  1052. }
  1053. /**
  1054. * Checks the existence of the folder where the current file will be uploaded both in the remote server and in the
  1055. * local database.
  1056. * <p/>
  1057. * If the upload is set to enforce the creation of the folder, the method tries to create it both remote and
  1058. * locally.
  1059. *
  1060. * @param pathToGrant Full remote path whose existence will be granted.
  1061. * @return An {@link OCFile} instance corresponding to the folder where the file will be uploaded.
  1062. */
  1063. private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
  1064. RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, false);
  1065. RemoteOperationResult result = operation.execute(client);
  1066. if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
  1067. SyncOperation syncOp = new CreateFolderOperation(pathToGrant, user, getContext(), getStorageManager());
  1068. result = syncOp.execute(client);
  1069. }
  1070. if (result.isSuccess()) {
  1071. OCFile parentDir = getStorageManager().getFileByPath(pathToGrant);
  1072. if (parentDir == null) {
  1073. parentDir = createLocalFolder(pathToGrant);
  1074. }
  1075. if (parentDir != null) {
  1076. result = new RemoteOperationResult(ResultCode.OK);
  1077. } else {
  1078. result = new RemoteOperationResult(ResultCode.CANNOT_CREATE_FILE);
  1079. }
  1080. }
  1081. return result;
  1082. }
  1083. private OCFile createLocalFolder(String remotePath) {
  1084. String parentPath = new File(remotePath).getParent();
  1085. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
  1086. parentPath : parentPath + OCFile.PATH_SEPARATOR;
  1087. OCFile parent = getStorageManager().getFileByPath(parentPath);
  1088. if (parent == null) {
  1089. parent = createLocalFolder(parentPath);
  1090. }
  1091. if (parent != null) {
  1092. OCFile createdFolder = new OCFile(remotePath);
  1093. createdFolder.setMimeType(MimeType.DIRECTORY);
  1094. createdFolder.setParentId(parent.getFileId());
  1095. getStorageManager().saveFile(createdFolder);
  1096. return createdFolder;
  1097. }
  1098. return null;
  1099. }
  1100. /**
  1101. * Create a new OCFile mFile with new remote path. This is required if nameCollisionPolicy==RENAME. New file is
  1102. * stored as mFile, original as mOldFile.
  1103. *
  1104. * @param newRemotePath new remote path
  1105. */
  1106. private void createNewOCFile(String newRemotePath) {
  1107. // a new OCFile instance must be created for a new remote path
  1108. OCFile newFile = new OCFile(newRemotePath);
  1109. newFile.setCreationTimestamp(mFile.getCreationTimestamp());
  1110. newFile.setFileLength(mFile.getFileLength());
  1111. newFile.setMimeType(mFile.getMimeType());
  1112. newFile.setModificationTimestamp(mFile.getModificationTimestamp());
  1113. newFile.setModificationTimestampAtLastSyncForData(
  1114. mFile.getModificationTimestampAtLastSyncForData()
  1115. );
  1116. newFile.setEtag(mFile.getEtag());
  1117. newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
  1118. newFile.setLastSyncDateForData(mFile.getLastSyncDateForData());
  1119. newFile.setStoragePath(mFile.getStoragePath());
  1120. newFile.setParentId(mFile.getParentId());
  1121. mOldFile = mFile;
  1122. mFile = newFile;
  1123. }
  1124. /**
  1125. * Returns a new and available (does not exists on the server) remotePath. This adds an incremental suffix.
  1126. *
  1127. * @param client OwnCloud client
  1128. * @param remotePath remote path of the file
  1129. * @param fileNames list of decrypted file names
  1130. * @return new remote path
  1131. */
  1132. private String getNewAvailableRemotePath(OwnCloudClient client,
  1133. String remotePath,
  1134. List<String> fileNames,
  1135. boolean encrypted) {
  1136. int extPos = remotePath.lastIndexOf('.');
  1137. String suffix;
  1138. String extension = "";
  1139. String remotePathWithoutExtension = "";
  1140. if (extPos >= 0) {
  1141. extension = remotePath.substring(extPos + 1);
  1142. remotePathWithoutExtension = remotePath.substring(0, extPos);
  1143. }
  1144. int count = 2;
  1145. boolean exists;
  1146. String newPath;
  1147. do {
  1148. suffix = " (" + count + ")";
  1149. newPath = extPos >= 0 ? remotePathWithoutExtension + suffix + "." + extension : remotePath + suffix;
  1150. exists = existsFile(client, newPath, fileNames, encrypted);
  1151. count++;
  1152. } while (exists);
  1153. return newPath;
  1154. }
  1155. private boolean existsFile(OwnCloudClient client,
  1156. String remotePath,
  1157. List<String> fileNames,
  1158. boolean encrypted) {
  1159. if (encrypted) {
  1160. String fileName = new File(remotePath).getName();
  1161. for (String name : fileNames) {
  1162. if (name.equalsIgnoreCase(fileName)) {
  1163. return true;
  1164. }
  1165. }
  1166. return false;
  1167. } else {
  1168. ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, false);
  1169. RemoteOperationResult result = existsOperation.execute(client);
  1170. return result.isSuccess();
  1171. }
  1172. }
  1173. /**
  1174. * Allows to cancel the actual upload operation. If actual upload operating is in progress it is cancelled, if
  1175. * upload preparation is being performed upload will not take place.
  1176. */
  1177. public void cancel(ResultCode cancellationReason) {
  1178. if (mUploadOperation == null) {
  1179. if (mUploadStarted.get()) {
  1180. Log_OC.d(TAG, "Cancelling upload during upload preparations.");
  1181. mCancellationRequested.set(true);
  1182. } else {
  1183. mCancellationRequested.set(true);
  1184. Log_OC.e(TAG, "No upload in progress. This should not happen.");
  1185. }
  1186. } else {
  1187. Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
  1188. mUploadOperation.cancel(cancellationReason);
  1189. }
  1190. }
  1191. /**
  1192. * As soon as this method return true, upload can be cancel via cancel().
  1193. */
  1194. public boolean isUploadInProgress() {
  1195. return mUploadStarted.get();
  1196. }
  1197. /**
  1198. * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
  1199. * TODO use Exceptions instead
  1200. *
  1201. * @param sourceFile Source file to copy.
  1202. * @param targetFile Target location to copy the file.
  1203. * @return {@link RemoteOperationResult}
  1204. * @throws IOException exception if file cannot be accessed
  1205. */
  1206. private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException {
  1207. Log_OC.d(TAG, "Copying local file");
  1208. if (FileStorageUtils.getUsableSpace() < sourceFile.length()) {
  1209. return new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL); // error when the file should be copied
  1210. } else {
  1211. Log_OC.d(TAG, "Creating temporal folder");
  1212. File temporalParent = targetFile.getParentFile();
  1213. if (!temporalParent.mkdirs() && !temporalParent.isDirectory()) {
  1214. return new RemoteOperationResult(ResultCode.CANNOT_CREATE_FILE);
  1215. }
  1216. Log_OC.d(TAG, "Creating temporal file");
  1217. if (!targetFile.createNewFile() && !targetFile.isFile()) {
  1218. return new RemoteOperationResult(ResultCode.CANNOT_CREATE_FILE);
  1219. }
  1220. Log_OC.d(TAG, "Copying file contents");
  1221. InputStream in = null;
  1222. OutputStream out = null;
  1223. try {
  1224. if (!mOriginalStoragePath.equals(targetFile.getAbsolutePath())) {
  1225. // In case document provider schema as 'content://'
  1226. if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
  1227. Uri uri = Uri.parse(mOriginalStoragePath);
  1228. in = mContext.getContentResolver().openInputStream(uri);
  1229. } else {
  1230. in = new FileInputStream(sourceFile);
  1231. }
  1232. out = new FileOutputStream(targetFile);
  1233. int nRead;
  1234. byte[] buf = new byte[4096];
  1235. while (!mCancellationRequested.get() &&
  1236. (nRead = in.read(buf)) > -1) {
  1237. out.write(buf, 0, nRead);
  1238. }
  1239. out.flush();
  1240. } // else: weird but possible situation, nothing to copy
  1241. if (mCancellationRequested.get()) {
  1242. return new RemoteOperationResult(new OperationCancelledException());
  1243. }
  1244. } catch (Exception e) {
  1245. return new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
  1246. } finally {
  1247. try {
  1248. if (in != null) {
  1249. in.close();
  1250. }
  1251. } catch (Exception e) {
  1252. Log_OC.d(TAG, "Weird exception while closing input stream for " +
  1253. mOriginalStoragePath + " (ignoring)", e);
  1254. }
  1255. try {
  1256. if (out != null) {
  1257. out.close();
  1258. }
  1259. } catch (Exception e) {
  1260. Log_OC.d(TAG, "Weird exception while closing output stream for " +
  1261. targetFile.getAbsolutePath() + " (ignoring)", e);
  1262. }
  1263. }
  1264. }
  1265. return new RemoteOperationResult(ResultCode.OK);
  1266. }
  1267. /**
  1268. * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
  1269. * TODO use Exceptions instead
  1270. * TODO refactor both this and 'copy' in a single method
  1271. *
  1272. * @param sourceFile Source file to move.
  1273. * @param targetFile Target location to move the file.
  1274. * @throws IOException exception if file cannot be read/wrote
  1275. */
  1276. private void move(File sourceFile, File targetFile) throws IOException {
  1277. if (!targetFile.equals(sourceFile)) {
  1278. File expectedFolder = targetFile.getParentFile();
  1279. expectedFolder.mkdirs();
  1280. if (expectedFolder.isDirectory()) {
  1281. if (!sourceFile.renameTo(targetFile)) {
  1282. // try to copy and then delete
  1283. targetFile.createNewFile();
  1284. FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
  1285. FileChannel outChannel = new FileOutputStream(targetFile).getChannel();
  1286. try {
  1287. inChannel.transferTo(0, inChannel.size(), outChannel);
  1288. sourceFile.delete();
  1289. } catch (Exception e) {
  1290. mFile.setStoragePath(""); // forget the local file
  1291. // by now, treat this as a success; the file was uploaded
  1292. // the best option could be show a warning message
  1293. } finally {
  1294. if (inChannel != null) {
  1295. inChannel.close();
  1296. }
  1297. if (outChannel != null) {
  1298. outChannel.close();
  1299. }
  1300. }
  1301. }
  1302. } else {
  1303. mFile.setStoragePath("");
  1304. }
  1305. }
  1306. }
  1307. /**
  1308. * Saves a OC File after a successful upload.
  1309. * <p>
  1310. * A PROPFIND is necessary to keep the props in the local database synchronized with the server, specially the
  1311. * modification time and Etag (where available)
  1312. */
  1313. private void saveUploadedFile(OwnCloudClient client) {
  1314. OCFile file = mFile;
  1315. if (file.fileExists()) {
  1316. file = getStorageManager().getFileById(file.getFileId());
  1317. }
  1318. if (file == null) {
  1319. // this can happen e.g. when the file gets deleted during upload
  1320. return;
  1321. }
  1322. long syncDate = System.currentTimeMillis();
  1323. file.setLastSyncDateForData(syncDate);
  1324. // new PROPFIND to keep data consistent with server
  1325. // in theory, should return the same we already have
  1326. // TODO from the appropriate OC server version, get data from last PUT response headers, instead
  1327. // TODO of a new PROPFIND; the latter may fail, specially for chunked uploads
  1328. String path;
  1329. if (encryptedAncestor) {
  1330. path = file.getParentRemotePath() + mFile.getEncryptedFileName();
  1331. } else {
  1332. path = getRemotePath();
  1333. }
  1334. ReadFileRemoteOperation operation = new ReadFileRemoteOperation(path);
  1335. RemoteOperationResult result = operation.execute(client);
  1336. if (result.isSuccess()) {
  1337. updateOCFile(file, (RemoteFile) result.getData().get(0));
  1338. file.setLastSyncDateForProperties(syncDate);
  1339. } else {
  1340. Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt...");
  1341. }
  1342. if (mWasRenamed) {
  1343. OCFile oldFile = getStorageManager().getFileByPath(mOldFile.getRemotePath());
  1344. if (oldFile != null) {
  1345. oldFile.setStoragePath(null);
  1346. getStorageManager().saveFile(oldFile);
  1347. getStorageManager().saveConflict(oldFile, null);
  1348. }
  1349. // else: it was just an automatic renaming due to a name
  1350. // coincidence; nothing else is needed, the storagePath is right
  1351. // in the instance returned by mCurrentUpload.getFile()
  1352. }
  1353. file.setUpdateThumbnailNeeded(true);
  1354. getStorageManager().saveFile(file);
  1355. getStorageManager().saveConflict(file, null);
  1356. if (MimeTypeUtil.isMedia(file.getMimeType())) {
  1357. FileDataStorageManager.triggerMediaScan(file.getStoragePath(), file);
  1358. }
  1359. // generate new Thumbnail
  1360. final ThumbnailsCacheManager.ThumbnailGenerationTask task =
  1361. new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), user);
  1362. task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, file.getRemoteId()));
  1363. }
  1364. private void updateOCFile(OCFile file, RemoteFile remoteFile) {
  1365. file.setCreationTimestamp(remoteFile.getCreationTimestamp());
  1366. file.setFileLength(remoteFile.getLength());
  1367. file.setMimeType(remoteFile.getMimeType());
  1368. file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
  1369. file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
  1370. file.setEtag(remoteFile.getEtag());
  1371. file.setRemoteId(remoteFile.getRemoteId());
  1372. file.setPermissions(remoteFile.getPermissions());
  1373. }
  1374. public interface OnRenameListener {
  1375. void onRenameUpload();
  1376. }
  1377. }