2cae6f2d1b489812364b605acd85fc6478a4c50c
[pub/Android/ownCloud.git] / src / com / owncloud / android / files / services / FileObserverService.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
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 as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
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.files.services;
20
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import com.owncloud.android.AccountUtils;
25 import com.owncloud.android.datamodel.FileDataStorageManager;
26 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
27 import com.owncloud.android.files.OwnCloudFileObserver;
28 import com.owncloud.android.files.OwnCloudFileObserver.FileObserverStatusListener;
29 import com.owncloud.android.ui.activity.ConflictsResolveActivity;
30
31 import android.accounts.Account;
32 import android.accounts.AccountManager;
33 import android.app.Service;
34 import android.content.BroadcastReceiver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.database.Cursor;
39 import android.os.Binder;
40 import android.os.IBinder;
41 import android.util.Log;
42
43 public class FileObserverService extends Service implements FileObserverStatusListener {
44
45 public final static String KEY_FILE_CMD = "KEY_FILE_CMD";
46 public final static String KEY_CMD_ARG = "KEY_CMD_ARG";
47
48 public final static int CMD_INIT_OBSERVED_LIST = 1;
49 public final static int CMD_ADD_OBSERVED_FILE = 2;
50 public final static int CMD_DEL_OBSERVED_FILE = 3;
51 public final static int CMD_ADD_DOWNLOADING_FILE = 4;
52
53 private static String TAG = FileObserverService.class.getSimpleName();
54 private static List<OwnCloudFileObserver> mObservers;
55 private static List<DownloadCompletedReceiver> mDownloadReceivers;
56 private static Object mReceiverListLock = new Object();
57 private IBinder mBinder = new LocalBinder();
58
59 public class LocalBinder extends Binder {
60 FileObserverService getService() {
61 return FileObserverService.this;
62 }
63 }
64
65 @Override
66 public IBinder onBind(Intent intent) {
67 return mBinder;
68 }
69
70 @Override
71 public int onStartCommand(Intent intent, int flags, int startId) {
72 // this occurs when system tries to restart
73 // service, so we need to reinitialize observers
74 if (intent == null) {
75 initializeObservedList();
76 return Service.START_STICKY;
77 }
78
79 if (!intent.hasExtra(KEY_FILE_CMD)) {
80 Log.e(TAG, "No KEY_FILE_CMD argument given");
81 return Service.START_STICKY;
82 }
83
84 switch (intent.getIntExtra(KEY_FILE_CMD, -1)) {
85 case CMD_INIT_OBSERVED_LIST:
86 initializeObservedList();
87 break;
88 case CMD_ADD_OBSERVED_FILE:
89 addObservedFile(intent.getStringExtra(KEY_CMD_ARG));
90 break;
91 case CMD_DEL_OBSERVED_FILE:
92 removeObservedFile(intent.getStringExtra(KEY_CMD_ARG));
93 break;
94 case CMD_ADD_DOWNLOADING_FILE:
95 addDownloadingFile(intent.getStringExtra(KEY_CMD_ARG));
96 break;
97 default:
98 Log.wtf(TAG, "Incorrect key given");
99 }
100
101 return Service.START_STICKY;
102 }
103
104 private void initializeObservedList() {
105 if (mObservers != null) return; // nothing to do here
106 mObservers = new ArrayList<OwnCloudFileObserver>();
107 mDownloadReceivers = new ArrayList<DownloadCompletedReceiver>();
108 Cursor c = getContentResolver().query(
109 ProviderTableMeta.CONTENT_URI,
110 null,
111 ProviderTableMeta.FILE_KEEP_IN_SYNC + " = ?",
112 new String[] {String.valueOf(1)},
113 null);
114 if (!c.moveToFirst()) return;
115 AccountManager acm = AccountManager.get(this);
116 Account[] accounts = acm.getAccounts();
117 do {
118 Account account = null;
119 for (Account a : accounts)
120 if (a.name.equals(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ACCOUNT_OWNER)))) {
121 account = a;
122 break;
123 }
124
125 if (account == null) continue;
126 FileDataStorageManager storage =
127 new FileDataStorageManager(account, getContentResolver());
128 if (!storage.fileExists(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH))))
129 continue;
130
131 String path = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH));
132 OwnCloudFileObserver observer =
133 new OwnCloudFileObserver(path, OwnCloudFileObserver.CHANGES_ONLY);
134 observer.setContext(getApplicationContext());
135 observer.setAccount(account);
136 observer.setStorageManager(storage);
137 observer.setOCFile(storage.getFileByPath(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH))));
138 observer.addObserverStatusListener(this);
139 observer.startWatching();
140 mObservers.add(observer);
141 Log.d(TAG, "Started watching file " + path);
142
143 } while (c.moveToNext());
144 c.close();
145 }
146
147 private void addObservedFile(String path) {
148 if (path == null) return;
149 if (mObservers == null) {
150 // this is very rare case when service was killed by system
151 // and observers list was deleted in that procedure
152 initializeObservedList();
153 }
154 boolean duplicate = false;
155 OwnCloudFileObserver observer = null;
156 for (int i = 0; i < mObservers.size(); ++i) {
157 observer = mObservers.get(i);
158 if (observer.getPath().equals(path))
159 duplicate = true;
160 observer.setContext(getBaseContext());
161 }
162 if (duplicate) return;
163 observer = new OwnCloudFileObserver(path, OwnCloudFileObserver.CHANGES_ONLY);
164 observer.setContext(getBaseContext());
165 Account account = AccountUtils.getCurrentOwnCloudAccount(getBaseContext());
166 observer.setAccount(account);
167 FileDataStorageManager storage =
168 new FileDataStorageManager(account, getContentResolver());
169 observer.setStorageManager(storage);
170 observer.setOCFile(storage.getFileByLocalPath(path));
171 observer.addObserverStatusListener(this);
172
173 DownloadCompletedReceiver receiver = new DownloadCompletedReceiver(path, observer);
174 registerReceiver(receiver, new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE));
175
176 mObservers.add(observer);
177 Log.d(TAG, "Observer added for path " + path);
178 }
179
180 private void removeObservedFile(String path) {
181 if (path == null) return;
182 if (mObservers == null) {
183 initializeObservedList();
184 return; // ISSUE 2: why return? ; the file still has to be removed of the mObservers !
185 }
186 for (int i = 0; i < mObservers.size(); ++i) {
187 OwnCloudFileObserver observer = mObservers.get(i);
188 if (observer.getPath().equals(path)) {
189 observer.stopWatching();
190 mObservers.remove(i);
191 break;
192 }
193 }
194 Log.d(TAG, "Stopped watching " + path);
195 }
196
197 private void addDownloadingFile(String remotePath) {
198 OwnCloudFileObserver observer = null;
199 for (OwnCloudFileObserver o : mObservers) {
200 if (o.getRemotePath().equals(remotePath)) {
201 observer = o;
202 break;
203 }
204 }
205 if (observer == null) {
206 Log.e(TAG, "Couldn't find observer for remote file " + remotePath);
207 return;
208 }
209 observer.stopWatching();
210 DownloadCompletedReceiver dcr = new DownloadCompletedReceiver(observer.getPath(), observer);
211 registerReceiver(dcr, new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE));
212 }
213
214
215 private static void addReceiverToList(DownloadCompletedReceiver r) {
216 synchronized(mReceiverListLock) {
217 mDownloadReceivers.add(r);
218 }
219 }
220
221 private static void removeReceiverFromList(DownloadCompletedReceiver r) {
222 synchronized(mReceiverListLock) {
223 mDownloadReceivers.remove(r);
224 }
225 }
226
227 @Override
228 public void OnObservedFileStatusUpdate(String localPath, String remotePath, Account account, Status status) {
229 switch (status) {
230 case CONFLICT:
231 {
232 // ISSUE 5: if the user is not running the app (this is a service!), this can be very intrusive; a notification should be preferred
233 Intent i = new Intent(getApplicationContext(), ConflictsResolveActivity.class);
234 i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
235 i.putExtra("remotepath", remotePath);
236 i.putExtra("localpath", localPath);
237 i.putExtra("account", account);
238 startActivity(i);
239 break;
240 }
241 case SENDING_TO_UPLOADER:
242 case INCORRECT_MASK:
243 break;
244 default:
245 Log.wtf(TAG, "Unhandled status " + status);
246 }
247 }
248
249 private class DownloadCompletedReceiver extends BroadcastReceiver {
250 String mPath;
251 OwnCloudFileObserver mObserver;
252
253 public DownloadCompletedReceiver(String path, OwnCloudFileObserver observer) {
254 mPath = path;
255 mObserver = observer;
256 addReceiverToList(this);
257 }
258
259 @Override
260 public void onReceive(Context context, Intent intent) {
261 if (mPath.equals(intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH))) { // ISSUE 3: this condition will be false if the download failed; in that case, the download won't ever be retried
262 context.unregisterReceiver(this);
263 removeReceiverFromList(this);
264 mObserver.startWatching();
265 Log.d(TAG, "Started watching " + mPath);
266 return;
267 }
268 }
269
270 @Override
271 public boolean equals(Object o) {
272 if (o instanceof DownloadCompletedReceiver)
273 return mPath.equals(((DownloadCompletedReceiver)o).mPath);
274 return super.equals(o);
275 }
276 }
277 }