Enforced check of local modification date when synchronizing file contents to avoid...
[pub/Android/ownCloud.git] / src / com / owncloud / android / operations / RemoteOperation.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2013 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17 package com.owncloud.android.operations;
18
19 import java.io.IOException;
20
21 import org.apache.commons.httpclient.Credentials;
22
23 import com.owncloud.android.Log_OC;
24 import com.owncloud.android.authentication.AccountAuthenticator;
25 import com.owncloud.android.network.BearerCredentials;
26 import com.owncloud.android.network.OwnCloudClientUtils;
27 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
28
29 import android.accounts.Account;
30 import android.accounts.AccountManager;
31 import android.accounts.AccountsException;
32 import android.app.Activity;
33 import android.content.Context;
34 import android.os.Handler;
35
36 import eu.alefzero.webdav.WebdavClient;
37
38 /**
39 * Operation which execution involves one or several interactions with an ownCloud server.
40 *
41 * Provides methods to execute the operation both synchronously or asynchronously.
42 *
43 * @author David A. Velasco
44 */
45 public abstract class RemoteOperation implements Runnable {
46
47 private static final String TAG = RemoteOperation.class.getSimpleName();
48
49 /** ownCloud account in the remote ownCloud server to operate */
50 private Account mAccount = null;
51
52 /** Android Application context */
53 private Context mContext = null;
54
55 /** Object to interact with the remote server */
56 private WebdavClient mClient = null;
57
58 /** Callback object to notify about the execution of the remote operation */
59 private OnRemoteOperationListener mListener = null;
60
61 /** Handler to the thread where mListener methods will be called */
62 private Handler mListenerHandler = null;
63
64 /** Activity */
65 private Activity mCallerActivity;
66
67
68 /**
69 * Abstract method to implement the operation in derived classes.
70 */
71 protected abstract RemoteOperationResult run(WebdavClient client);
72
73
74 /**
75 * Synchronously executes the remote operation on the received ownCloud account.
76 *
77 * Do not call this method from the main thread.
78 *
79 * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}.
80 *
81 * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation.
82 * @param context Android context for the component calling the method.
83 * @return Result of the operation.
84 */
85 public final RemoteOperationResult execute(Account account, Context context) {
86 if (account == null)
87 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account");
88 if (context == null)
89 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context");
90 mAccount = account;
91 mContext = context.getApplicationContext();
92 try {
93 mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext);
94 } catch (Exception e) {
95 Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e);
96 return new RemoteOperationResult(e);
97 }
98 return run(mClient);
99 }
100
101
102 /**
103 * Synchronously executes the remote operation
104 *
105 * Do not call this method from the main thread.
106 *
107 * @param client Client object to reach an ownCloud server during the execution of the operation.
108 * @return Result of the operation.
109 */
110 public final RemoteOperationResult execute(WebdavClient client) {
111 if (client == null)
112 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient");
113 mClient = client;
114 return run(client);
115 }
116
117
118 /**
119 * Asynchronously executes the remote operation
120 *
121 * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}.
122 *
123 * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation.
124 * @param context Android context for the component calling the method.
125 * @param listener Listener to be notified about the execution of the operation.
126 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
127 * @return Thread were the remote operation is executed.
128 */
129 public final Thread execute(Account account, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) {
130 if (account == null)
131 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account");
132 if (context == null)
133 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context");
134 mAccount = account;
135 mContext = context.getApplicationContext();
136 mCallerActivity = callerActivity;
137 mClient = null; // the client instance will be created from mAccount and mContext in the runnerThread to create below
138
139 mListener = listener;
140
141 mListenerHandler = listenerHandler;
142
143 Thread runnerThread = new Thread(this);
144 runnerThread.start();
145 return runnerThread;
146 }
147
148
149 /**
150 * Asynchronously executes the remote operation
151 *
152 * @param client Client object to reach an ownCloud server during the execution of the operation.
153 * @param listener Listener to be notified about the execution of the operation.
154 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
155 * @return Thread were the remote operation is executed.
156 */
157 public final Thread execute(WebdavClient client, OnRemoteOperationListener listener, Handler listenerHandler) {
158 if (client == null) {
159 throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient");
160 }
161 mClient = client;
162
163 if (listener == null) {
164 throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result");
165 }
166 mListener = listener;
167
168 if (listenerHandler == null) {
169 throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread");
170 }
171 mListenerHandler = listenerHandler;
172
173 Thread runnerThread = new Thread(this);
174 runnerThread.start();
175 return runnerThread;
176 }
177
178 /**
179 * Synchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient)}
180 *
181 * @param listener Listener to be notified about the execution of the operation.
182 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
183 * @return Thread were the remote operation is executed.
184 */
185 public final RemoteOperationResult retry() {
186 return execute(mClient);
187 }
188
189 /**
190 * Asynchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)}
191 *
192 * @param listener Listener to be notified about the execution of the operation.
193 * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
194 * @return Thread were the remote operation is executed.
195 */
196 public final Thread retry(OnRemoteOperationListener listener, Handler listenerHandler) {
197 return execute(mClient, listener, listenerHandler);
198 }
199
200
201 /**
202 * Asynchronous execution of the operation
203 * started by {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)},
204 * and result posting.
205 *
206 * TODO refactor && clean the code; now it's a mess
207 */
208 @Override
209 public final void run() {
210 RemoteOperationResult result = null;
211 boolean repeat = false;
212 do {
213 try{
214 if (mClient == null) {
215 if (mAccount != null && mContext != null) {
216 if (mCallerActivity != null) {
217 mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext, mCallerActivity);
218 } else {
219 mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext);
220 }
221 } else {
222 throw new IllegalStateException("Trying to run a remote operation asynchronously with no client instance or account");
223 }
224 }
225
226 } catch (IOException e) {
227 Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, new AccountsException("I/O exception while trying to authorize the account", e));
228 result = new RemoteOperationResult(e);
229
230 } catch (AccountsException e) {
231 Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e);
232 result = new RemoteOperationResult(e);
233 }
234
235 if (result == null)
236 result = run(mClient);
237
238 repeat = false;
239 if (mCallerActivity != null && mAccount != null && mContext != null && !result.isSuccess() &&
240 // (result.getCode() == ResultCode.UNAUTHORIZED || (result.isTemporalRedirection() && result.isIdPRedirection()))) {
241 (result.getCode() == ResultCode.UNAUTHORIZED || result.isIdPRedirection())) {
242 /// possible fail due to lack of authorization in an operation performed in foreground
243 Credentials cred = mClient.getCredentials();
244 String ssoSessionCookie = mClient.getSsoSessionCookie();
245 if (cred != null || ssoSessionCookie != null) {
246 /// confirmed : unauthorized operation
247 AccountManager am = AccountManager.get(mContext);
248 boolean bearerAuthorization = (cred != null && cred instanceof BearerCredentials);
249 boolean samlBasedSsoAuthorization = (cred == null && ssoSessionCookie != null);
250 if (bearerAuthorization) {
251 am.invalidateAuthToken(AccountAuthenticator.ACCOUNT_TYPE, ((BearerCredentials)cred).getAccessToken());
252 } else if (samlBasedSsoAuthorization ) {
253 am.invalidateAuthToken(AccountAuthenticator.ACCOUNT_TYPE, ssoSessionCookie);
254 } else {
255 am.clearPassword(mAccount);
256 }
257 mClient = null;
258 repeat = true; // when repeated, the creation of a new OwnCloudClient after erasing the saved credentials will trigger the login activity
259 result = null;
260 }
261 }
262 } while (repeat);
263
264 final RemoteOperationResult resultToSend = result;
265 if (mListenerHandler != null && mListener != null) {
266 mListenerHandler.post(new Runnable() {
267 @Override
268 public void run() {
269 mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend);
270 }
271 });
272 }
273 }
274
275
276 /**
277 * Returns the current client instance to access the remote server.
278 *
279 * @return Current client instance to access the remote server.
280 */
281 public final WebdavClient getClient() {
282 return mClient;
283 }
284
285
286 }