UploadFileOperation.java 55 KB

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