1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 * Copyright (C) 2012-2014 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
.services
.observer
;
22 import java
.util
.HashMap
;
23 import java
.util
.Iterator
;
26 import android
.accounts
.Account
;
27 import android
.app
.Service
;
28 import android
.content
.BroadcastReceiver
;
29 import android
.content
.Context
;
30 import android
.content
.Intent
;
31 import android
.content
.IntentFilter
;
32 import android
.database
.Cursor
;
33 import android
.media
.MediaScannerConnection
;
34 import android
.os
.IBinder
;
36 import com
.owncloud
.android
.MainApp
;
37 import com
.owncloud
.android
.authentication
.AccountUtils
;
38 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
;
39 import com
.owncloud
.android
.datamodel
.OCFile
;
40 import com
.owncloud
.android
.db
.ProviderMeta
.ProviderTableMeta
;
41 import com
.owncloud
.android
.files
.services
.FileDownloader
;
42 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
;
43 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
;
44 import com
.owncloud
.android
.utils
.FileStorageUtils
;
48 * Service keeping a list of {@link FolderObserver} instances that watch for local
49 * changes in favorite files (formerly known as kept-in-sync files) and try to
50 * synchronize them with the OC server as soon as possible.
52 * Tries to be alive as long as possible; that is the reason why stopSelf() is
55 * It is expected that the system eventually kills the service when runs low of
56 * memory. To minimize the impact of this, the service always returns
57 * Service.START_STICKY, and the later restart of the service is explicitly
58 * considered in {@link FileObserverService#onStartCommand(Intent, int, int)}.
60 * @author David A. Velasco
62 public class FileObserverService
extends Service
{
64 public final static String MY_NAME
= FileObserverService
.class.getCanonicalName();
65 public final static String ACTION_START_OBSERVE
= MY_NAME
+ ".action.START_OBSERVATION";
66 public final static String ACTION_ADD_OBSERVED_FILE
= MY_NAME
+ ".action.ADD_OBSERVED_FILE";
67 public final static String ACTION_DEL_OBSERVED_FILE
= MY_NAME
+ ".action.DEL_OBSERVED_FILE";
69 private final static String ARG_FILE
= "ARG_FILE";
70 private final static String ARG_ACCOUNT
= "ARG_ACCOUNT";
72 private static String TAG
= FileObserverService
.class.getSimpleName();
74 private Map
<String
, FolderObserver
> mFolderObserversMap
;
75 private DownloadCompletedReceiver mDownloadReceiver
;
78 * Factory method to create intents that allow to start an ACTION_START_OBSERVE command.
80 * @param context Android context of the caller component.
81 * @return Intent that starts a command ACTION_START_OBSERVE when
82 * {@link Context#startService(Intent)} is called.
84 public static Intent
makeInitIntent(Context context
) {
85 Intent i
= new Intent(context
, FileObserverService
.class);
86 i
.setAction(ACTION_START_OBSERVE
);
91 * Factory method to create intents that allow to start or stop the
92 * observance of a file.
94 * @param context Android context of the caller component.
95 * @param file OCFile to start or stop to watch.
96 * @param account OC account containing file.
97 * @param watchIt 'True' creates an intent to watch, 'false' an intent to stop watching.
98 * @return Intent to start or stop the observance of a file through a call
99 * to {@link Context#startService(Intent)}.
101 public static Intent
makeObservedFileIntent(
102 Context context
, OCFile file
, Account account
, boolean watchIt
) {
103 Intent intent
= new Intent(context
, FileObserverService
.class);
104 intent
.setAction(watchIt ? FileObserverService
.ACTION_ADD_OBSERVED_FILE
105 : FileObserverService
.ACTION_DEL_OBSERVED_FILE
);
106 intent
.putExtra(FileObserverService
.ARG_FILE
, file
);
107 intent
.putExtra(FileObserverService
.ARG_ACCOUNT
, account
);
112 * Initialize the service.
115 public void onCreate() {
116 Log_OC
.d(TAG
, "onCreate");
119 mDownloadReceiver
= new DownloadCompletedReceiver();
120 IntentFilter filter
= new IntentFilter();
121 filter
.addAction(FileDownloader
.getDownloadAddedMessage());
122 filter
.addAction(FileDownloader
.getDownloadFinishMessage());
123 registerReceiver(mDownloadReceiver
, filter
);
125 mFolderObserversMap
= new HashMap
<String
, FolderObserver
>();
132 public void onDestroy() {
133 Log_OC
.d(TAG
, "onDestroy - finishing observation of favorite files");
135 unregisterReceiver(mDownloadReceiver
);
137 Iterator
<FolderObserver
> itOCFolder
= mFolderObserversMap
.values().iterator();
138 while (itOCFolder
.hasNext()) {
139 itOCFolder
.next().stopWatching();
141 mFolderObserversMap
.clear();
142 mFolderObserversMap
= null
;
148 * This service cannot be bound.
151 public IBinder
onBind(Intent intent
) {
156 * Handles requests to:
157 * - (re)start watching (ACTION_START_OBSERVE)
158 * - add an {@link OCFile} to be watched (ATION_ADD_OBSERVED_FILE)
159 * - stop observing an {@link OCFile} (ACTION_DEL_OBSERVED_FILE)
162 public int onStartCommand(Intent intent
, int flags
, int startId
) {
163 Log_OC
.d(TAG
, "Starting command " + intent
);
165 if (intent
== null
|| ACTION_START_OBSERVE
.equals(intent
.getAction())) {
166 // NULL occurs when system tries to restart the service after its
167 // process was killed
169 return Service
.START_STICKY
;
171 } else if (ACTION_ADD_OBSERVED_FILE
.equals(intent
.getAction())) {
172 OCFile file
= (OCFile
) intent
.getParcelableExtra(ARG_FILE
);
173 Account account
= (Account
) intent
.getParcelableExtra(ARG_ACCOUNT
);
174 addObservedFile(file
, account
);
176 } else if (ACTION_DEL_OBSERVED_FILE
.equals(intent
.getAction())) {
177 removeObservedFile((OCFile
) intent
.getParcelableExtra(ARG_FILE
),
178 (Account
) intent
.getParcelableExtra(ARG_ACCOUNT
));
181 Log_OC
.e(TAG
, "Unknown action recieved; ignoring it: " + intent
.getAction());
184 return Service
.START_STICKY
;
189 * Read from the local database the list of files that must to be kept
190 * synchronized and starts observers to monitor local changes on them.
192 * Updates the list of currently observed files if called multiple times.
194 private void startObservation() {
195 Log_OC
.d(TAG
, "Loading all kept-in-sync files from database to start watching them");
197 // query for any favorite file in any OC account
198 Cursor cursorOnKeptInSync
= getContentResolver().query(
199 ProviderTableMeta
.CONTENT_URI
,
201 ProviderTableMeta
.FILE_KEEP_IN_SYNC
+ " = ?",
202 new String
[] { String
.valueOf(1) },
206 if (cursorOnKeptInSync
!= null
) {
208 if (cursorOnKeptInSync
.moveToFirst()) {
210 String localPath
= "";
211 String accountName
= "";
212 Account account
= null
;
214 localPath
= cursorOnKeptInSync
.getString(cursorOnKeptInSync
215 .getColumnIndex(ProviderTableMeta
.FILE_STORAGE_PATH
));
216 accountName
= cursorOnKeptInSync
.getString(cursorOnKeptInSync
217 .getColumnIndex(ProviderTableMeta
.FILE_ACCOUNT_OWNER
));
219 account
= new Account(accountName
, MainApp
.getAccountType());
220 if (!AccountUtils
.exists(account
, this) || localPath
== null
|| localPath
.length() <= 0) {
224 addObservedFile(localPath
, account
);
226 } while (cursorOnKeptInSync
.moveToNext());
229 cursorOnKeptInSync
.close();
232 // service does not stopSelf() ; that way it tries to be alive forever
238 * Registers the local copy of a remote file to be observed for local
239 * changes, an automatically updated in the ownCloud server.
241 * This method does NOT perform a {@link SynchronizeFileOperation} over the
244 * @param file Object representing a remote file which local copy must be observed.
245 * @param account OwnCloud account containing file.
247 private void addObservedFile(OCFile file
, Account account
) {
248 Log_OC
.v(TAG
, "Adding a file to be watched");
251 Log_OC
.e(TAG
, "Trying to add a NULL file to observer");
254 if (account
== null
) {
255 Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer");
259 String localPath
= file
.getStoragePath();
260 if (localPath
== null
|| localPath
.length() <= 0) {
261 // file downloading or to be downloaded for the first time
262 localPath
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
);
265 addObservedFile(localPath
, account
);
273 * Registers a local file to be observed for changes.
275 * @param localPath Absolute path in the local file system to the file to be observed.
276 * @param account OwnCloud account associated to the local file.
278 private void addObservedFile(String localPath
, Account account
) {
279 File file
= new File(localPath
);
280 String parentPath
= file
.getParent();
281 FolderObserver observer
= mFolderObserversMap
.get(parentPath
);
282 if (observer
== null
) {
283 observer
= new FolderObserver(parentPath
, account
, getApplicationContext());
284 mFolderObserversMap
.put(parentPath
, observer
);
285 Log_OC
.d(TAG
, "Observer added for parent folder " + parentPath
+ "/");
288 observer
.startWatching(file
.getName());
289 Log_OC
.d(TAG
, "Added " + localPath
+ " to list of observed children");
294 * Unregisters the local copy of a remote file to be observed for local changes.
296 * @param file Object representing a remote file which local copy must be not
298 * @param account OwnCloud account containing file.
300 private void removeObservedFile(OCFile file
, Account account
) {
301 Log_OC
.v(TAG
, "Removing a file from being watched");
304 Log_OC
.e(TAG
, "Trying to remove a NULL file");
307 if (account
== null
) {
308 Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer");
312 String localPath
= file
.getStoragePath();
313 if (localPath
== null
|| localPath
.length() <= 0) {
314 localPath
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
);
317 removeObservedFile(localPath
);
322 * Unregisters a local file from being observed for changes.
324 * @param localPath Absolute path in the local file system to the target file.
326 private void removeObservedFile(String localPath
) {
327 File file
= new File(localPath
);
328 String parentPath
= file
.getParent();
329 FolderObserver observer
= mFolderObserversMap
.get(parentPath
);
330 if (observer
!= null
) {
331 observer
.stopWatching(file
.getName());
332 if (observer
.isEmpty()) {
333 mFolderObserversMap
.remove(parentPath
);
334 Log_OC
.d(TAG
, "Observer removed for parent folder " + parentPath
+ "/");
338 Log_OC
.d(TAG
, "No observer to remove for path " + localPath
);
344 * Private receiver listening to events broadcasted by the {@link FileDownloader} service.
346 * Pauses and resumes the observance on registered files while being download,
347 * in order to avoid to unnecessary synchronizations.
349 private class DownloadCompletedReceiver
extends BroadcastReceiver
{
352 public void onReceive(Context context
, Intent intent
) {
353 Log_OC
.d(TAG
, "Received broadcast intent " + intent
);
355 File downloadedFile
= new File(intent
.getStringExtra(FileDownloader
.EXTRA_FILE_PATH
));
356 String parentPath
= downloadedFile
.getParent();
357 FolderObserver observer
= mFolderObserversMap
.get(parentPath
);
358 if (observer
!= null
) {
359 if (intent
.getAction().equals(FileDownloader
.getDownloadFinishMessage())
360 && downloadedFile
.exists()) {
361 // no matter if the download was successful or not; the
362 // file could be down anyway due to a former download or upload
363 observer
.startWatching(downloadedFile
.getName());
364 Log_OC
.d(TAG
, "Resuming observance of " + downloadedFile
.getAbsolutePath());
366 } else if (intent
.getAction().equals(FileDownloader
.getDownloadAddedMessage())) {
367 observer
.stopWatching(downloadedFile
.getName());
368 Log_OC
.d(TAG
, "Pausing observance of " + downloadedFile
.getAbsolutePath());
373 if (downloadedFile
.exists()){
374 Log_OC
.d("mediaScan", "mediaScan : " + downloadedFile
.getAbsolutePath());
375 MediaScannerConnection
.scanFile(getApplicationContext(),
376 new String
[]{downloadedFile
.getAbsolutePath()}, null
, null
);
379 Log_OC
.d(TAG
, "No observer for path " + downloadedFile
.getAbsolutePath());