44713d11d38b0f886b5d37c2aa36e8d399fe0306
[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.io.File;
22 import java.util.ArrayList;
23 import java.util.List;
24
25 import com.owncloud.android.AccountUtils;
26 import com.owncloud.android.datamodel.FileDataStorageManager;
27 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
28 import com.owncloud.android.files.OwnCloudFileObserver;
29 import com.owncloud.android.files.OwnCloudFileObserver.FileObserverStatusListener;
30 import com.owncloud.android.ui.activity.ConflictsResolveActivity;
31
32 import android.accounts.Account;
33 import android.accounts.AccountManager;
34 import android.app.Service;
35 import android.content.BroadcastReceiver;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.database.Cursor;
40 import android.os.Binder;
41 import android.os.IBinder;
42 import android.util.Log;
43
44 public class FileObserverService extends Service implements FileObserverStatusListener {
45
46 public final static String KEY_FILE_CMD = "KEY_FILE_CMD";
47 public final static String KEY_CMD_ARG = "KEY_CMD_ARG";
48
49 public final static int CMD_INIT_OBSERVED_LIST = 1;
50 public final static int CMD_ADD_OBSERVED_FILE = 2;
51 public final static int CMD_DEL_OBSERVED_FILE = 3;
52 public final static int CMD_ADD_DOWNLOADING_FILE = 4;
53
54 private static String TAG = FileObserverService.class.getSimpleName();
55 private static List<OwnCloudFileObserver> mObservers;
56 private static List<DownloadCompletedReceiver> mDownloadReceivers;
57 private static Object mReceiverListLock = new Object();
58 private IBinder mBinder = new LocalBinder();
59
60 public class LocalBinder extends Binder {
61 FileObserverService getService() {
62 return FileObserverService.this;
63 }
64 }
65
66 @Override
67 public IBinder onBind(Intent intent) {
68 return mBinder;
69 }
70
71 @Override
72 public int onStartCommand(Intent intent, int flags, int startId) {
73 // this occurs when system tries to restart
74 // service, so we need to reinitialize observers
75 if (intent == null) {
76 initializeObservedList();
77 return Service.START_STICKY;
78 }
79
80 if (!intent.hasExtra(KEY_FILE_CMD)) {
81 Log.e(TAG, "No KEY_FILE_CMD argument given");
82 return Service.START_STICKY;
83 }
84
85 switch (intent.getIntExtra(KEY_FILE_CMD, -1)) {
86 case CMD_INIT_OBSERVED_LIST:
87 initializeObservedList();
88 break;
89 case CMD_ADD_OBSERVED_FILE:
90 addObservedFile(intent.getStringExtra(KEY_CMD_ARG));
91 break;
92 case CMD_DEL_OBSERVED_FILE:
93 removeObservedFile(intent.getStringExtra(KEY_CMD_ARG));
94 break;
95 case CMD_ADD_DOWNLOADING_FILE:
96 addDownloadingFile(intent.getStringExtra(KEY_CMD_ARG));
97 break;
98 default:
99 Log.wtf(TAG, "Incorrect key given");
100 }
101
102 return Service.START_STICKY;
103 }
104
105 private void initializeObservedList() {
106 if (mObservers != null) return; // nothing to do here
107 mObservers = new ArrayList<OwnCloudFileObserver>();
108 mDownloadReceivers = new ArrayList<DownloadCompletedReceiver>();
109 Cursor c = getContentResolver().query(
110 ProviderTableMeta.CONTENT_URI,
111 null,
112 ProviderTableMeta.FILE_KEEP_IN_SYNC + " = ?",
113 new String[] {String.valueOf(1)},
114 null);
115 if (!c.moveToFirst()) return;
116 AccountManager acm = AccountManager.get(this);
117 Account[] accounts = acm.getAccounts();
118 do {
119 Account account = null;
120 for (Account a : accounts)
121 if (a.name.equals(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ACCOUNT_OWNER)))) {
122 account = a;
123 break;
124 }
125
126 if (account == null) continue;
127 FileDataStorageManager storage =
128 new FileDataStorageManager(account, getContentResolver());
129 if (!storage.fileExists(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH))))
130 continue;
131
132 String path = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH));
133 OwnCloudFileObserver observer =
134 new OwnCloudFileObserver(path, OwnCloudFileObserver.CHANGES_ONLY);
135 observer.setContext(getApplicationContext());
136 observer.setAccount(account);
137 observer.setStorageManager(storage);
138 observer.setOCFile(storage.getFileByPath(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH))));
139 observer.addObserverStatusListener(this);
140 observer.startWatching();
141 mObservers.add(observer);
142 Log.d(TAG, "Started watching file " + path);
143
144 } while (c.moveToNext());
145 c.close();
146 }
147
148 private void addObservedFile(String path) {
149 if (path == null) return;
150 if (mObservers == null) {
151 // this is very rare case when service was killed by system
152 // and observers list was deleted in that procedure
153 initializeObservedList();
154 }
155 boolean duplicate = false;
156 OwnCloudFileObserver observer = null;
157 for (int i = 0; i < mObservers.size(); ++i) {
158 observer = mObservers.get(i);
159 if (observer.getPath().equals(path))
160 duplicate = true;
161 observer.setContext(getApplicationContext());
162 }
163 if (duplicate) return;
164 observer = new OwnCloudFileObserver(path, OwnCloudFileObserver.CHANGES_ONLY);
165 observer.setContext(getApplicationContext());
166 Account account = AccountUtils.getCurrentOwnCloudAccount(getApplicationContext());
167 observer.setAccount(account);
168 FileDataStorageManager storage =
169 new FileDataStorageManager(account, getContentResolver());
170 observer.setStorageManager(storage);
171 observer.setOCFile(storage.getFileByLocalPath(path)); // ISSUE 10 - the fix in FileDetailsFragment to avoid path == null was not enough; it the file was never down before, this sets a NULL OCFile in the observer
172 observer.addObserverStatusListener(this);
173
174 DownloadCompletedReceiver receiver = new DownloadCompletedReceiver(path, observer);
175 registerReceiver(receiver, new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE));
176
177 mObservers.add(observer);
178 Log.d(TAG, "Observer added for path " + path);
179 }
180
181 private void removeObservedFile(String path) {
182 if (path == null) return;
183 if (mObservers == null) {
184 initializeObservedList();
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 Log.d(TAG, "Stopped watching " + path);
192 break;
193 }
194 }
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))) {
262 if ((new File(mPath)).exists()) {
263 // the download could be successful, or not; in both cases, the file could be down, due to a former download or upload
264 context.unregisterReceiver(this);
265 removeReceiverFromList(this);
266 mObserver.startWatching();
267 Log.d(TAG, "Started watching " + mPath);
268 return;
269 } // else - keep waiting for a future retry of the download ;
270 // mObserver.startWatching() won't ever work if the file is not in the device when it's called
271 }
272 }
273
274 @Override
275 public boolean equals(Object o) {
276 if (o instanceof DownloadCompletedReceiver)
277 return mPath.equals(((DownloadCompletedReceiver)o).mPath);
278 return super.equals(o);
279 }
280 }
281 }