1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012 Bartek Przybylski 
   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. 
   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
; 
  25 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  26 import com
.owncloud
.android
.datamodel
.OCFile
; 
  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
.operations
.RemoteOperationResult
; 
  31 import com
.owncloud
.android
.operations
.RemoteOperationResult
.ResultCode
; 
  32 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
; 
  33 import com
.owncloud
.android
.ui
.activity
.ConflictsResolveActivity
; 
  34 import com
.owncloud
.android
.utils
.FileStorageUtils
; 
  36 import android
.accounts
.Account
; 
  37 import android
.accounts
.AccountManager
; 
  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
.Binder
; 
  45 import android
.os
.IBinder
; 
  46 import android
.util
.Log
; 
  48 public class FileObserverService 
extends Service 
implements FileObserverStatusListener 
{ 
  50     public final static int CMD_INIT_OBSERVED_LIST 
= 1; 
  51     public final static int CMD_ADD_OBSERVED_FILE 
= 2; 
  52     public final static int CMD_DEL_OBSERVED_FILE 
= 3; 
  54     public final static String KEY_FILE_CMD 
= "KEY_FILE_CMD"; 
  55     public final static String KEY_CMD_ARG_FILE 
= "KEY_CMD_ARG_FILE"; 
  56     public final static String KEY_CMD_ARG_ACCOUNT 
= "KEY_CMD_ARG_ACCOUNT"; 
  58     private static String TAG 
= FileObserverService
.class.getSimpleName(); 
  60     private static Map
<String
, OwnCloudFileObserver
> mObserversMap
; 
  61     private static DownloadCompletedReceiverBis mDownloadReceiver
; 
  62     private IBinder mBinder 
= new LocalBinder(); 
  64     public class LocalBinder 
extends Binder 
{ 
  65         FileObserverService 
getService() { 
  66             return FileObserverService
.this; 
  71     public void onCreate() { 
  73         mDownloadReceiver 
= new DownloadCompletedReceiverBis(); 
  74         IntentFilter filter 
= new IntentFilter(); 
  75         filter
.addAction(FileDownloader
.DOWNLOAD_ADDED_MESSAGE
); 
  76         filter
.addAction(FileDownloader
.DOWNLOAD_FINISH_MESSAGE
);         
  77         registerReceiver(mDownloadReceiver
, filter
); 
  79         mObserversMap 
= new HashMap
<String
, OwnCloudFileObserver
>(); 
  80         initializeObservedList(); 
  85     public void onDestroy() { 
  87         unregisterReceiver(mDownloadReceiver
); 
  88         mObserversMap 
= null
;   // TODO study carefully the life cycle of Services to grant the best possible observance 
  93     public IBinder 
onBind(Intent intent
) { 
  98     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
  99         // this occurs when system tries to restart 
 100         // service, so we need to reinitialize observers 
 101         if (intent 
== null
) { 
 102             initializeObservedList(); 
 103             return Service
.START_STICKY
; 
 106         if (!intent
.hasExtra(KEY_FILE_CMD
)) { 
 107             Log
.e(TAG
, "No KEY_FILE_CMD argument given"); 
 108             return Service
.START_STICKY
; 
 111         switch (intent
.getIntExtra(KEY_FILE_CMD
, -1)) { 
 112             case CMD_INIT_OBSERVED_LIST
: 
 113                 initializeObservedList(); 
 115             case CMD_ADD_OBSERVED_FILE
: 
 116                 addObservedFile( (OCFile
)intent
.getParcelableExtra(KEY_CMD_ARG_FILE
),  
 117                                  (Account
)intent
.getParcelableExtra(KEY_CMD_ARG_ACCOUNT
)); 
 119             case CMD_DEL_OBSERVED_FILE
: 
 120                 removeObservedFile( (OCFile
)intent
.getParcelableExtra(KEY_CMD_ARG_FILE
),  
 121                                     (Account
)intent
.getParcelableExtra(KEY_CMD_ARG_ACCOUNT
)); 
 124                 Log
.wtf(TAG
, "Incorrect key given"); 
 127         return Service
.START_STICKY
; 
 132      * Read from the local database the list of files that must to be kept synchronized and  
 133      * starts file observers to monitor local changes on them 
 135     private void initializeObservedList() { 
 136         mObserversMap
.clear(); 
 137         Cursor c 
= getContentResolver().query( 
 138                 ProviderTableMeta
.CONTENT_URI
, 
 140                 ProviderTableMeta
.FILE_KEEP_IN_SYNC 
+ " = ?", 
 141                 new String
[] {String
.valueOf(1)}, 
 143         if (!c
.moveToFirst()) return; 
 144         AccountManager acm 
= AccountManager
.get(this); 
 145         Account
[] accounts 
= acm
.getAccounts(); 
 147             Account account 
= null
; 
 148             for (Account a 
: accounts
) 
 149                 if (a
.name
.equals(c
.getString(c
.getColumnIndex(ProviderTableMeta
.FILE_ACCOUNT_OWNER
)))) { 
 154             if (account 
== null
) continue; 
 155             FileDataStorageManager storage 
= 
 156                     new FileDataStorageManager(account
, getContentResolver()); 
 157             if (!storage
.fileExists(c
.getString(c
.getColumnIndex(ProviderTableMeta
.FILE_PATH
)))) 
 160             String path 
= c
.getString(c
.getColumnIndex(ProviderTableMeta
.FILE_STORAGE_PATH
)); 
 161             OwnCloudFileObserver observer 
= 
 162                     new OwnCloudFileObserver(path
, OwnCloudFileObserver
.CHANGES_ONLY
); 
 163             observer
.setContext(getApplicationContext()); 
 164             observer
.setAccount(account
); 
 165             observer
.setStorageManager(storage
); 
 166             observer
.setOCFile(storage
.getFileByPath(c
.getString(c
.getColumnIndex(ProviderTableMeta
.FILE_PATH
)))); 
 167             observer
.addObserverStatusListener(this); 
 168             mObserversMap
.put(path
, observer
); 
 169             if (new File(path
).exists()) { 
 170                 observer
.startWatching(); 
 171                 Log
.d(TAG
, "Started watching file " + path
); 
 174         } while (c
.moveToNext()); 
 179      * Registers the local copy of a remote file to be observed for local changes, 
 180      * an automatically updated in the ownCloud server. 
 182      * This method does NOT perform a {@link SynchronizeFileOperation} over the file.  
 184      * TODO We are ignoring that, currently, a local file can be linked to different files 
 185      * in ownCloud if it's uploaded several times. That's something pending to update: we  
 186      * will avoid that the same local file is linked to different remote files. 
 188      * @param file      Object representing a remote file which local copy must be observed. 
 189      * @param account   OwnCloud account containing file. 
 191     private void addObservedFile(OCFile file
, Account account
) { 
 193             Log
.e(TAG
, "Trying to add a NULL file to observer"); 
 196         String localPath 
= file
.getStoragePath(); 
 197         if (localPath 
== null 
|| localPath
.length() <= 0) { // file downloading / to be download for the first time 
 198             localPath 
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
); 
 200         OwnCloudFileObserver observer 
= mObserversMap
.get(localPath
); 
 201         if (observer 
== null
) { 
 202             /// the local file was never registered to observe before 
 203             observer 
= new OwnCloudFileObserver(localPath
, OwnCloudFileObserver
.CHANGES_ONLY
); 
 204             //Account account = AccountUtils.getCurrentOwnCloudAccount(getApplicationContext()); 
 205             observer
.setAccount(account
); 
 206             FileDataStorageManager storage 
= 
 207                     new FileDataStorageManager(account
, getContentResolver());  // I don't trust in this resolver's life span... 
 208             observer
.setStorageManager(storage
); 
 209             //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 
 210             observer
.setOCFile(file
); 
 211             observer
.addObserverStatusListener(this); 
 212             observer
.setContext(getApplicationContext()); 
 214             mObserversMap
.put(localPath
, observer
); 
 215             Log
.d(TAG
, "Observer added for path " + localPath
); 
 218                 observer
.startWatching(); 
 219                 Log
.d(TAG
, "Started watching " + localPath
); 
 220             }   // else - the observance can't be started on a file not already down; mDownloadReceiver will get noticed when the download of the file finishes 
 227      * Unregisters the local copy of a remote file to be observed for local changes. 
 229      * Starts to watch it, if the file has a local copy to watch. 
 231      * TODO We are ignoring that, currently, a local file can be linked to different files 
 232      * in ownCloud if it's uploaded several times. That's something pending to update: we  
 233      * will avoid that the same local file is linked to different remote files. 
 235      * @param file      Object representing a remote file which local copy must be not observed longer. 
 236      * @param account   OwnCloud account containing file. 
 238     private void removeObservedFile(OCFile file
, Account account
) { 
 240             Log
.e(TAG
, "Trying to remove a NULL file"); 
 243         String localPath 
= file
.getStoragePath(); 
 244         if (localPath 
== null 
|| localPath
.length() <= 0) { 
 245             localPath 
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
); 
 248         OwnCloudFileObserver observer 
= mObserversMap
.get(localPath
); 
 249         if (observer 
!= null
) { 
 250             observer
.stopWatching(); 
 251             mObserversMap
.remove(observer
); 
 252             Log
.d(TAG
, "Stopped watching " + localPath
); 
 259     public void onObservedFileStatusUpdate(String localPath
, String remotePath
, Account account
, RemoteOperationResult result
) { 
 260         if (!result
.isSuccess()) { 
 261             if (result
.getCode() == ResultCode
.SYNC_CONFLICT
) { 
 262                 // ISSUE 5: if the user is not running the app (this is a service!), this can be very intrusive; a notification should be preferred 
 263                 Intent i 
= new Intent(getApplicationContext(), ConflictsResolveActivity
.class); 
 264                 i
.setFlags(i
.getFlags() | Intent
.FLAG_ACTIVITY_NEW_TASK
); 
 265                 i
.putExtra("remotepath", remotePath
); 
 266                 i
.putExtra("localpath", localPath
); 
 267                 i
.putExtra("account", account
); 
 271                 // TODO send notification to the notification bar? 
 273         } // else, nothing else to do; now it's duty of FileUploader service  
 279      *  Private receiver listening to events broadcast by the FileDownloader service. 
 281      *  Starts and stops the observance on registered files when they are being download, 
 282      *  in order to avoid to start unnecessary synchronizations.  
 284     private class DownloadCompletedReceiverBis 
extends BroadcastReceiver 
{ 
 287         public void onReceive(Context context
, Intent intent
) { 
 288             String downloadPath 
= intent
.getStringExtra(FileDownloader
.EXTRA_FILE_PATH
); 
 289             OwnCloudFileObserver observer 
= mObserversMap
.get(downloadPath
); 
 290             if (observer 
!= null
) { 
 291                 if (intent
.getAction().equals(FileDownloader
.DOWNLOAD_FINISH_MESSAGE
) && 
 292                         new File(downloadPath
).exists()) {  // the download could be successful, or not; in both cases, the file could be down, due to a former download or upload    
 293                     observer
.startWatching(); 
 294                     Log
.d(TAG
, "Watching again " + downloadPath
); 
 296                 } else if (intent
.getAction().equals(FileDownloader
.DOWNLOAD_ADDED_MESSAGE
)) { 
 297                     observer
.stopWatching(); 
 298                     Log
.d(TAG
, "Disabling observance of " + downloadPath
);