2  *   ownCloud Android client application 
   4  *   @author David A. Velasco 
   5  *   Copyright (C) 2015 ownCloud Inc. 
   7  *   This program is free software: you can redistribute it and/or modify 
   8  *   it under the terms of the GNU General Public License version 2, 
   9  *   as published by the Free Software Foundation. 
  11  *   This program is distributed in the hope that it will be useful, 
  12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  14  *   GNU General Public License for more details. 
  16  *   You should have received a copy of the GNU General Public License 
  17  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  21 package com
.owncloud
.android
.services
.observer
; 
  24 import java
.util
.HashMap
; 
  27 import android
.accounts
.Account
; 
  28 import android
.content
.Context
; 
  29 import android
.content
.Intent
; 
  30 import android
.os
.FileObserver
; 
  32 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  33 import com
.owncloud
.android
.datamodel
.OCFile
; 
  34 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  35 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  36 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  37 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
; 
  38 import com
.owncloud
.android
.ui
.activity
.ConflictsResolveActivity
; 
  41  * Observer watching a folder to request the synchronization of kept-in-sync files 
  44  * Takes into account two possible update cases: 
  45  *  - an editor directly updates the file; 
  46  *  - an editor works on a temporary file, and later replaces the kept-in-sync file with the 
  49  *  The second case requires to monitor the folder parent of the files, since a direct  
  50  *  {@link FileObserver} on it will not receive more events after the file is deleted to 
  53 public class FolderObserver 
extends FileObserver 
{ 
  55     private static String TAG 
= FolderObserver
.class.getSimpleName(); 
  57     private static int UPDATE_MASK 
= ( 
  58             FileObserver
.ATTRIB 
| FileObserver
.MODIFY 
|  
  59             FileObserver
.MOVED_TO 
| FileObserver
.CLOSE_WRITE
 
  62     private static int IN_IGNORE 
= 32768; 
  64     private static int ALL_EVENTS_EVEN_THOSE_NOT_DOCUMENTED = 0x7fffffff;   // NEVER use 0xffffffff 
  68     private Account mAccount
; 
  69     private Context mContext
; 
  70     private Map
<String
, Boolean
> mObservedChildren
; 
  75      * Initializes the observer to receive events about the update of the passed folder, and 
  78      * @param path          Absolute path to the local folder to watch. 
  79      * @param account       OwnCloud account associated to the folder. 
  80      * @param context       Used to start an operation to synchronize the file, when needed.     
  82     public FolderObserver(String path
, Account account
, Context context
) { 
  83         super(path
, UPDATE_MASK
); 
  86             throw new IllegalArgumentException("NULL path argument received"); 
  88             throw new IllegalArgumentException("NULL account argument received"); 
  90             throw new IllegalArgumentException("NULL context argument received"); 
  95         mObservedChildren 
= new HashMap
<String
, Boolean
>(); 
 100      * Receives and processes events about updates of the monitor folder and its children files. 
 102      * @param event     Kind of event occurred. 
 103      * @param path      Relative path of the file referred by the event. 
 106     public void onEvent(int event
, String path
) { 
 107         Log_OC
.d(TAG
, "Got event " + event 
+ " on FOLDER " + mPath 
+ " about " 
 108                 + ((path 
!= null
) ? path 
: "")); 
 110         boolean shouldSynchronize 
= false
; 
 111         synchronized(mObservedChildren
) { 
 112             if (path 
!= null 
&& path
.length() > 0 && mObservedChildren
.containsKey(path
)) { 
 114                 if (    ((event 
& FileObserver
.MODIFY
) != 0) || 
 115                         ((event 
& FileObserver
.ATTRIB
) != 0) || 
 116                         ((event 
& FileObserver
.MOVED_TO
) != 0) ) { 
 118                     if (mObservedChildren
.get(path
) != true
) { 
 119                         mObservedChildren
.put(path
, Boolean
.valueOf(true
)); 
 123                 if ((event 
& FileObserver
.CLOSE_WRITE
) != 0 && mObservedChildren
.get(path
)) { 
 124                     mObservedChildren
.put(path
, Boolean
.valueOf(false
)); 
 125                     shouldSynchronize 
= true
; 
 129         if (shouldSynchronize
) { 
 130             startSyncOperation(path
); 
 133         if ((event 
& IN_IGNORE
) != 0 && 
 134                 (path 
== null 
|| path
.length() == 0)) { 
 135             Log_OC
.d(TAG
, "Stopping the observance on " + mPath
); 
 142      * Adds a child file to the list of files observed by the folder observer. 
 144      * @param fileName         Name of a file inside the observed folder.  
 146     public void startWatching(String fileName
) { 
 147         synchronized (mObservedChildren
) { 
 148             if (!mObservedChildren
.containsKey(fileName
)) { 
 149                 mObservedChildren
.put(fileName
, Boolean
.valueOf(false
)); 
 153         if (new File(mPath
).exists()) { 
 155             Log_OC
.d(TAG
, "Started watching parent folder " + mPath 
+ "/"); 
 157         // else - the observance can't be started on a file not existing; 
 162      * Removes a child file from the list of files observed by the folder observer. 
 164      * @param fileName         Name of a file inside the observed folder.  
 166     public void stopWatching(String fileName
) { 
 167         synchronized (mObservedChildren
) { 
 168             mObservedChildren
.remove(fileName
); 
 169             if (mObservedChildren
.isEmpty()) { 
 171                 Log_OC
.d(TAG
, "Stopped watching parent folder " + mPath 
+ "/"); 
 177      * @return      'True' when the folder is not watching any file inside. 
 179     public boolean isEmpty() { 
 180         synchronized (mObservedChildren
) { 
 181             return mObservedChildren
.isEmpty(); 
 187      * Triggers an operation to synchronize the contents of a file inside the observed folder with 
 188      * its remote counterpart in the associated ownCloud account. 
 190      * @param fileName          Name of a file inside the watched folder. 
 192     private void startSyncOperation(String fileName
) { 
 193         FileDataStorageManager storageManager 
=  
 194                 new FileDataStorageManager(mAccount
, mContext
.getContentResolver()); 
 195         // a fresh object is needed; many things could have occurred to the file 
 196         // since it was registered to observe again, assuming that local files 
 197         // are linked to a remote file AT MOST, SOMETHING TO BE DONE; 
 198         OCFile file 
= storageManager
.getFileByLocalPath(mPath 
+ File
.separator 
+ fileName
); 
 199         SynchronizeFileOperation sfo 
=  
 200                 new SynchronizeFileOperation(file
, null
, mAccount
, true
, mContext
); 
 201         RemoteOperationResult result 
= sfo
.execute(storageManager
, mContext
); 
 202         if (result
.getCode() == ResultCode
.SYNC_CONFLICT
) { 
 203             // ISSUE 5: if the user is not running the app (this is a service!), 
 204             // this can be very intrusive; a notification should be preferred 
 205             Intent i 
= new Intent(mContext
, ConflictsResolveActivity
.class); 
 206             i
.setFlags(i
.getFlags() | Intent
.FLAG_ACTIVITY_NEW_TASK
); 
 207             i
.putExtra(ConflictsResolveActivity
.EXTRA_FILE
, file
); 
 208             i
.putExtra(ConflictsResolveActivity
.EXTRA_ACCOUNT
, mAccount
); 
 209             mContext
.startActivity(i
); 
 211         // TODO save other errors in some point where the user can inspect them later; 
 212         // or maybe just toast them; 
 213         // or nothing, very strange fails