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