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