UploadFileOperation.java 55 KB

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