6bc3321e93dcfb6f83bb0e2ee6707eeb7dd7a88f
[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 import android.media.MediaScannerConnection;
37
38 /**
39 * Remote operation performing the read of remote file in the ownCloud server.
40 *
41 * @author David A. Velasco
42 * @author masensio
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 /**
60 * Constructor.
61 *
62 * Uses remotePath to retrieve all the data in local cache and remote server when the operation
63 * is executed, instead of reusing {@link OCFile} instances.
64 *
65 * @param
66 * @param account ownCloud account holding the file.
67 * @param syncFileContents When 'true', transference of data will be started by the
68 * operation if needed and no conflict is detected.
69 * @param context Android context; needed to start transfers.
70 */
71 public SynchronizeFileOperation(
72 String remotePath,
73 Account account,
74 boolean syncFileContents,
75 Context context) {
76
77 mRemotePath = remotePath;
78 mLocalFile = null;
79 mServerFile = null;
80 mAccount = account;
81 mSyncFileContents = syncFileContents;
82 mContext = context;
83 }
84
85
86 /**
87 * Constructor allowing to reuse {@link OCFile} instances just queried from cache or network.
88 *
89 * Useful for folder / account synchronizations.
90 *
91 * @param localFile Data of file currently hold in device cache. MUSTN't be null.
92 * @param serverFile Data of file just retrieved from network. If null, will be
93 * retrieved from network by the operation when executed.
94 * @param account ownCloud account holding the file.
95 * @param syncFileContents When 'true', transference of data will be started by the
96 * operation if needed and no conflict is detected.
97 * @param context Android context; needed to start transfers.
98 */
99 public SynchronizeFileOperation(
100 OCFile localFile,
101 OCFile serverFile,
102 Account account,
103 boolean syncFileContents,
104 Context context) {
105
106 mLocalFile = localFile;
107 mServerFile = serverFile;
108 mRemotePath = localFile.getRemotePath();
109 mAccount = account;
110 mSyncFileContents = syncFileContents;
111 mContext = context;
112 }
113
114
115 @Override
116 protected RemoteOperationResult run(OwnCloudClient client) {
117
118 RemoteOperationResult result = null;
119 mTransferWasRequested = false;
120
121 if (mLocalFile == null) {
122 // Get local file from the DB
123 mLocalFile = getStorageManager().getFileByPath(mRemotePath);
124 }
125
126 if (!mLocalFile.isDown()) {
127 /// easy decision
128 requestForDownload(mLocalFile);
129 result = new RemoteOperationResult(ResultCode.OK);
130
131 } else {
132 /// local copy in the device -> need to think a bit more before do anything
133
134 if (mServerFile == null) {
135 ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mRemotePath);
136 result = operation.execute(client);
137 if (result.isSuccess()){
138 mServerFile = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
139 mServerFile.setLastSyncDateForProperties(System.currentTimeMillis());
140 }
141 }
142
143 if (mServerFile != null) {
144
145 /// check changes in server and local file
146 boolean serverChanged = false;
147 /* time for eTag is coming, but not yet
148 if (mServerFile.getEtag() != null) {
149 serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged?
150 } else { */
151 // server without etags
152 serverChanged = (mServerFile.getModificationTimestamp() != mLocalFile.getModificationTimestampAtLastSyncForData());
153 //}
154 boolean localChanged = (mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData());
155 // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads
156
157 /// decide action to perform depending upon changes
158 //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
159 if (localChanged && serverChanged) {
160 result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
161
162 } else if (localChanged) {
163 if (mSyncFileContents) {
164 requestForUpload(mLocalFile);
165 // the local update of file properties will be done by the FileUploader service when the upload finishes
166 } else {
167 // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid;
168 // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect
169 // that an upload is necessary (for instance, in FileObserverService).
170 }
171 result = new RemoteOperationResult(ResultCode.OK);
172
173 } else if (serverChanged) {
174 mLocalFile.setRemoteId(mServerFile.getRemoteId());
175
176 if (mSyncFileContents) {
177 requestForDownload(mLocalFile); // local, not server; we won't to keep the value of keepInSync!
178 // the update of local data will be done later by the FileUploader service when the upload finishes
179 } else {
180 // TODO CHECK: is this really useful in some point in the code?
181 mServerFile.setKeepInSync(mLocalFile.keepInSync());
182 mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData());
183 mServerFile.setStoragePath(mLocalFile.getStoragePath());
184 mServerFile.setParentId(mLocalFile.getParentId());
185 getStorageManager().saveFile(mServerFile);
186
187 }
188 result = new RemoteOperationResult(ResultCode.OK);
189
190 } else {
191 // nothing changed, nothing to do
192 result = new RemoteOperationResult(ResultCode.OK);
193 }
194
195 }
196
197 }
198
199 // trigger MediaScan
200 MediaScannerConnection.scanFile(
201 null,
202 new String[]{mLocalFile.getStoragePath()},
203 null,null);
204
205 Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage());
206
207 return result;
208 }
209
210
211 /**
212 * Requests for an upload to the FileUploader service
213 *
214 * @param file OCFile object representing the file to upload
215 */
216 private void requestForUpload(OCFile file) {
217 Intent i = new Intent(mContext, FileUploader.class);
218 i.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
219 i.putExtra(FileUploader.KEY_FILE, file);
220 /*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!
221 i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/
222 i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
223 i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
224 mContext.startService(i);
225 mTransferWasRequested = true;
226 }
227
228
229 /**
230 * Requests for a download to the FileDownloader service
231 *
232 * @param file OCFile object representing the file to download
233 */
234 private void requestForDownload(OCFile file) {
235 Intent i = new Intent(mContext, FileDownloader.class);
236 i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
237 i.putExtra(FileDownloader.EXTRA_FILE, file);
238 mContext.startService(i);
239 mTransferWasRequested = true;
240 }
241
242
243 public boolean transferWasRequested() {
244 return mTransferWasRequested;
245 }
246
247
248 public OCFile getLocalFile() {
249 return mLocalFile;
250 }
251
252 }