1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 * Copyright (C) 2012-2013 ownCloud Inc.
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.
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.
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/>.
19 package com
.owncloud
.android
.operations
;
21 import org
.apache
.http
.HttpStatus
;
22 import org
.apache
.jackrabbit
.webdav
.DavConstants
;
23 import org
.apache
.jackrabbit
.webdav
.MultiStatus
;
24 import org
.apache
.jackrabbit
.webdav
.client
.methods
.PropFindMethod
;
26 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
;
27 import com
.owncloud
.android
.datamodel
.OCFile
;
28 import com
.owncloud
.android
.files
.services
.FileDownloader
;
29 import com
.owncloud
.android
.files
.services
.FileUploader
;
30 import com
.owncloud
.android
.oc_framework
.network
.webdav
.WebdavClient
;
31 import com
.owncloud
.android
.oc_framework
.network
.webdav
.WebdavEntry
;
32 import com
.owncloud
.android
.oc_framework
.network
.webdav
.WebdavUtils
;
33 import com
.owncloud
.android
.oc_framework
.operations
.RemoteOperation
;
34 import com
.owncloud
.android
.oc_framework
.operations
.RemoteOperationResult
;
35 import com
.owncloud
.android
.oc_framework
.operations
.RemoteOperationResult
.ResultCode
;
36 import com
.owncloud
.android
.utils
.Log_OC
;
38 import android
.accounts
.Account
;
39 import android
.content
.Context
;
40 import android
.content
.Intent
;
43 public class SynchronizeFileOperation
extends RemoteOperation
{
45 private String TAG
= SynchronizeFileOperation
.class.getSimpleName();
46 private static final int SYNC_READ_TIMEOUT
= 10000;
47 private static final int SYNC_CONNECTION_TIMEOUT
= 5000;
49 private OCFile mLocalFile
;
50 private OCFile mServerFile
;
51 private FileDataStorageManager mStorageManager
;
52 private Account mAccount
;
53 private boolean mSyncFileContents
;
54 private Context mContext
;
56 private boolean mTransferWasRequested
= false
;
58 public SynchronizeFileOperation(
60 OCFile serverFile
, // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation
61 FileDataStorageManager storageManager
,
63 boolean syncFileContents
,
66 mLocalFile
= localFile
;
67 mServerFile
= serverFile
;
68 mStorageManager
= storageManager
;
70 mSyncFileContents
= syncFileContents
;
76 protected RemoteOperationResult
run(WebdavClient client
) {
78 PropFindMethod propfind
= null
;
79 RemoteOperationResult result
= null
;
80 mTransferWasRequested
= false
;
82 if (!mLocalFile
.isDown()) {
84 requestForDownload(mLocalFile
);
85 result
= new RemoteOperationResult(ResultCode
.OK
);
88 /// local copy in the device -> need to think a bit more before do anything
90 if (mServerFile
== null
) {
91 /// take the duty of check the server for the current state of the file there
92 propfind
= new PropFindMethod(client
.getBaseUri() + WebdavUtils
.encodePath(mLocalFile
.getRemotePath()),
93 DavConstants
.PROPFIND_ALL_PROP
,
94 DavConstants
.DEPTH_0
);
95 int status
= client
.executeMethod(propfind
, SYNC_READ_TIMEOUT
, SYNC_CONNECTION_TIMEOUT
);
96 boolean isMultiStatus
= status
== HttpStatus
.SC_MULTI_STATUS
;
98 MultiStatus resp
= propfind
.getResponseBodyAsMultiStatus();
99 WebdavEntry we
= new WebdavEntry(resp
.getResponses()[0],
100 client
.getBaseUri().getPath());
101 mServerFile
= fillOCFile(we
);
102 mServerFile
.setLastSyncDateForProperties(System
.currentTimeMillis());
105 client
.exhaustResponse(propfind
.getResponseBodyAsStream());
106 result
= new RemoteOperationResult(false
, status
, propfind
.getResponseHeaders());
110 if (result
== null
) { // true if the server was not checked. nothing was wrong with the remote request
112 /// check changes in server and local file
113 boolean serverChanged
= false
;
114 /* time for eTag is coming, but not yet
115 if (mServerFile.getEtag() != null) {
116 serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged?
118 // server without etags
119 serverChanged
= (mServerFile
.getModificationTimestamp() > mLocalFile
.getModificationTimestampAtLastSyncForData());
121 boolean localChanged
= (mLocalFile
.getLocalModificationTimestamp() > mLocalFile
.getLastSyncDateForData());
122 // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads
124 /// decide action to perform depending upon changes
125 //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
126 if (localChanged
&& serverChanged
) {
127 result
= new RemoteOperationResult(ResultCode
.SYNC_CONFLICT
);
129 } else if (localChanged
) {
130 if (mSyncFileContents
) {
131 requestForUpload(mLocalFile
);
132 // the local update of file properties will be done by the FileUploader service when the upload finishes
134 // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid;
135 // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect
136 // that an upload is necessary (for instance, in FileObserverService).
138 result
= new RemoteOperationResult(ResultCode
.OK
);
140 } else if (serverChanged
) {
141 if (mSyncFileContents
) {
142 requestForDownload(mLocalFile
); // local, not server; we won't to keep the value of keepInSync!
143 // the update of local data will be done later by the FileUploader service when the upload finishes
145 // TODO CHECK: is this really useful in some point in the code?
146 mServerFile
.setKeepInSync(mLocalFile
.keepInSync());
147 mServerFile
.setLastSyncDateForData(mLocalFile
.getLastSyncDateForData());
148 mServerFile
.setStoragePath(mLocalFile
.getStoragePath());
149 mServerFile
.setParentId(mLocalFile
.getParentId());
150 mStorageManager
.saveFile(mServerFile
);
153 result
= new RemoteOperationResult(ResultCode
.OK
);
156 // nothing changed, nothing to do
157 result
= new RemoteOperationResult(ResultCode
.OK
);
164 Log_OC
.i(TAG
, "Synchronizing " + mAccount
.name
+ ", file " + mLocalFile
.getRemotePath() + ": " + result
.getLogMessage());
166 } catch (Exception e
) {
167 result
= new RemoteOperationResult(e
);
168 Log_OC
.e(TAG
, "Synchronizing " + mAccount
.name
+ ", file " + (mLocalFile
!= null ? mLocalFile
.getRemotePath() : "NULL") + ": " + result
.getLogMessage(), result
.getException());
171 if (propfind
!= null
)
172 propfind
.releaseConnection();
179 * Requests for an upload to the FileUploader service
181 * @param file OCFile object representing the file to upload
183 private void requestForUpload(OCFile file
) {
184 Intent i
= new Intent(mContext
, FileUploader
.class);
185 i
.putExtra(FileUploader
.KEY_ACCOUNT
, mAccount
);
186 i
.putExtra(FileUploader
.KEY_FILE
, file
);
187 /*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!
188 i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/
189 i
.putExtra(FileUploader
.KEY_UPLOAD_TYPE
, FileUploader
.UPLOAD_SINGLE_FILE
);
190 i
.putExtra(FileUploader
.KEY_FORCE_OVERWRITE
, true
);
191 mContext
.startService(i
);
192 mTransferWasRequested
= true
;
197 * Requests for a download to the FileDownloader service
199 * @param file OCFile object representing the file to download
201 private void requestForDownload(OCFile file
) {
202 Intent i
= new Intent(mContext
, FileDownloader
.class);
203 i
.putExtra(FileDownloader
.EXTRA_ACCOUNT
, mAccount
);
204 i
.putExtra(FileDownloader
.EXTRA_FILE
, file
);
205 mContext
.startService(i
);
206 mTransferWasRequested
= true
;
211 * Creates and populates a new {@link OCFile} object with the data read from the server.
213 * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder).
214 * @return New OCFile instance representing the remote resource described by we.
216 private OCFile
fillOCFile(WebdavEntry we
) {
217 OCFile file
= new OCFile(we
.decodedPath());
218 file
.setCreationTimestamp(we
.createTimestamp());
219 file
.setFileLength(we
.contentLength());
220 file
.setMimetype(we
.contentType());
221 file
.setModificationTimestamp(we
.modifiedTimestamp());
222 file
.setEtag(we
.etag());
228 public boolean transferWasRequested() {
229 return mTransferWasRequested
;
233 public OCFile
getLocalFile() {