UploadFileOperation.java 58 KB

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