SynchronizeFileOperation.java 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /* ownCloud Android client application
  2. * Copyright (C) 2012 Bartek Przybylski
  3. * Copyright (C) 2012-2013 ownCloud Inc.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package com.owncloud.android.operations;
  20. import org.apache.http.HttpStatus;
  21. import org.apache.jackrabbit.webdav.MultiStatus;
  22. import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
  23. import android.accounts.Account;
  24. import android.content.Context;
  25. import android.content.Intent;
  26. import android.util.Log;
  27. import com.owncloud.android.Log_OC;
  28. import com.owncloud.android.datamodel.DataStorageManager;
  29. import com.owncloud.android.datamodel.OCFile;
  30. import com.owncloud.android.files.services.FileDownloader;
  31. import com.owncloud.android.files.services.FileUploader;
  32. import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
  33. import eu.alefzero.webdav.WebdavClient;
  34. import eu.alefzero.webdav.WebdavEntry;
  35. import eu.alefzero.webdav.WebdavUtils;
  36. public class SynchronizeFileOperation extends RemoteOperation {
  37. private String TAG = SynchronizeFileOperation.class.getSimpleName();
  38. private static final int SYNC_READ_TIMEOUT = 10000;
  39. private static final int SYNC_CONNECTION_TIMEOUT = 5000;
  40. private OCFile mLocalFile;
  41. private OCFile mServerFile;
  42. private DataStorageManager mStorageManager;
  43. private Account mAccount;
  44. private boolean mSyncFileContents;
  45. private boolean mLocalChangeAlreadyKnown;
  46. private Context mContext;
  47. private boolean mTransferWasRequested = false;
  48. public SynchronizeFileOperation(
  49. OCFile localFile,
  50. OCFile serverFile, // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation
  51. DataStorageManager storageManager,
  52. Account account,
  53. boolean syncFileContents,
  54. boolean localChangeAlreadyKnown,
  55. Context context) {
  56. mLocalFile = localFile;
  57. mServerFile = serverFile;
  58. mStorageManager = storageManager;
  59. mAccount = account;
  60. mSyncFileContents = syncFileContents;
  61. mLocalChangeAlreadyKnown = localChangeAlreadyKnown;
  62. mContext = context;
  63. }
  64. @Override
  65. protected RemoteOperationResult run(WebdavClient client) {
  66. PropFindMethod propfind = null;
  67. RemoteOperationResult result = null;
  68. mTransferWasRequested = false;
  69. try {
  70. if (!mLocalFile.isDown()) {
  71. /// easy decision
  72. requestForDownload(mLocalFile);
  73. result = new RemoteOperationResult(ResultCode.OK);
  74. } else {
  75. /// local copy in the device -> need to think a bit more before do anything
  76. if (mServerFile == null) {
  77. /// take the duty of check the server for the current state of the file there
  78. propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mLocalFile.getRemotePath()));
  79. int status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT);
  80. boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS;
  81. if (isMultiStatus) {
  82. MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
  83. WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
  84. client.getBaseUri().getPath());
  85. mServerFile = fillOCFile(we);
  86. mServerFile.setLastSyncDateForProperties(System.currentTimeMillis());
  87. } else {
  88. client.exhaustResponse(propfind.getResponseBodyAsStream());
  89. result = new RemoteOperationResult(false, status);
  90. }
  91. }
  92. if (result == null) { // true if the server was not checked, or nothing was wrong with the remote request
  93. /// check changes in server and local file
  94. boolean serverChanged = false;
  95. if (mServerFile.getEtag() != null) {
  96. serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged?
  97. } else {
  98. // server without etags
  99. serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestampAtLastSyncForData());
  100. }
  101. boolean localChanged = (mLocalChangeAlreadyKnown || mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData());
  102. // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads
  103. /// decide action to perform depending upon changes
  104. if (localChanged && serverChanged) {
  105. result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  106. } else if (localChanged) {
  107. if (mSyncFileContents) {
  108. requestForUpload(mLocalFile);
  109. // the local update of file properties will be done by the FileUploader service when the upload finishes
  110. } else {
  111. // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid;
  112. // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect
  113. // that an upload is necessary (for instance, in FileObserverService).
  114. }
  115. result = new RemoteOperationResult(ResultCode.OK);
  116. } else if (serverChanged) {
  117. if (mSyncFileContents) {
  118. requestForDownload(mLocalFile); // local, not server; we won't to keep the value of keepInSync!
  119. // the update of local data will be done later by the FileUploader service when the upload finishes
  120. } else {
  121. // TODO CHECK: is this really useful in some point in the code?
  122. mServerFile.setKeepInSync(mLocalFile.keepInSync());
  123. mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData());
  124. mServerFile.setStoragePath(mLocalFile.getStoragePath());
  125. mServerFile.setParentId(mLocalFile.getParentId());
  126. mStorageManager.saveFile(mServerFile);
  127. }
  128. result = new RemoteOperationResult(ResultCode.OK);
  129. } else {
  130. // nothing changed, nothing to do
  131. result = new RemoteOperationResult(ResultCode.OK);
  132. }
  133. }
  134. }
  135. Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage());
  136. } catch (Exception e) {
  137. result = new RemoteOperationResult(e);
  138. Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage(), result.getException());
  139. } finally {
  140. if (propfind != null)
  141. propfind.releaseConnection();
  142. }
  143. return result;
  144. }
  145. /**
  146. * Requests for an upload to the FileUploader service
  147. *
  148. * @param file OCFile object representing the file to upload
  149. */
  150. private void requestForUpload(OCFile file) {
  151. Intent i = new Intent(mContext, FileUploader.class);
  152. i.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
  153. i.putExtra(FileUploader.KEY_FILE, file);
  154. /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); // doing this we would lose the value of keepInSync in the road, and maybe it's not updated in the database when the FileUploader service gets it!
  155. i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/
  156. i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
  157. i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
  158. mContext.startService(i);
  159. mTransferWasRequested = true;
  160. }
  161. /**
  162. * Requests for a download to the FileDownloader service
  163. *
  164. * @param file OCFile object representing the file to download
  165. */
  166. private void requestForDownload(OCFile file) {
  167. Intent i = new Intent(mContext, FileDownloader.class);
  168. i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
  169. i.putExtra(FileDownloader.EXTRA_FILE, file);
  170. mContext.startService(i);
  171. mTransferWasRequested = true;
  172. }
  173. /**
  174. * Creates and populates a new {@link OCFile} object with the data read from the server.
  175. *
  176. * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder).
  177. * @return New OCFile instance representing the remote resource described by we.
  178. */
  179. private OCFile fillOCFile(WebdavEntry we) {
  180. OCFile file = new OCFile(we.decodedPath());
  181. file.setCreationTimestamp(we.createTimestamp());
  182. file.setFileLength(we.contentLength());
  183. file.setMimetype(we.contentType());
  184. file.setModificationTimestamp(we.modifiedTimestamp());
  185. return file;
  186. }
  187. public boolean transferWasRequested() {
  188. return mTransferWasRequested;
  189. }
  190. public OCFile getLocalFile() {
  191. return mLocalFile;
  192. }
  193. }