SynchronizeFileOperation.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author David A. Velasco
  5. * @author masensio
  6. * Copyright (C) 2012 Bartek Przybylski
  7. * Copyright (C) 2015 ownCloud Inc.
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License version 2,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. package com.owncloud.android.operations;
  23. import android.accounts.Account;
  24. import android.content.Context;
  25. import android.content.Intent;
  26. import android.os.Parcelable;
  27. import com.owncloud.android.datamodel.OCFile;
  28. import com.owncloud.android.files.services.FileDownloader;
  29. import com.owncloud.android.files.services.FileUploader;
  30. import com.owncloud.android.lib.common.OwnCloudClient;
  31. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  32. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  33. import com.owncloud.android.lib.common.utils.Log_OC;
  34. import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
  35. import com.owncloud.android.lib.resources.files.RemoteFile;
  36. import com.owncloud.android.operations.common.SyncOperation;
  37. import com.owncloud.android.utils.FileStorageUtils;
  38. /**
  39. * Remote operation performing the read of remote file in the ownCloud server.
  40. */
  41. public class SynchronizeFileOperation extends SyncOperation {
  42. private String TAG = SynchronizeFileOperation.class.getSimpleName();
  43. private OCFile mLocalFile;
  44. private String mRemotePath;
  45. private OCFile mServerFile;
  46. private Account mAccount;
  47. private boolean mSyncFileContents;
  48. private Context mContext;
  49. private boolean mTransferWasRequested = false;
  50. /**
  51. * When 'false', uploads to the server are not done; only downloads or conflict detection.
  52. * This is a temporal field.
  53. * TODO Remove when 'folder synchronization' replaces 'folder download'.
  54. */
  55. private boolean mAllowUploads;
  56. /**
  57. * Constructor for "full synchronization mode".
  58. *
  59. * Uses remotePath to retrieve all the data both in local cache and in the remote OC server
  60. * when the operation is executed, instead of reusing {@link OCFile} instances.
  61. *
  62. * Useful for direct synchronization of a single file.
  63. *
  64. * @param
  65. * @param account ownCloud account holding the file.
  66. * @param syncFileContents When 'true', transference of data will be started by the
  67. * operation if needed and no conflict is detected.
  68. * @param context Android context; needed to start transfers.
  69. */
  70. public SynchronizeFileOperation(
  71. String remotePath,
  72. Account account,
  73. boolean syncFileContents,
  74. Context context) {
  75. mRemotePath = remotePath;
  76. mLocalFile = null;
  77. mServerFile = null;
  78. mAccount = account;
  79. mSyncFileContents = syncFileContents;
  80. mContext = context;
  81. mAllowUploads = true;
  82. }
  83. /**
  84. * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or
  85. * from remote OC server.
  86. *
  87. * Useful to include this operation as part of the synchronization of a folder
  88. * (or a full account), avoiding the repetition of fetch operations (both in local database
  89. * or remote server).
  90. *
  91. * At least one of localFile or serverFile MUST NOT BE NULL. If you don't have none of them,
  92. * use the other constructor.
  93. *
  94. * @param localFile Data of file (just) retrieved from local cache/database.
  95. * @param serverFile Data of file (just) retrieved from a remote server. If null,
  96. * will be retrieved from network by the operation when executed.
  97. * @param account ownCloud account holding the file.
  98. * @param syncFileContents When 'true', transference of data will be started by the
  99. * operation if needed and no conflict is detected.
  100. * @param context Android context; needed to start transfers.
  101. */
  102. public SynchronizeFileOperation(
  103. OCFile localFile,
  104. OCFile serverFile,
  105. Account account,
  106. boolean syncFileContents,
  107. Context context) {
  108. mLocalFile = localFile;
  109. mServerFile = serverFile;
  110. if (mLocalFile != null) {
  111. mRemotePath = mLocalFile.getRemotePath();
  112. if (mServerFile != null && !mServerFile.getRemotePath().equals(mRemotePath)) {
  113. throw new IllegalArgumentException("serverFile and localFile do not correspond" +
  114. " to the same OC file");
  115. }
  116. } else if (mServerFile != null) {
  117. mRemotePath = mServerFile.getRemotePath();
  118. } else {
  119. throw new IllegalArgumentException("Both serverFile and localFile are NULL");
  120. }
  121. mAccount = account;
  122. mSyncFileContents = syncFileContents;
  123. mContext = context;
  124. mAllowUploads = true;
  125. }
  126. /**
  127. * Temporal constructor.
  128. *
  129. * Extends the previous one to allow constrained synchronizations where uploads are never
  130. * performed - only downloads or conflict detection.
  131. *
  132. * Do not use unless you are involved in 'folder synchronization' or 'folder download' work
  133. * in progress.
  134. *
  135. * TODO Remove when 'folder synchronization' replaces 'folder download'.
  136. *
  137. * @param localFile Data of file (just) retrieved from local cache/database.
  138. * MUSTN't be null.
  139. * @param serverFile Data of file (just) retrieved from a remote server.
  140. * If null, will be retrieved from network by the operation
  141. * when executed.
  142. * @param account ownCloud account holding the file.
  143. * @param syncFileContents When 'true', transference of data will be started by the
  144. * operation if needed and no conflict is detected.
  145. * @param allowUploads When 'false', uploads to the server are not done;
  146. * only downloads or conflict detection.
  147. * @param context Android context; needed to start transfers.
  148. */
  149. public SynchronizeFileOperation(
  150. OCFile localFile,
  151. OCFile serverFile,
  152. Account account,
  153. boolean syncFileContents,
  154. boolean allowUploads,
  155. Context context) {
  156. this(localFile, serverFile, account, syncFileContents, context);
  157. mAllowUploads = allowUploads;
  158. }
  159. @Override
  160. protected RemoteOperationResult run(OwnCloudClient client) {
  161. RemoteOperationResult result = null;
  162. mTransferWasRequested = false;
  163. if (mLocalFile == null) {
  164. // Get local file from the DB
  165. mLocalFile = getStorageManager().getFileByPath(mRemotePath);
  166. }
  167. if (!mLocalFile.isDown()) {
  168. /// easy decision
  169. requestForDownload(mLocalFile);
  170. result = new RemoteOperationResult(ResultCode.OK);
  171. } else {
  172. /// local copy in the device -> need to think a bit more before do anything
  173. if (mServerFile == null) {
  174. ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mRemotePath);
  175. result = operation.execute(client);
  176. if (result.isSuccess()){
  177. mServerFile = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
  178. mServerFile.setLastSyncDateForProperties(System.currentTimeMillis());
  179. }
  180. }
  181. if (mServerFile != null) {
  182. /// check changes in server and local file
  183. boolean serverChanged = false;
  184. if (mLocalFile.getEtag() == null || mLocalFile.getEtag().length() == 0) {
  185. // file uploaded (null) or downloaded ("") before upgrade to version 1.8.0; check the old condition
  186. serverChanged = mServerFile.getModificationTimestamp() !=
  187. mLocalFile.getModificationTimestampAtLastSyncForData();
  188. } else {
  189. serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag()));
  190. }
  191. boolean localChanged = (
  192. mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()
  193. );
  194. /// decide action to perform depending upon changes
  195. //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
  196. if (localChanged && serverChanged) {
  197. result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  198. getStorageManager().saveConflict(mLocalFile, mServerFile.getEtag());
  199. } else if (localChanged) {
  200. if (mSyncFileContents && mAllowUploads) {
  201. requestForUpload(mLocalFile);
  202. // the local update of file properties will be done by the FileUploader
  203. // service when the upload finishes
  204. } else {
  205. // NOTHING TO DO HERE: updating the properties of the file in the server
  206. // without uploading the contents would be stupid;
  207. // So, an instance of SynchronizeFileOperation created with
  208. // syncFileContents == false is completely useless when we suspect
  209. // that an upload is necessary (for instance, in FileObserverService).
  210. }
  211. result = new RemoteOperationResult(ResultCode.OK);
  212. } else if (serverChanged) {
  213. mLocalFile.setRemoteId(mServerFile.getRemoteId());
  214. if (mSyncFileContents) {
  215. requestForDownload(mLocalFile); // local, not server; we won't to keep
  216. // the value of favorite!
  217. // the update of local data will be done later by the FileUploader
  218. // service when the upload finishes
  219. } else {
  220. // TODO CHECK: is this really useful in some point in the code?
  221. mServerFile.setFavorite(mLocalFile.isFavorite());
  222. mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData());
  223. mServerFile.setStoragePath(mLocalFile.getStoragePath());
  224. mServerFile.setParentId(mLocalFile.getParentId());
  225. mServerFile.setEtag(mLocalFile.getEtag());
  226. getStorageManager().saveFile(mServerFile);
  227. }
  228. result = new RemoteOperationResult(ResultCode.OK);
  229. } else {
  230. // nothing changed, nothing to do
  231. result = new RemoteOperationResult(ResultCode.OK);
  232. }
  233. // safe blanket: sync'ing a not in-conflict file will clean wrong conflict markers in ancestors
  234. if (result.getCode() != ResultCode.SYNC_CONFLICT) {
  235. getStorageManager().saveConflict(mLocalFile, null);
  236. }
  237. }
  238. }
  239. Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() +
  240. ": " + result.getLogMessage());
  241. return result;
  242. }
  243. /**
  244. * Requests for an upload to the FileUploader service
  245. *
  246. * @param file OCFile object representing the file to upload
  247. */
  248. private void requestForUpload(OCFile file) {
  249. Intent i = new Intent(mContext, FileUploader.class);
  250. i.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
  251. i.putExtra(FileUploader.KEY_FILE, file);
  252. /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath);
  253. // doing this we would lose the value of keepInSync in the road, and maybe it's not updated
  254. in the database when the FileUploader service gets it!
  255. i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/
  256. i.putExtra(FileUploader.KEY_UPLOAD_TYPE,
  257. FileUploader.UPLOAD_SINGLE_FILE);
  258. i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
  259. mContext.startService(i);
  260. mTransferWasRequested = true;
  261. }
  262. /**
  263. * Requests for a download to the FileDownloader service
  264. *
  265. * @param file OCFile object representing the file to download
  266. */
  267. private void requestForDownload(OCFile file) {
  268. Intent i = new Intent(mContext, FileDownloader.class);
  269. i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
  270. i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable)file);
  271. mContext.startService(i);
  272. mTransferWasRequested = true;
  273. }
  274. public boolean transferWasRequested() {
  275. return mTransferWasRequested;
  276. }
  277. public OCFile getLocalFile() {
  278. return mLocalFile;
  279. }
  280. }