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