SynchronizeFileOperation.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /*
  2. * Nextcloud - Android Client
  3. *
  4. * SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
  5. * SPDX-FileCopyrightText: 2021 Chris Narkiewicz <hello@ezaquarii.com>
  6. * SPDX-FileCopyrightText: 2021 Tobias Kaminsky <tobias@kaminsky.me>
  7. * SPDX-FileCopyrightText: 2016-2018 Andy Scherzinger <info@andy-scherzinger.de>
  8. * SPDX-FileCopyrightText: 2016 ownCloud Inc.
  9. * SPDX-FileCopyrightText: 2013-2016 María Asensio Valverde <masensio@solidgear.es>
  10. * SPDX-FileCopyrightText: 2012 David A. Velasco <dvelasco@solidgear.es>
  11. * SPDX-FileCopyrightText: 2012 Bartek Przybylski <bart.p.pl@gmail.com>
  12. * SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
  13. */
  14. package com.owncloud.android.operations;
  15. import android.content.Context;
  16. import android.text.TextUtils;
  17. import com.nextcloud.client.account.User;
  18. import com.nextcloud.client.jobs.download.FileDownloadHelper;
  19. import com.nextcloud.client.jobs.upload.FileUploadHelper;
  20. import com.nextcloud.client.jobs.upload.FileUploadWorker;
  21. import com.owncloud.android.datamodel.FileDataStorageManager;
  22. import com.owncloud.android.datamodel.OCFile;
  23. import com.owncloud.android.files.services.NameCollisionPolicy;
  24. import com.owncloud.android.lib.common.OwnCloudClient;
  25. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  26. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  27. import com.owncloud.android.lib.common.utils.Log_OC;
  28. import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
  29. import com.owncloud.android.lib.resources.files.model.RemoteFile;
  30. import com.owncloud.android.operations.common.SyncOperation;
  31. import com.owncloud.android.utils.FileStorageUtils;
  32. /**
  33. * Remote operation performing the read of remote file in the ownCloud server.
  34. */
  35. public class SynchronizeFileOperation extends SyncOperation {
  36. private static final String TAG = SynchronizeFileOperation.class.getSimpleName();
  37. private OCFile mLocalFile;
  38. private String mRemotePath;
  39. private OCFile mServerFile;
  40. private User mUser;
  41. private boolean mSyncFileContents;
  42. private Context mContext;
  43. private boolean mTransferWasRequested;
  44. /**
  45. * When 'false', uploads to the server are not done; only downloads or conflict detection. This is a temporal
  46. * field.
  47. * TODO Remove when 'folder synchronization' replaces 'folder download'.
  48. */
  49. private boolean mAllowUploads;
  50. /**
  51. * Constructor for "full synchronization mode".
  52. * <p/>
  53. * Uses remotePath to retrieve all the data both in local cache and in the remote OC server when the operation is
  54. * executed, instead of reusing {@link OCFile} instances.
  55. * <p/>
  56. * Useful for direct synchronization of a single file.
  57. *
  58. * @param remotePath remote path of the file
  59. * @param user Nextcloud user owning the file.
  60. * @param syncFileContents When 'true', transference of data will be started by the operation if needed and no
  61. * conflict is detected.
  62. * @param context Android context; needed to start transfers.
  63. */
  64. public SynchronizeFileOperation(
  65. String remotePath,
  66. User user,
  67. boolean syncFileContents,
  68. Context context,
  69. FileDataStorageManager storageManager) {
  70. super(storageManager);
  71. mRemotePath = remotePath;
  72. mLocalFile = null;
  73. mServerFile = null;
  74. mUser = user;
  75. mSyncFileContents = syncFileContents;
  76. mContext = context;
  77. mAllowUploads = true;
  78. }
  79. /**
  80. * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or from remote OC server.
  81. * <p>
  82. * Useful to include this operation as part of the synchronization of a folder (or a full account), avoiding the
  83. * repetition of fetch operations (both in local database or remote server).
  84. * <p>
  85. * At least one of localFile or serverFile MUST NOT BE NULL. If you don't have none of them, use the other
  86. * constructor.
  87. *
  88. * @param localFile Data of file (just) retrieved from local cache/database.
  89. * @param serverFile Data of file (just) retrieved from a remote server. If null, will be retrieved from
  90. * network by the operation when executed.
  91. * @param user Nextcloud user owning the file.
  92. * @param syncFileContents When 'true', transference of data will be started by the operation if needed and no
  93. * conflict is detected.
  94. * @param context Android context; needed to start transfers.
  95. */
  96. public SynchronizeFileOperation(
  97. OCFile localFile,
  98. OCFile serverFile,
  99. User user,
  100. boolean syncFileContents,
  101. Context context,
  102. FileDataStorageManager storageManager) {
  103. super(storageManager);
  104. mLocalFile = localFile;
  105. mServerFile = serverFile;
  106. if (mLocalFile != null) {
  107. mRemotePath = mLocalFile.getRemotePath();
  108. if (mServerFile != null && !mServerFile.getRemotePath().equals(mRemotePath)) {
  109. throw new IllegalArgumentException("serverFile and localFile do not correspond" +
  110. " to the same OC file");
  111. }
  112. } else if (mServerFile != null) {
  113. mRemotePath = mServerFile.getRemotePath();
  114. } else {
  115. throw new IllegalArgumentException("Both serverFile and localFile are NULL");
  116. }
  117. mUser = user;
  118. mSyncFileContents = syncFileContents;
  119. mContext = context;
  120. mAllowUploads = true;
  121. }
  122. /**
  123. * Temporal constructor.
  124. * <p>
  125. * Extends the previous one to allow constrained synchronizations where uploads are never performed - only downloads
  126. * or conflict detection.
  127. * <p>
  128. * Do not use unless you are involved in 'folder synchronization' or 'folder download' work in progress.
  129. * <p>
  130. * TODO Remove when 'folder synchronization' replaces 'folder download'.
  131. *
  132. * @param localFile Data of file (just) retrieved from local cache/database. MUSTN't be null.
  133. * @param serverFile Data of file (just) retrieved from a remote server. If null, will be retrieved from
  134. * network by the operation when executed.
  135. * @param user Nextcloud user owning the file.
  136. * @param syncFileContents When 'true', transference of data will be started by the operation if needed and no
  137. * conflict is detected.
  138. * @param allowUploads When 'false', uploads to the server are not done; only downloads or conflict detection.
  139. * @param context Android context; needed to start transfers.
  140. */
  141. public SynchronizeFileOperation(
  142. OCFile localFile,
  143. OCFile serverFile,
  144. User user,
  145. boolean syncFileContents,
  146. boolean allowUploads,
  147. Context context,
  148. FileDataStorageManager storageManager) {
  149. this(localFile, serverFile, user, syncFileContents, context, storageManager);
  150. mAllowUploads = allowUploads;
  151. }
  152. @Override
  153. protected RemoteOperationResult run(OwnCloudClient client) {
  154. RemoteOperationResult result = null;
  155. mTransferWasRequested = false;
  156. if (mLocalFile == null) {
  157. // Get local file from the DB
  158. mLocalFile = getStorageManager().getFileByPath(mRemotePath);
  159. }
  160. if (!mLocalFile.isDown()) {
  161. /// easy decision
  162. requestForDownload(mLocalFile);
  163. result = new RemoteOperationResult(ResultCode.OK);
  164. } else {
  165. /// local copy in the device -> need to think a bit more before do anything
  166. if (mServerFile == null) {
  167. ReadFileRemoteOperation operation = new ReadFileRemoteOperation(mRemotePath);
  168. result = operation.execute(client);
  169. if (result.isSuccess()) {
  170. mServerFile = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
  171. mServerFile.setLastSyncDateForProperties(System.currentTimeMillis());
  172. } else if (result.getCode() != ResultCode.FILE_NOT_FOUND) {
  173. return result;
  174. }
  175. }
  176. if (mServerFile != null) {
  177. /// check changes in server and local file
  178. boolean serverChanged;
  179. if (TextUtils.isEmpty(mLocalFile.getEtag())) {
  180. // file uploaded (null) or downloaded ("") before upgrade to version 1.8.0; check the old condition
  181. serverChanged = mServerFile.getModificationTimestamp() !=
  182. mLocalFile.getModificationTimestampAtLastSyncForData();
  183. } else {
  184. serverChanged = !mServerFile.getEtag().equals(mLocalFile.getEtag());
  185. }
  186. boolean localChanged =
  187. mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData();
  188. /// decide action to perform depending upon changes
  189. //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
  190. if (localChanged && serverChanged) {
  191. result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  192. getStorageManager().saveConflict(mLocalFile, mServerFile.getEtag());
  193. } else if (localChanged) {
  194. if (mSyncFileContents && mAllowUploads) {
  195. requestForUpload(mLocalFile);
  196. // the local update of file properties will be done by the FileUploader
  197. // service when the upload finishes
  198. } else {
  199. // NOTHING TO DO HERE: updating the properties of the file in the server
  200. // without uploading the contents would be stupid;
  201. // So, an instance of SynchronizeFileOperation created with
  202. // syncFileContents == false is completely useless when we suspect
  203. // that an upload is necessary (for instance, in FileObserverService).
  204. Log_OC.d(TAG, "Nothing to do here");
  205. }
  206. result = new RemoteOperationResult(ResultCode.OK);
  207. } else if (serverChanged) {
  208. mLocalFile.setRemoteId(mServerFile.getRemoteId());
  209. if (mSyncFileContents) {
  210. requestForDownload(mLocalFile); // local, not server; we won't to keep
  211. // the value of favorite!
  212. // the update of local data will be done later by the FileUploader
  213. // service when the upload finishes
  214. } else {
  215. // TODO CHECK: is this really useful in some point in the code?
  216. mServerFile.setFavorite(mLocalFile.isFavorite());
  217. mServerFile.setHidden(mLocalFile.shouldHide());
  218. mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData());
  219. mServerFile.setStoragePath(mLocalFile.getStoragePath());
  220. mServerFile.setParentId(mLocalFile.getParentId());
  221. mServerFile.setEtag(mLocalFile.getEtag());
  222. getStorageManager().saveFile(mServerFile);
  223. }
  224. result = new RemoteOperationResult(ResultCode.OK);
  225. } else {
  226. // nothing changed, nothing to do
  227. result = new RemoteOperationResult(ResultCode.OK);
  228. }
  229. // safe blanket: sync'ing a not in-conflict file will clean wrong conflict markers in ancestors
  230. if (result.getCode() != ResultCode.SYNC_CONFLICT) {
  231. getStorageManager().saveConflict(mLocalFile, null);
  232. }
  233. } else {
  234. // remote file does not exist, deleting local copy
  235. boolean deleteResult = getStorageManager().removeFile(mLocalFile, true, true);
  236. if (deleteResult) {
  237. result = new RemoteOperationResult(ResultCode.FILE_NOT_FOUND);
  238. } else {
  239. Log_OC.e(TAG, "Removal of local copy failed (remote file does not exist any longer).");
  240. }
  241. }
  242. }
  243. Log_OC.i(TAG, "Synchronizing " + mUser.getAccountName() + ", file " + mLocalFile.getRemotePath() +
  244. ": " + result.getLogMessage());
  245. return result;
  246. }
  247. /**
  248. * Requests for an upload to the FileUploader service
  249. *
  250. * @param file OCFile object representing the file to upload
  251. */
  252. private void requestForUpload(OCFile file) {
  253. FileUploadHelper.Companion.instance().uploadUpdatedFile(
  254. mUser,
  255. new OCFile[]{ file },
  256. FileUploadWorker.LOCAL_BEHAVIOUR_MOVE,
  257. NameCollisionPolicy.OVERWRITE);
  258. mTransferWasRequested = true;
  259. }
  260. private void requestForDownload(OCFile file) {
  261. Log_OC.d("InternalTwoWaySyncWork", "download file: " + file.getFileName());
  262. FileDownloadHelper.Companion.instance().downloadFile(
  263. mUser,
  264. file);
  265. mTransferWasRequested = true;
  266. }
  267. public boolean transferWasRequested() {
  268. return mTransferWasRequested;
  269. }
  270. public OCFile getLocalFile() {
  271. return mLocalFile;
  272. }
  273. }