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
.files
.services
;
22 import java
.util
.HashMap
;
23 import java
.util
.Iterator
;
26 import com
.owncloud
.android
.MainApp
;
27 import com
.owncloud
.android
.authentication
.AccountUtils
;
28 import com
.owncloud
.android
.datamodel
.OCFile
;
29 import com
.owncloud
.android
.db
.ProviderMeta
.ProviderTableMeta
;
30 import com
.owncloud
.android
.files
.OwnCloudFileObserver
;
31 import com
.owncloud
.android
.files
.OwnCloudFolderObserver
;
32 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
;
33 import com
.owncloud
.android
.utils
.FileStorageUtils
;
34 import com
.owncloud
.android
.utils
.Log_OC
;
37 import android
.accounts
.Account
;
38 import android
.app
.Service
;
39 import android
.content
.BroadcastReceiver
;
40 import android
.content
.Context
;
41 import android
.content
.Intent
;
42 import android
.content
.IntentFilter
;
43 import android
.database
.Cursor
;
44 import android
.os
.IBinder
;
47 * Service keeping a list of {@link FileObserver} instances that watch for local
48 * changes in favorite files (formerly known as kept-in-sync files) and try to
49 * synchronize them with the OC server as soon as possible.
51 * Tries to be alive as long as possible; that is the reason why stopSelf() is
54 * It is expected that the system eventually kills the service when runs low of
55 * memory. To minimize the impact of this, the service always returns
56 * Service.START_STICKY, and the later restart of the service is explicitly
57 * considered in {@link FileObserverService#onStartCommand(Intent, int, int)}.
59 * @author David A. Velasco
61 public class FileObserverService
extends Service
{
63 public final static String MY_NAME
= FileObserverService
.class.getCanonicalName();
64 public final static String ACTION_INIT_OBSERVED_LIST
= MY_NAME
+ ".action.INIT_OBSERVED_LIST";
65 public final static String CMD_ADD_OBSERVED_FILE
= MY_NAME
+ ".action.ADD_OBSERVED_FILE";
66 public final static String CMD_DEL_OBSERVED_FILE
= MY_NAME
+ ".action.DEL_OBSERVED_FILE";
68 public final static String KEY_CMD_ARG_FILE
= "KEY_CMD_ARG_FILE";
69 public final static String KEY_CMD_ARG_ACCOUNT
= "KEY_CMD_ARG_ACCOUNT";
71 private static String TAG
= FileObserverService
.class.getSimpleName();
73 private static Map
<String
, OwnCloudFileObserver
> mObserversMap
;
74 private static Map
<String
, OwnCloudFolderObserver
> mObserversFolderMap
;
75 private static DownloadCompletedReceiver mDownloadReceiver
;
78 * Factory method to create intents that allow to start an
79 * ACTION_INIT_OBSERVED_LIST command.
81 * @param context Android context of the caller component.
82 * @return Intent that starts a command ACTION_INIT_OBSERVED_LIST when
83 * {@link Context#startService(Intent)} is called.
85 public static Intent
makeInitIntent(Context context
) {
86 Intent i
= new Intent(context
, FileObserverService
.class);
87 i
.setAction(ACTION_INIT_OBSERVED_LIST
);
92 * Factory method to create intents that allow to start or stop the
93 * observance of a file.
95 * @param context Android context of the caller component.
96 * @param file OCFile to start or stop to watch.
97 * @param account OC account containing file.
98 * @param watchIt 'True' creates an intent to watch, 'false' an intent to
100 * @return Intent to start or stop the observance of a file through a call
101 * to {@link Context#startService(Intent)}.
103 public static Intent
makeObservedFileIntent(Context context
, OCFile file
, Account account
, boolean watchIt
) {
104 Intent intent
= new Intent(context
, FileObserverService
.class);
105 intent
.setAction(watchIt ? FileObserverService
.CMD_ADD_OBSERVED_FILE
106 : FileObserverService
.CMD_DEL_OBSERVED_FILE
);
107 intent
.putExtra(FileObserverService
.KEY_CMD_ARG_FILE
, file
);
108 intent
.putExtra(FileObserverService
.KEY_CMD_ARG_ACCOUNT
, account
);
113 public void onCreate() {
114 Log_OC
.d(TAG
, "onCreate");
117 mDownloadReceiver
= new DownloadCompletedReceiver();
118 IntentFilter filter
= new IntentFilter();
119 filter
.addAction(FileDownloader
.getDownloadAddedMessage());
120 filter
.addAction(FileDownloader
.getDownloadFinishMessage());
121 registerReceiver(mDownloadReceiver
, filter
);
123 mObserversMap
= new HashMap
<String
, OwnCloudFileObserver
>();
124 mObserversFolderMap
= new HashMap
<String
, OwnCloudFolderObserver
>();
128 public void onLowMemory() {
129 Log_OC
.d(TAG
, "ON LOW MEMORY");
134 public void onDestroy() {
135 Log_OC
.d(TAG
, "onDestroy - FINISHING OBSERVATION");
137 unregisterReceiver(mDownloadReceiver
);
139 Iterator
<OwnCloudFileObserver
> it
= mObserversMap
.values().iterator();
140 while (it
.hasNext()) {
141 it
.next().stopWatching();
143 mObserversMap
.clear();
144 mObserversMap
= null
;
146 Iterator
<OwnCloudFolderObserver
> itOCFolder
= mObserversFolderMap
.values().iterator();
147 while (itOCFolder
.hasNext()) {
148 itOCFolder
.next().stopWatching();
150 mObserversFolderMap
.clear();
151 mObserversFolderMap
= null
;
157 public IBinder
onBind(Intent intent
) {
158 // this service cannot be bound
163 public int onStartCommand(Intent intent
, int flags
, int startId
) {
164 Log_OC
.d(TAG
, "Starting command " + intent
);
166 if (intent
== null
|| ACTION_INIT_OBSERVED_LIST
.equals(intent
.getAction())) {
167 // NULL occurs when system tries to restart the service after its
170 initializeObservedList();
171 return Service
.START_STICKY
;
173 } else if (CMD_ADD_OBSERVED_FILE
.equals(intent
.getAction())) {
174 OCFile file
= (OCFile
) intent
.getParcelableExtra(KEY_CMD_ARG_FILE
);
175 Account account
= (Account
) intent
.getParcelableExtra(KEY_CMD_ARG_ACCOUNT
);
176 addObservedFile(file
, account
);
178 String localPath
= file
.getStoragePath();
179 if (localPath
== null
|| localPath
.length() <= 0) {
180 // file downloading or to be downloaded for the first time
181 localPath
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
);
184 String parentPath
= (new File(localPath
)).getParent();
186 addObservedFolder(parentPath
, account
);
188 } else if (CMD_DEL_OBSERVED_FILE
.equals(intent
.getAction())) {
189 removeObservedFile((OCFile
) intent
.getParcelableExtra(KEY_CMD_ARG_FILE
),
190 (Account
) intent
.getParcelableExtra(KEY_CMD_ARG_ACCOUNT
));
193 Log_OC
.e(TAG
, "Unknown action recieved; ignoring it: " + intent
.getAction());
196 return Service
.START_STICKY
;
200 * Read from the local database the list of files that must to be kept
201 * synchronized and starts file observers to monitor local changes on them
203 private void initializeObservedList() {
204 Log_OC
.d(TAG
, "Loading all kept-in-sync files from database to start watching them");
206 // mObserversMap.clear();
207 // mObserverParentsMap.clear();
209 Cursor cursorOnKeptInSync
= getContentResolver().query(ProviderTableMeta
.CONTENT_URI
, null
,
210 ProviderTableMeta
.FILE_KEEP_IN_SYNC
+ " = ?", new String
[] { String
.valueOf(1) }, null
);
212 if (cursorOnKeptInSync
!= null
) {
214 if (cursorOnKeptInSync
.moveToFirst()) {
216 String localPath
= "";
217 // String remotePath = "";
218 String accountName
= "";
219 Account account
= null
;
221 localPath
= cursorOnKeptInSync
.getString(cursorOnKeptInSync
222 .getColumnIndex(ProviderTableMeta
.FILE_STORAGE_PATH
));
223 accountName
= cursorOnKeptInSync
.getString(cursorOnKeptInSync
224 .getColumnIndex(ProviderTableMeta
.FILE_ACCOUNT_OWNER
));
226 * remotePath = cursorOnKeptInSync.getString(
228 * .getColumnIndex(ProviderTableMeta.FILE_PATH) );
231 account
= new Account(accountName
, MainApp
.getAccountType());
232 if (!AccountUtils
.exists(account
, this) || localPath
== null
|| localPath
.length() <= 0) {
236 OwnCloudFileObserver observer
= mObserversMap
.get(localPath
);
237 if (observer
== null
) {
238 observer
= new OwnCloudFileObserver(localPath
, account
, getApplicationContext(), mHandler
);
239 mObserversMap
.put(localPath
, observer
);
241 // only if being added
242 if (new File(localPath
).exists()) {
243 observer
.startWatching();
244 Log_OC
.d(TAG
, "Started watching file " + localPath
);
248 String parentPath
= (new File(localPath
)).getParent();
249 OwnCloudFolderObserver observerFolder
= mObserversFolderMap
.get(parentPath
);
250 if (observerFolder
== null
) {
251 observerFolder
= new OwnCloudFolderObserver(parentPath
, account
, getApplicationContext());
252 mObserversFolderMap
.put(parentPath
, observerFolder
);
254 if (new File(parentPath
).exists()) {
255 observerFolder
.startWatching();
256 Log_OC
.d(TAG
, "Started watching parent folder " + parentPath
+ "/");
260 } while (cursorOnKeptInSync
.moveToNext());
263 cursorOnKeptInSync
.close();
266 // service does not stopSelf() ; that way it tries to be alive forever
271 * Registers the local copy of a remote file to be observed for local
272 * changes, an automatically updated in the ownCloud server.
274 * This method does NOT perform a {@link SynchronizeFileOperation} over the
277 * @param file Object representing a remote file which local copy must be
279 * @param account OwnCloud account containing file.
281 private void addObservedFile(OCFile file
, Account account
) {
282 Log_OC
.v(TAG
, "Adding a file to be watched");
285 Log_OC
.e(TAG
, "Trying to add a NULL file to observer");
288 if (account
== null
) {
289 Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer");
293 String localPath
= file
.getStoragePath();
294 if (localPath
== null
|| localPath
.length() <= 0) {
295 // file downloading or to be downloaded for the first time
296 localPath
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
);
298 OwnCloudFileObserver observer
= mObserversMap
.get(localPath
);
299 if (observer
== null
) {
300 // / the local file was never registered to observe before
301 observer
= new OwnCloudFileObserver(localPath
, account
, getApplicationContext(), mHandler
);
302 mObserversMap
.put(localPath
, observer
);
303 Log_OC
.d(TAG
, "Observer added for path " + localPath
);
306 observer
.startWatching();
307 Log_OC
.d(TAG
, "Started watching " + localPath
);
309 // else - the observance can't be started on a file not already
311 // mDownloadReceiver will get noticed when the download of the file
317 * Registers the folder to be observed in which there are changes inside any
320 * @param localPath String representing the folder will be observed
321 * @param account OwnCloud account containing file.
323 public void addObservedFolder(String localPath
, Account account
) {
324 Log_OC
.v(TAG
, "Adding a child file to be watched");
326 String parentPath
= (new File(localPath
)).getParent();
327 OwnCloudFolderObserver observerParent
= mObserversFolderMap
.get(parentPath
);
328 if (observerParent
== null
) {
329 observerParent
= new OwnCloudFolderObserver(parentPath
, account
, getApplicationContext());
330 mObserversFolderMap
.put(parentPath
, observerParent
);
331 Log_OC
.d(TAG
, "Observer added for parent folder " + parentPath
+ "/");
333 if (new File(parentPath
).exists()) {
334 observerParent
.startWatching();
335 Log_OC
.d(TAG
, "Started watching parent folder " + parentPath
+ "/");
341 * Unregisters the local copy of a remote file to be observed for local
344 * Starts to watch it, if the file has a local copy to watch.
346 * @param file Object representing a remote file which local copy must be
347 * not observed longer.
348 * @param account OwnCloud account containing file.
350 private void removeObservedFile(OCFile file
, Account account
) {
351 Log_OC
.v(TAG
, "Removing a file from being watched");
354 Log_OC
.e(TAG
, "Trying to remove a NULL file");
357 if (account
== null
) {
358 Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer");
362 String localPath
= file
.getStoragePath();
363 if (localPath
== null
|| localPath
.length() <= 0) {
364 localPath
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
);
367 OwnCloudFileObserver observer
= mObserversMap
.get(localPath
);
368 if (observer
!= null
) {
369 observer
.stopWatching();
370 mObserversMap
.remove(observer
);
371 Log_OC
.d(TAG
, "Stopped watching " + localPath
);
374 Log_OC
.d(TAG
, "No observer to remove for path " + localPath
);
380 * Private receiver listening to events broadcast by the FileDownloader
383 * Starts and stops the observance on registered files when they are being
384 * download, in order to avoid to start unnecessary synchronizations.
386 private class DownloadCompletedReceiver
extends BroadcastReceiver
{
389 public void onReceive(Context context
, Intent intent
) {
390 Log_OC
.d(TAG
, "Received broadcast intent " + intent
);
392 String downloadPath
= intent
.getStringExtra(FileDownloader
.EXTRA_FILE_PATH
);
393 OwnCloudFileObserver observer
= mObserversMap
.get(downloadPath
);
394 if (observer
!= null
) {
395 if (intent
.getAction().equals(FileDownloader
.getDownloadFinishMessage())
396 && new File(downloadPath
).exists()) {
397 // no matter is the download was be successful or not; the
398 // file could be down,
399 // anyway due to a former download or upload
400 observer
.startWatching();
401 Log_OC
.d(TAG
, "Resuming observance of " + downloadPath
);
403 } else if (intent
.getAction().equals(FileDownloader
.getDownloadAddedMessage())) {
404 observer
.stopWatching();
405 Log_OC
.d(TAG
, "Pausing observance of " + downloadPath
);
409 Log_OC
.d(TAG
, "No observer for path " + downloadPath
);