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