UploadFileOperation.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. /* ownCloud Android client application
  2. * Copyright (C) 2012-2013 ownCloud Inc.
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License version 2,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. package com.owncloud.android.operations;
  18. import java.io.File;
  19. import java.io.FileInputStream;
  20. import java.io.FileOutputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.OutputStream;
  24. import java.util.HashSet;
  25. import java.util.Iterator;
  26. import java.util.Set;
  27. import java.util.concurrent.atomic.AtomicBoolean;
  28. import org.apache.commons.httpclient.methods.PutMethod;
  29. import org.apache.commons.httpclient.methods.RequestEntity;
  30. import android.accounts.Account;
  31. import android.content.Context;
  32. import android.net.Uri;
  33. import com.owncloud.android.MainApp;
  34. import com.owncloud.android.datamodel.OCFile;
  35. import com.owncloud.android.files.services.FileUploader;
  36. import com.owncloud.android.lib.common.OwnCloudClient;
  37. import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
  38. import com.owncloud.android.lib.common.network.ProgressiveDataTransferer;
  39. import com.owncloud.android.lib.common.operations.OperationCancelledException;
  40. import com.owncloud.android.lib.common.operations.RemoteOperation;
  41. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  42. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  43. import com.owncloud.android.lib.common.utils.Log_OC;
  44. import com.owncloud.android.lib.resources.files.ChunkedUploadRemoteFileOperation;
  45. import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
  46. import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
  47. import com.owncloud.android.utils.FileStorageUtils;
  48. import com.owncloud.android.utils.UriUtils;
  49. /**
  50. * Remote operation performing the upload of a file to an ownCloud server
  51. *
  52. * @author David A. Velasco
  53. */
  54. public class UploadFileOperation extends RemoteOperation {
  55. private static final String TAG = UploadFileOperation.class.getSimpleName();
  56. private Account mAccount;
  57. private OCFile mFile;
  58. private OCFile mOldFile;
  59. private String mRemotePath = null;
  60. private boolean mChunked = false;
  61. private boolean mIsInstant = false;
  62. private boolean mRemoteFolderToBeCreated = false;
  63. private boolean mForceOverwrite = false;
  64. private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
  65. private boolean mWasRenamed = false;
  66. private String mOriginalFileName = null;
  67. private String mOriginalStoragePath = null;
  68. PutMethod mPutMethod = null;
  69. private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
  70. private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
  71. private Context mContext;
  72. private UploadRemoteFileOperation mUploadOperation;
  73. protected RequestEntity mEntity = null;
  74. public UploadFileOperation( Account account,
  75. OCFile file,
  76. boolean chunked,
  77. boolean isInstant,
  78. boolean forceOverwrite,
  79. int localBehaviour,
  80. Context context) {
  81. if (account == null)
  82. throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation");
  83. if (file == null)
  84. throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
  85. if (file.getStoragePath() == null || file.getStoragePath().length() <= 0) {
  86. throw new IllegalArgumentException(
  87. "Illegal file in UploadFileOperation; storage path invalid: "
  88. + file.getStoragePath());
  89. }
  90. mAccount = account;
  91. mFile = file;
  92. mRemotePath = file.getRemotePath();
  93. mChunked = chunked;
  94. mIsInstant = isInstant;
  95. mForceOverwrite = forceOverwrite;
  96. mLocalBehaviour = localBehaviour;
  97. mOriginalStoragePath = mFile.getStoragePath();
  98. mOriginalFileName = mFile.getFileName();
  99. mContext = context;
  100. }
  101. public Account getAccount() {
  102. return mAccount;
  103. }
  104. public String getFileName() {
  105. return mOriginalFileName;
  106. }
  107. public OCFile getFile() {
  108. return mFile;
  109. }
  110. public OCFile getOldFile() {
  111. return mOldFile;
  112. }
  113. public String getOriginalStoragePath() {
  114. return mOriginalStoragePath;
  115. }
  116. public String getStoragePath() {
  117. return mFile.getStoragePath();
  118. }
  119. public String getRemotePath() {
  120. return mFile.getRemotePath();
  121. }
  122. public String getMimeType() {
  123. return mFile.getMimetype();
  124. }
  125. public boolean isInstant() {
  126. return mIsInstant;
  127. }
  128. public boolean isRemoteFolderToBeCreated() {
  129. return mRemoteFolderToBeCreated;
  130. }
  131. public void setRemoteFolderToBeCreated() {
  132. mRemoteFolderToBeCreated = true;
  133. }
  134. public boolean getForceOverwrite() {
  135. return mForceOverwrite;
  136. }
  137. public boolean wasRenamed() {
  138. return mWasRenamed;
  139. }
  140. public Set<OnDatatransferProgressListener> getDataTransferListeners() {
  141. return mDataTransferListeners;
  142. }
  143. public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
  144. synchronized (mDataTransferListeners) {
  145. mDataTransferListeners.add(listener);
  146. }
  147. if (mEntity != null) {
  148. ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener);
  149. }
  150. }
  151. public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
  152. synchronized (mDataTransferListeners) {
  153. mDataTransferListeners.remove(listener);
  154. }
  155. if (mEntity != null) {
  156. ((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener);
  157. }
  158. }
  159. @Override
  160. protected RemoteOperationResult run(OwnCloudClient client) {
  161. RemoteOperationResult result = null;
  162. boolean localCopyPassed = false, nameCheckPassed = false;
  163. File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null;
  164. try {
  165. // / rename the file to upload, if necessary
  166. if (!mForceOverwrite) {
  167. String remotePath = getAvailableRemotePath(client, mRemotePath);
  168. mWasRenamed = !remotePath.equals(mRemotePath);
  169. if (mWasRenamed) {
  170. createNewOCFile(remotePath);
  171. }
  172. }
  173. nameCheckPassed = true;
  174. String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); // /
  175. // not
  176. // before
  177. // getAvailableRemotePath()
  178. // !!!
  179. expectedFile = new File(expectedPath);
  180. // check location of local file; if not the expected, copy to a
  181. // temporal file before upload (if COPY is the expected behaviour)
  182. if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) {
  183. if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
  184. result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
  185. return result; // error condition when the file should be
  186. // copied
  187. } else {
  188. String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
  189. mFile.setStoragePath(temporalPath);
  190. temporalFile = new File(temporalPath);
  191. File temporalParent = temporalFile.getParentFile();
  192. temporalParent.mkdirs();
  193. if (!temporalParent.isDirectory()) {
  194. throw new IOException("Unexpected error: parent directory could not be created");
  195. }
  196. temporalFile.createNewFile();
  197. if (!temporalFile.isFile()) {
  198. throw new IOException("Unexpected error: target file could not be created");
  199. }
  200. InputStream in = null;
  201. OutputStream out = null;
  202. try {
  203. // In case document provider schema as 'content://'
  204. if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
  205. Uri uri = Uri.parse(mOriginalStoragePath);
  206. in = MainApp.getAppContext().getContentResolver().openInputStream(uri);
  207. out = new FileOutputStream(temporalFile);
  208. int nRead;
  209. byte[] data = new byte[16384];
  210. while ((nRead = in.read(data, 0, data.length)) != -1) {
  211. out.write(data, 0, nRead);
  212. }
  213. out.flush();
  214. } else {
  215. if (!mOriginalStoragePath.equals(temporalPath)) { // preventing
  216. // weird
  217. // but
  218. // possible
  219. // situation
  220. in = new FileInputStream(originalFile);
  221. out = new FileOutputStream(temporalFile);
  222. byte[] buf = new byte[1024];
  223. int len;
  224. while ((len = in.read(buf)) > 0) {
  225. out.write(buf, 0, len);
  226. }
  227. }
  228. }
  229. } catch (Exception e) {
  230. result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
  231. return result;
  232. } finally {
  233. try {
  234. if (in != null)
  235. in.close();
  236. } catch (Exception e) {
  237. Log_OC.d(TAG, "Weird exception while closing input stream for " + mOriginalStoragePath + " (ignoring)", e);
  238. }
  239. try {
  240. if (out != null)
  241. out.close();
  242. } catch (Exception e) {
  243. Log_OC.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e);
  244. }
  245. }
  246. }
  247. }
  248. localCopyPassed = true;
  249. /// perform the upload
  250. if ( mChunked && (new File(mFile.getStoragePath())).length() > ChunkedUploadRemoteFileOperation.CHUNK_SIZE ) {
  251. mUploadOperation = new ChunkedUploadRemoteFileOperation(mFile.getStoragePath(), mFile.getRemotePath(),
  252. mFile.getMimetype());
  253. } else {
  254. mUploadOperation = new UploadRemoteFileOperation(mFile.getStoragePath(), mFile.getRemotePath(),
  255. mFile.getMimetype());
  256. }
  257. Iterator <OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
  258. while (listener.hasNext()) {
  259. mUploadOperation.addDatatransferProgressListener(listener.next());
  260. }
  261. result = mUploadOperation.execute(client);
  262. /// move local temporal file or original file to its corresponding
  263. // location in the ownCloud local folder
  264. if (result.isSuccess()) {
  265. if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) {
  266. mFile.setStoragePath(null);
  267. } else {
  268. mFile.setStoragePath(expectedPath);
  269. File fileToMove = null;
  270. if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
  271. // ; see where temporalFile was
  272. // set
  273. fileToMove = temporalFile;
  274. } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
  275. fileToMove = originalFile;
  276. }
  277. if (!expectedFile.equals(fileToMove)) {
  278. File expectedFolder = expectedFile.getParentFile();
  279. expectedFolder.mkdirs();
  280. if (!expectedFolder.isDirectory() || !fileToMove.renameTo(expectedFile)) {
  281. mFile.setStoragePath(null); // forget the local file
  282. // by now, treat this as a success; the file was
  283. // uploaded; the user won't like that the local file
  284. // is not linked, but this should be a very rare
  285. // fail;
  286. // the best option could be show a warning message
  287. // (but not a fail)
  288. // result = new
  289. // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
  290. // return result;
  291. }
  292. }
  293. }
  294. }
  295. } catch (Exception e) {
  296. // TODO something cleaner with cancellations
  297. if (mCancellationRequested.get()) {
  298. result = new RemoteOperationResult(new OperationCancelledException());
  299. } else {
  300. result = new RemoteOperationResult(e);
  301. }
  302. } finally {
  303. if (temporalFile != null && !originalFile.equals(temporalFile)) {
  304. temporalFile.delete();
  305. }
  306. if (result.isSuccess()) {
  307. Log_OC.i(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
  308. } else {
  309. if (result.getException() != null) {
  310. String complement = "";
  311. if (!nameCheckPassed) {
  312. complement = " (while checking file existence in server)";
  313. } else if (!localCopyPassed) {
  314. complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name)
  315. + ")";
  316. }
  317. Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage() + complement, result.getException());
  318. } else {
  319. Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
  320. }
  321. }
  322. }
  323. return result;
  324. }
  325. private void createNewOCFile(String newRemotePath) {
  326. // a new OCFile instance must be created for a new remote path
  327. OCFile newFile = new OCFile(newRemotePath);
  328. newFile.setCreationTimestamp(mFile.getCreationTimestamp());
  329. newFile.setFileLength(mFile.getFileLength());
  330. newFile.setMimetype(mFile.getMimetype());
  331. newFile.setModificationTimestamp(mFile.getModificationTimestamp());
  332. newFile.setModificationTimestampAtLastSyncForData(mFile.getModificationTimestampAtLastSyncForData());
  333. // newFile.setEtag(mFile.getEtag())
  334. newFile.setKeepInSync(mFile.keepInSync());
  335. newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
  336. newFile.setLastSyncDateForData(mFile.getLastSyncDateForData());
  337. newFile.setStoragePath(mFile.getStoragePath());
  338. newFile.setParentId(mFile.getParentId());
  339. mOldFile = mFile;
  340. mFile = newFile;
  341. }
  342. /**
  343. * Checks if remotePath does not exist in the server and returns it, or adds
  344. * a suffix to it in order to avoid the server file is overwritten.
  345. *
  346. * @param wc
  347. * @param remotePath
  348. * @return
  349. */
  350. private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) throws Exception {
  351. boolean check = existsFile(wc, remotePath);
  352. if (!check) {
  353. return remotePath;
  354. }
  355. int pos = remotePath.lastIndexOf(".");
  356. String suffix = "";
  357. String extension = "";
  358. if (pos >= 0) {
  359. extension = remotePath.substring(pos + 1);
  360. remotePath = remotePath.substring(0, pos);
  361. }
  362. int count = 2;
  363. do {
  364. suffix = " (" + count + ")";
  365. if (pos >= 0) {
  366. check = existsFile(wc, remotePath + suffix + "." + extension);
  367. }
  368. else {
  369. check = existsFile(wc, remotePath + suffix);
  370. }
  371. count++;
  372. } while (check);
  373. if (pos >= 0) {
  374. return remotePath + suffix + "." + extension;
  375. } else {
  376. return remotePath + suffix;
  377. }
  378. }
  379. private boolean existsFile(OwnCloudClient client, String remotePath){
  380. ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, mContext, false);
  381. RemoteOperationResult result = existsOperation.execute(client);
  382. return result.isSuccess();
  383. }
  384. public void cancel() {
  385. if (mUploadOperation != null)
  386. mUploadOperation.cancel();
  387. }
  388. }