2 * ownCloud Android client application
4 * @author David A. Velasco
6 * Copyright (C) 2012 Bartek Przybylski
7 * Copyright (C) 2015 ownCloud Inc.
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.
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.
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/>.
23 package com
.owncloud
.android
.operations
;
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
;
37 import android
.accounts
.Account
;
38 import android
.content
.Context
;
39 import android
.content
.Intent
;
42 * Remote operation performing the read of remote file in the ownCloud server.
45 public class SynchronizeFileOperation
extends SyncOperation
{
47 private String TAG
= SynchronizeFileOperation
.class.getSimpleName();
49 private OCFile mLocalFile
;
50 private String mRemotePath
;
51 private OCFile mServerFile
;
52 private Account mAccount
;
53 private boolean mSyncFileContents
;
54 private Context mContext
;
56 private boolean mTransferWasRequested
= false
;
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'.
63 private boolean mAllowUploads
;
67 * Constructor for "full synchronization mode".
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.
72 * Useful for direct synchronization of a single file.
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.
80 public SynchronizeFileOperation(
83 boolean syncFileContents
,
86 mRemotePath
= remotePath
;
90 mSyncFileContents
= syncFileContents
;
97 * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or
98 * from remote OC server.
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
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.
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.
115 public SynchronizeFileOperation(
119 boolean syncFileContents
,
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");
130 } else if (mServerFile
!= null
) {
131 mRemotePath
= mServerFile
.getRemotePath();
133 throw new IllegalArgumentException("Both serverFile and localFile are NULL");
136 mSyncFileContents
= syncFileContents
;
138 mAllowUploads
= true
;
143 * Temporal constructor.
145 * Extends the previous one to allow constrained synchronizations where uploads are never
146 * performed - only downloads or conflict detection.
148 * Do not use unless you are involved in 'folder synchronization' or 'folder download' work
151 * TODO Remove when 'folder synchronization' replaces 'folder download'.
153 * @param localFile Data of file (just) retrieved from local cache/database.
155 * @param serverFile Data of file (just) retrieved from a remote server.
156 * If null, will be retrieved from network by the operation
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.
165 public SynchronizeFileOperation(
169 boolean syncFileContents
,
170 boolean allowUploads
,
173 this(localFile
, serverFile
, account
, syncFileContents
, context
);
174 mAllowUploads
= allowUploads
;
179 protected RemoteOperationResult
run(OwnCloudClient client
) {
181 RemoteOperationResult result
= null
;
182 mTransferWasRequested
= false
;
184 if (mLocalFile
== null
) {
185 // Get local file from the DB
186 mLocalFile
= getStorageManager().getFileByPath(mRemotePath
);
189 if (!mLocalFile
.isDown()) {
191 requestForDownload(mLocalFile
);
192 result
= new RemoteOperationResult(ResultCode
.OK
);
195 /// local copy in the device -> need to think a bit more before do anything
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());
206 if (mServerFile
!= null
) {
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();
215 serverChanged
= (!mServerFile
.getEtag().equals(mLocalFile
.getEtag()));
217 boolean localChanged
= (
218 mLocalFile
.getLocalModificationTimestamp() > mLocalFile
.getLastSyncDateForData()
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
);
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
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).
239 result
= new RemoteOperationResult(ResultCode
.OK
);
241 } else if (serverChanged
) {
242 mLocalFile
.setRemoteId(mServerFile
.getRemoteId());
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
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
);
259 result
= new RemoteOperationResult(ResultCode
.OK
);
262 // nothing changed, nothing to do
263 result
= new RemoteOperationResult(ResultCode
.OK
);
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
);
274 Log_OC
.i(TAG
, "Synchronizing " + mAccount
.name
+ ", file " + mLocalFile
.getRemotePath() +
275 ": " + result
.getLogMessage());
282 * Requests for an upload to the FileUploader service
284 * @param file OCFile object representing the file to upload
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
;
302 * Requests for a download to the FileDownloader service
304 * @param file OCFile object representing the file to download
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
;
315 public boolean transferWasRequested() {
316 return mTransferWasRequested
;
320 public OCFile
getLocalFile() {