sbLinuxFileSystemWatcher.cpp
Go to the documentation of this file.
1 /*
2 //
3 // BEGIN SONGBIRD GPL
4 //
5 // This file is part of the Songbird web player.
6 //
7 // Copyright(c) 2005-2009 POTI, Inc.
8 // http://songbirdnest.com
9 //
10 // This file may be licensed under the terms of of the
11 // GNU General Public License Version 2 (the "GPL").
12 //
13 // Software distributed under the License is distributed
14 // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
15 // express or implied. See the GPL for the specific language
16 // governing rights and limitations.
17 //
18 // You should have received a copy of the GPL along with this
19 // program. If not, go to http://www.gnu.org/licenses/gpl.html
20 // or write to the Free Software Foundation, Inc.,
21 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 // END SONGBIRD GPL
24 //
25 */
26 
28 
29 #include <nsComponentManagerUtils.h>
30 #include <sys/inotify.h>
31 #include <unistd.h>
32 #include <errno.h>
33 #include <string.h>
34 
35 #include <sbDebugUtils.h>
36 
37 typedef sbFileDescMap::value_type sbFileDescPair;
38 typedef sbFileDescMap::const_iterator sbFileDescIter;
39 
45 //------------------------------------------------------------------------------
46 
47 // Inotify callback function
48 static gboolean
49 Inotify_Callback(GIOChannel *source, GIOCondition condition, gpointer data)
50 {
51  sbLinuxFileSystemWatcher *watcher =
52  static_cast<sbLinuxFileSystemWatcher *>(data);
53  if (watcher) {
54  watcher->OnInotifyEvent();
55  }
56 
57  return true;
58 }
59 
60 //------------------------------------------------------------------------------
61 
63 {
64  SB_PRLOG_SETUP(sbLinuxFSWatcher);
65 
66  mIsWatching = PR_FALSE;
67 }
68 
70 {
71  if (mIsWatching) {
72  nsresult SB_UNUSED_IN_RELEASE(rv) = Cleanup();
73  NS_ASSERTION(NS_SUCCEEDED(rv), "ERROR: Could not cleanup inotify!");
74  }
75 }
76 
77 nsresult
79 {
80  // Remove all the inotify file descriptor paths
81  sbFileDescIter descBegin = mFileDescMap.begin();
82  sbFileDescIter descEnd = mFileDescMap.end();
83  sbFileDescIter descNext;
84  for (descNext = descBegin; descNext != descEnd; ++descNext) {
85  inotify_rm_watch(mInotifyFileDesc, descNext->first);
86  }
87 
88  // Close the main inotify file descriptor
89  close(mInotifyFileDesc);
90 
91  // Remove ourselves from the main glib thread.
92  if (mInotifySource) {
93  g_source_remove(mInotifySource);
94  }
95 
96  return NS_OK;
97 }
98 
99 nsresult
100 sbLinuxFileSystemWatcher::AddInotifyHook(const nsAString & aDirPath)
101 {
102  PRUint32 watchFlags =
103  IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF |
104  IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO;
105 
106  LOG("%s: adding inotify hook for [%s]",
107  __PRETTY_FUNCTION__,
108  NS_ConvertUTF16toUTF8(aDirPath).get());
109 
110  int pathFileDesc = inotify_add_watch(mInotifyFileDesc,
111  NS_ConvertUTF16toUTF8(aDirPath).get(),
112  watchFlags);
113  if (pathFileDesc == -1) {
114  #if PR_LOGGING
115  int errnum = errno;
116  char buf[0x1000];
117  char *errorDesc = strerror_r(errnum, buf, sizeof(buf));
118  LOG("%s: could not add inotify watch path [%s], error %d (%s)",
119  NS_ConvertUTF16toUTF8(aDirPath).get(),
120  errnum,
121  errorDesc);
122  #endif /* PR_LOGGING */
123  NS_WARNING("Could not add a inotify watch path!!");
124 
125  // Notify the listener of an invalid path error.
126  nsresult SB_UNUSED_IN_RELEASE(rv) =
128  aDirPath);
129  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
130  "Could not notify listener of INVALID_DIRECTORY!");
131 
132  return NS_ERROR_UNEXPECTED;
133  }
134 
135  mFileDescMap.insert(sbFileDescPair(pathFileDesc,
136  nsString(aDirPath)));
137 
138  return NS_OK;
139 }
140 
141 NS_IMETHODIMP
142 sbLinuxFileSystemWatcher::StopWatching(PRBool aShouldSaveSession)
143 {
144  if (!mIsWatching) {
145  return NS_OK;
146  }
147 
148  nsresult rv = Cleanup();
149  NS_ENSURE_SUCCESS(rv, rv);
150 
151  return sbBaseFileSystemWatcher::StopWatching(aShouldSaveSession);
152 }
153 
154 nsresult
156 {
157  // This method is called when inotify tells us an event has happened.
158  // Read the inotify file-descriptor to find out what changed.
159 
160  // The buffer will have enough room for at least one event.
161  // FWIW, on my system PATH_MAX is 4k
162  char buffer[sizeof(struct inotify_event) + PATH_MAX];
163 
164  PRInt32 n = read(mInotifyFileDesc, buffer, sizeof(buffer));
165  if (n > 0) {
166  int i = 0;
167 
168  // for each buffer
169  while (i < n) {
170  // find the event structure in the buffer
171  struct inotify_event *event = (struct inotify_event *) &buffer[i];
172 
173  // Find the associated path in the map.
174  sbFileDescIter curEventFileDesc = mFileDescMap.find(event->wd);
175  if (curEventFileDesc != mFileDescMap.end()) {
176  TRACE("%s: inotify event for %s length %u",
177  __PRETTY_FUNCTION__,
178  NS_ConvertUTF16toUTF8(curEventFileDesc->second).get(),
179  event->len);
180  // If the |event| has a |len| value, something has changed. Inform
181  // the tree to update at the current path.
182  if (event->len) {
183  mTree->Update(curEventFileDesc->second);
184  }
185 
186  // If the folder was deleted or moved, we want to remove the inotify
187  // hook here. The tree will go ahead and inform us of changes in
188  // |OnChangeFound()|, but only with a native path. That unfortunately
189  // requires a O(n) loop through the map to find the associated file
190  // descriptor. So, to keep things simple - just remove the hook here.
191  if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF) {
192  mFileDescMap.erase(curEventFileDesc->first);
193  inotify_rm_watch(mInotifyFileDesc, curEventFileDesc->first);
194  }
195  }
196  else {
197  NS_ASSERTION(PR_FALSE,
198  "Error: Could not find a file desc for inotify event!");
199  }
200 
201  // Get the next event.
202  i += sizeof(struct inotify_event) + event->len;
203  }
204  }
205 
206  return NS_OK;
207 }
208 
209 //------------------------------------------------------------------------------
210 // sbFileSystemTreeListener
211 
212 NS_IMETHODIMP
213 sbLinuxFileSystemWatcher::OnChangeFound(const nsAString & aChangePath,
214  EChangeType aChangeType)
215 {
216  // Only setup the inotify hooks if this class is currently watching.
217  // Events that were received from a previous session will be called
218  // before |OnTreeReady()|.
219  if (mIsWatching) {
220  // Check to see if |aChangePath| represents a directory. If it does, add
221  // a new inotify hook.
222  nsresult rv;
223  nsCOMPtr<nsILocalFile> curPathFile =
224  do_CreateInstance("@mozilla.org/file/local;1", &rv);
225  NS_ENSURE_SUCCESS(rv, rv);
226 
227  rv = curPathFile->InitWithPath(aChangePath);
228  NS_ENSURE_SUCCESS(rv, rv);
229 
230  PRBool exists;
231  rv = curPathFile->Exists(&exists);
232  NS_ENSURE_SUCCESS(rv, rv);
233 
234  if (exists) {
235  PRBool isDir;
236  rv = curPathFile->IsDirectory(&isDir);
237 
238  NS_ENSURE_SUCCESS(rv, rv);
239 
240  if (isDir) {
241  rv = AddInotifyHook(aChangePath);
242  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not add a inotify hook!");
243  }
244  }
245  }
246 
247  return sbBaseFileSystemWatcher::OnChangeFound(aChangePath, aChangeType);
248 }
249 
250 NS_IMETHODIMP
251 sbLinuxFileSystemWatcher::OnTreeReady(const nsAString & aTreeRootPath,
252  sbStringArray & aDirPathArray)
253 {
254  TRACE("%s: adding root path [%s] and friends (previously watching [%s])",
255  __PRETTY_FUNCTION__,
256  NS_ConvertUTF16toUTF8(aTreeRootPath).get(),
257  NS_ConvertUTF16toUTF8(mWatchPath).get());
258  if (mWatchPath.Equals(EmptyString())) {
259  // If the watch path is empty here, this means that the tree was loaded
260  // from a previous session. Set the watch path now.
261  mWatchPath.Assign(aTreeRootPath);
262  }
263 
264  // Now that the tree has been built, start the inotify file-descriptor.
265  mInotifyFileDesc = inotify_init();
266  NS_ENSURE_TRUE(mInotifyFileDesc != -1, NS_ERROR_UNEXPECTED);
267 
268  // Add the inotify file descriptor to the glib mainloop.
269  // TODO: Check the glib return values.
270  GIOChannel *ioc = g_io_channel_unix_new(mInotifyFileDesc);
271  mInotifySource = g_io_add_watch_full(ioc,
272  G_PRIORITY_DEFAULT,
273  G_IO_IN,
275  this,
276  nsnull);
277  g_io_channel_unref(ioc);
278 
279  // Add the root path, it will not be included in the passed in array.
280 
281  // The tree gurantess that |mWatchPath| exists when this method is called.
282  // However, if inotify fails to set itself up, report an invalid dir error.
283  nsresult rv = AddInotifyHook(mWatchPath);
284  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
285  "Could not add inotify hook for the root watch path!");
286 
287  // Add the other directories that were discovered in the tree build.
288  PRUint32 pathCount = aDirPathArray.Length();
289  for (PRUint32 i = 0; i < pathCount; i++) {
290  rv = AddInotifyHook(aDirPathArray[i]);
291  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
292  "Could not add inotify hook for a directory path!");
293  }
294 
295  mIsWatching = PR_TRUE;
296 
297  rv = mListener->OnWatcherStarted();
298  NS_ENSURE_SUCCESS(rv, rv);
299 
300  return NS_OK;
301 }
302 
nsCOMPtr< sbIFileSystemListener > mListener
nsresult AddInotifyHook(const nsAString &aDirPath)
#define SB_PRLOG_SETUP(x)
Definition: sbDebugUtils.h:115
return NS_OK
#define LOG(args)
nsTArray< nsString > sbStringArray
NS_DECL_ISUPPORTS NS_DECL_SBIFILESYSTEMWATCHER NS_IMETHOD OnChangeFound(const nsAString &aChangePath, EChangeType aChangeType)
sbFileDescMap::const_iterator sbFileDescIter
var event
NS_IMETHOD OnTreeReady(const nsAString &aTreeRootPath, sbStringArray &aDirPathArray)
const unsigned long INVALID_DIRECTORY
nsRefPtr< sbFileSystemTree > mTree
function TRACE(s)
NS_IMETHOD OnChangeFound(const nsAString &aChangePath, EChangeType aChangeType)
sbFileDescMap::value_type sbFileDescPair
static gboolean Inotify_Callback(GIOChannel *source, GIOCondition condition, gpointer data)
observe data
Definition: FeedWriter.js:1329
NS_IMETHOD StopWatching(PRBool aShouldSaveSession)
_getSelectedPageStyle s i
#define SB_UNUSED_IN_RELEASE(decl)
Definition: sbDebugUtils.h:55