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
.operations
.SynchronizeFileOperation
;
32 import com
.owncloud
.android
.utils
.FileStorageUtils
;
33 import com
.owncloud
.android
.utils
.Log_OC
;
36 import android
.accounts
.Account
;
37 import android
.app
.Service
;
38 import android
.content
.BroadcastReceiver
;
39 import android
.content
.Context
;
40 import android
.content
.Intent
;
41 import android
.content
.IntentFilter
;
42 import android
.database
.Cursor
;
43 import android
.os
.IBinder
;
46 * Service keeping a list of {@link FileObserver} instances that watch for local changes in
47 * favorite files (formerly known as kept-in-sync files) and try to synchronize them with the
48 * OC server as soon as possible.
50 * Tries to be alive as long as possible; that is the reason why stopSelf() is never called.
52 * It is expected that the system eventually kills the service when runs low of memory.
53 * To minimize the impact of this, the service always returns Service.START_STICKY, and the later
54 * restart of the service is explicitly considered in
55 * {@link FileObserverService#onStartCommand(Intent, int, int)}.
57 * @author David A. Velasco
59 public class FileObserverService
extends Service
{
61 public final static String MY_NAME
= FileObserverService
.class.getCanonicalName();
62 public final static String ACTION_INIT_OBSERVED_LIST
= MY_NAME
+ ".action.INIT_OBSERVED_LIST";
63 public final static String CMD_ADD_OBSERVED_FILE
= MY_NAME
+ ".action.ADD_OBSERVED_FILE";
64 public final static String CMD_DEL_OBSERVED_FILE
= MY_NAME
+ ".action.DEL_OBSERVED_FILE";
66 public final static String KEY_CMD_ARG_FILE
= "KEY_CMD_ARG_FILE";
67 public final static String KEY_CMD_ARG_ACCOUNT
= "KEY_CMD_ARG_ACCOUNT";
69 private static String TAG
= FileObserverService
.class.getSimpleName();
71 private static Map
<String
, OwnCloudFileObserver
> mObserversMap
;
72 private static Map
<String
, OwnCloudFileObserver
> mObserverParentsMap
;
73 private static DownloadCompletedReceiver mDownloadReceiver
;
77 * Factory method to create intents that allow to start an ACTION_INIT_OBSERVED_LIST command.
79 * @param context Android context of the caller component.
80 * @return Intent that starts a command ACTION_INIT_OBSERVED_LIST when
81 * {@link Context#startService(Intent)} is called.
83 public static Intent
makeInitIntent(Context context
) {
84 Intent i
= new Intent(context
, FileObserverService
.class);
85 i
.setAction(ACTION_INIT_OBSERVED_LIST
);
91 * Factory method to create intents that allow to start or stop the observance of a file.
93 * @param context Android context of the caller component.
94 * @param file OCFile to start or stop to watch.
95 * @param account OC account containing file.
96 * @param watchIt 'True' creates an intent to watch, 'false' an intent to stop watching.
97 * @return Intent to start or stop the observance of a file through a call
98 * to {@link Context#startService(Intent)}.
100 public static Intent
makeObservedFileIntent(
101 Context context
, OCFile file
, Account account
, boolean watchIt
) {
102 Intent intent
= new Intent(context
, FileObserverService
.class);
105 FileObserverService
.CMD_ADD_OBSERVED_FILE
107 FileObserverService
.CMD_DEL_OBSERVED_FILE
109 intent
.putExtra(FileObserverService
.KEY_CMD_ARG_FILE
, file
);
110 intent
.putExtra(FileObserverService
.KEY_CMD_ARG_ACCOUNT
, account
);
117 public void onCreate() {
118 Log_OC
.d(TAG
, "onCreate");
121 mDownloadReceiver
= new DownloadCompletedReceiver();
122 IntentFilter filter
= new IntentFilter();
123 filter
.addAction(FileDownloader
.getDownloadAddedMessage());
124 filter
.addAction(FileDownloader
.getDownloadFinishMessage());
125 registerReceiver(mDownloadReceiver
, filter
);
127 mObserversMap
= new HashMap
<String
, OwnCloudFileObserver
>();
128 mObserverParentsMap
= new HashMap
<String
, OwnCloudFileObserver
>();
133 public void onDestroy() {
134 Log_OC
.d(TAG
, "onDestroy - FINISHING OBSERVATION");
136 unregisterReceiver(mDownloadReceiver
);
138 Iterator
<OwnCloudFileObserver
> it
= mObserversMap
.values().iterator();
139 while (it
.hasNext()) {
140 it
.next().stopWatching();
142 mObserversMap
.clear();
143 mObserversMap
= null
;
145 it
= mObserverParentsMap
.values().iterator();
146 while (it
.hasNext()) {
147 it
.next().stopWatching();
149 mObserverParentsMap
.clear();
150 mObserverParentsMap
= 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 process
169 initializeObservedList();
170 return Service
.START_STICKY
;
172 } else if (CMD_ADD_OBSERVED_FILE
.equals(intent
.getAction())) {
174 (OCFile
)intent
.getParcelableExtra(KEY_CMD_ARG_FILE
),
175 (Account
)intent
.getParcelableExtra(KEY_CMD_ARG_ACCOUNT
)
178 } else if (CMD_DEL_OBSERVED_FILE
.equals(intent
.getAction())) {
180 (OCFile
)intent
.getParcelableExtra(KEY_CMD_ARG_FILE
),
181 (Account
)intent
.getParcelableExtra(KEY_CMD_ARG_ACCOUNT
)
185 Log_OC
.e(TAG
, "Unknown action recieved; ignoring it: " + intent
.getAction());
188 return Service
.START_STICKY
;
193 * Read from the local database the list of files that must to be kept synchronized and
194 * starts file observers to monitor local changes on them
196 private void initializeObservedList() {
197 Log_OC
.d(TAG
, "Loading all kept-in-sync files from database to start watching them");
199 //mObserversMap.clear();
200 //mObserverParentsMap.clear();
202 Cursor cursorOnKeptInSync
= getContentResolver().query(
203 ProviderTableMeta
.CONTENT_URI
,
205 ProviderTableMeta
.FILE_KEEP_IN_SYNC
+ " = ?",
206 new String
[] {String
.valueOf(1)},
210 if (cursorOnKeptInSync
!= null
) {
212 if (cursorOnKeptInSync
.moveToFirst()) {
214 String localPath
= "";
215 //String remotePath = "";
216 String accountName
= "";
217 Account account
= null
;
219 localPath
= cursorOnKeptInSync
.getString(
220 cursorOnKeptInSync
.getColumnIndex(ProviderTableMeta
.FILE_STORAGE_PATH
)
222 accountName
= cursorOnKeptInSync
.getString(
223 cursorOnKeptInSync
.getColumnIndex(ProviderTableMeta
.FILE_ACCOUNT_OWNER
)
226 remotePath = cursorOnKeptInSync.getString(
227 cursorOnKeptInSync.getColumnIndex(ProviderTableMeta.FILE_PATH)
231 account
= new Account(accountName
, MainApp
.getAccountType());
232 if (!AccountUtils
.exists(account
, this) ||
233 localPath
== null
|| localPath
.length() <= 0) {
237 OwnCloudFileObserver observer
= mObserversMap
.get(localPath
);
238 if (observer
== null
) {
239 observer
= new OwnCloudFileObserver(
240 localPath
, account
, getApplicationContext()
242 mObserversMap
.put(localPath
, observer
);
244 // only if being added
245 if (new File(localPath
).exists()) {
246 observer
.startWatching();
247 Log_OC
.d(TAG
, "Started watching file " + localPath
);
251 String parentPath
= (new File(localPath
)).getParent();
252 OwnCloudFileObserver observerParent
= mObserverParentsMap
.get(parentPath
);
253 if (observerParent
== null
) {
254 observerParent
= new OwnCloudFileObserver(
255 parentPath
, account
, getApplicationContext()
257 mObserverParentsMap
.put(parentPath
, observer
);
258 if (new File(parentPath
).exists()) {
259 observerParent
.startWatching();
260 Log_OC
.d(TAG
, "Started watching parent folder " + parentPath
);
264 } while (cursorOnKeptInSync
.moveToNext());
267 cursorOnKeptInSync
.close();
270 // service does not stopSelf() ; that way it tries to be alive forever
276 * Registers the local copy of a remote file to be observed for local changes,
277 * an automatically updated in the ownCloud server.
279 * This method does NOT perform a {@link SynchronizeFileOperation} over the file.
281 * @param file Object representing a remote file which local copy must be observed.
282 * @param account OwnCloud account containing file.
284 private void addObservedFile(OCFile file
, Account account
) {
285 Log_OC
.v(TAG
, "Adding a file to be watched");
288 Log_OC
.e(TAG
, "Trying to add a NULL file to observer");
291 if (account
== null
) {
292 Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer");
296 String localPath
= file
.getStoragePath();
297 if (localPath
== null
|| localPath
.length() <= 0) {
298 // file downloading or to be downloaded for the first time
299 localPath
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
);
301 OwnCloudFileObserver observer
= mObserversMap
.get(localPath
);
302 if (observer
== null
) {
303 /// the local file was never registered to observe before
304 observer
= new OwnCloudFileObserver(
305 localPath
, account
, getApplicationContext()
307 mObserversMap
.put(localPath
, observer
);
308 Log_OC
.d(TAG
, "Observer added for path " + localPath
);
311 observer
.startWatching();
312 Log_OC
.d(TAG
, "Started watching " + localPath
);
314 // else - the observance can't be started on a file not already down;
315 // mDownloadReceiver will get noticed when the download of the file finishes
318 String parentPath
= (new File(localPath
)).getParent();
319 OwnCloudFileObserver observerParent
= mObserverParentsMap
.get(parentPath
);
320 if (observerParent
== null
) {
321 observerParent
= new OwnCloudFileObserver(
322 parentPath
, account
, getApplicationContext()
324 mObserverParentsMap
.put(parentPath
, observerParent
);
325 Log_OC
.d(TAG
, "Observer added for parent folder " + localPath
);
328 observerParent
.startWatching();
329 Log_OC
.d(TAG
, "Started watching parent folder " + parentPath
);
337 * Unregisters the local copy of a remote file to be observed for local changes.
339 * Starts to watch it, if the file has a local copy to watch.
341 * @param file Object representing a remote file which local copy must be not observed longer.
342 * @param account OwnCloud account containing file.
344 private void removeObservedFile(OCFile file
, Account account
) {
345 Log_OC
.v(TAG
, "Removing a file from being watched");
348 Log_OC
.e(TAG
, "Trying to remove a NULL file");
351 if (account
== null
) {
352 Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer");
356 String localPath
= file
.getStoragePath();
357 if (localPath
== null
|| localPath
.length() <= 0) {
358 localPath
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
);
361 OwnCloudFileObserver observer
= mObserversMap
.get(localPath
);
362 if (observer
!= null
) {
363 observer
.stopWatching();
364 mObserversMap
.remove(observer
);
365 Log_OC
.d(TAG
, "Stopped watching " + localPath
);
368 Log_OC
.d(TAG
, "No observer to remove for path " + localPath
);
375 * Private receiver listening to events broadcast by the FileDownloader service.
377 * Starts and stops the observance on registered files when they are being download,
378 * in order to avoid to start unnecessary synchronizations.
380 private class DownloadCompletedReceiver
extends BroadcastReceiver
{
383 public void onReceive(Context context
, Intent intent
) {
384 Log_OC
.d(TAG
, "Received broadcast intent " + intent
);
386 String downloadPath
= intent
.getStringExtra(FileDownloader
.EXTRA_FILE_PATH
);
387 OwnCloudFileObserver observer
= mObserversMap
.get(downloadPath
);
388 if (observer
!= null
) {
389 if (intent
.getAction().equals(FileDownloader
.getDownloadFinishMessage()) &&
390 new File(downloadPath
).exists()) {
391 // no matter is the download was be successful or not; the file could be down,
392 // anyway due to a former download or upload
393 observer
.startWatching();
394 Log_OC
.d(TAG
, "Resuming observance of " + downloadPath
);
396 } else if (intent
.getAction().equals(FileDownloader
.getDownloadAddedMessage())) {
397 observer
.stopWatching();
398 Log_OC
.d(TAG
, "Pausing observance of " + downloadPath
);
402 Log_OC
.d(TAG
, "No observer for path " + downloadPath
);