sbWin32FileSystemWatcher.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 <nsServiceManagerUtils.h>
31 #include <nsIObserverService.h>
32 #include <nsMemory.h>
33 #include <nsThreadUtils.h>
34 #include <nsAutoLock.h>
35 #include <sbStringUtils.h>
36 #include <sbDebugUtils.h>
37 
38 #define BUFFER_LEN 1024 * 64
39 
45 //------------------------------------------------------------------------------
46 // Async callback for ReadDirectoryChangesW()
47 
48 VOID CALLBACK
49 ReadDirectoryChangesWCallbackRoutine(__in DWORD dwErrorCode,
50  __in DWORD dwNumberOfBytesTransfered,
51  __in LPOVERLAPPED lpOverlapped)
52 {
53 
54  sbWin32FileSystemWatcher *watcher =
55  (sbWin32FileSystemWatcher *)lpOverlapped->hEvent;
56  if (!watcher) {
57  LOG("%s: no watcher!", __FUNCTION__);
58  return;
59  }
60 
61  if (FAILED(HRESULT_FROM_WIN32(dwErrorCode))) {
62  return;
63  }
64 
65  // Don't bother processing if no bytes were transfered.
66  if (dwNumberOfBytesTransfered == 0) {
67  return;
68  }
69 
70  // Extract the event paths
71  FILE_NOTIFY_INFORMATION *fileInfo =
72  (FILE_NOTIFY_INFORMATION *)watcher->GetBuffer();
73  if (fileInfo) {
74  {
75  TRACE("%s: found changes in %s", __FUNCTION__, fileInfo->FileName);
76  nsAutoLock lock(watcher->GetEventPathsSetLock());
77 
78  while (PR_TRUE) {
79  // Push in the event path into the queue
80  nsString curEventPath;
81  curEventPath.Assign(fileInfo->FileName,
82  (fileInfo->FileNameLength / sizeof(WCHAR)));
83 
84  watcher->GetEventPathsSet()->insert(curEventPath);
85 
86  if (fileInfo->NextEntryOffset == 0) {
87  break;
88  }
89  fileInfo =
90  (FILE_NOTIFY_INFORMATION *)((char *)fileInfo + fileInfo->NextEntryOffset);
91  }
92  }
93  }
94 
95  watcher->WatchNextChange();
96 }
97 
98 //------------------------------------------------------------------------------
99 // Background win32 thread to toggle the event chain
100 
101 DWORD WINAPI BackgroundThreadProc(void *p)
102 {
104  if (!watcher) {
105  NS_WARNING("Could not get sbWin32FileSystemWatcher context in thread!");
106  return 0;
107  }
108 
109  while (watcher->GetShouldRunThread()) {
110  // If the thread was just started, the event chain needs to be started for
111  // the first time.
112  if (!watcher->GetIsThreadRunning()) {
113  watcher->WatchNextChange();
114  watcher->SetIsThreadRunning(PR_TRUE);
115  }
116 
117  SleepEx(100, TRUE);
118  }
119  watcher->Cleanup();
120 
121  watcher->SetIsThreadRunning(PR_FALSE);
122  return 0;
123 }
124 
125 //------------------------------------------------------------------------------
126 
129  nsIObserver,
131 
133  mRootDirHandle(INVALID_HANDLE_VALUE),
134  mWatcherThread(INVALID_HANDLE_VALUE),
135  mBuffer(nsnull),
136  mShouldRunThread(PR_FALSE),
137  mIsThreadRunning(PR_FALSE),
138  mShuttingDown(PR_FALSE)
139 {
140  SB_PRLOG_SETUP(sbWin32FSWatcher);
141 
142  mEventPathsSetLock =
143  nsAutoLock::NewLock("sbWin32FileSystemWatcher::mEventPathsSetLock");
144  NS_ASSERTION(mEventPathsSetLock, "Failed to create lock");
145 }
146 
148 {
149  TRACE("%s", __FUNCTION__);
150  nsAutoLock::DestroyLock(mEventPathsSetLock);
151 
152  // The thread was asked to terminate in the "quit-application" notification.
153  // If it is still running, force kill it now.
154  if ((mWatcherThread != INVALID_HANDLE_VALUE) && mIsThreadRunning) {
155  TerminateThread(mWatcherThread, 0);
156  }
157  mWatcherThread = INVALID_HANDLE_VALUE;
158 }
159 
160 NS_IMETHODIMP
162  const nsAString & aRootPath,
163  PRBool aIsRecursive)
164 {
165  TRACE("%s: root path = %s", __FUNCTION__,
166  NS_ConvertUTF16toUTF8(aRootPath).get());
167  nsresult rv;
168  nsCOMPtr<nsIObserverService> obsService =
169  do_GetService("@mozilla.org/observer-service;1", &rv);
170  NS_ENSURE_SUCCESS(rv, rv);
171 
172  rv = obsService->AddObserver(this, "quit-application", PR_FALSE);
173  NS_ENSURE_SUCCESS(rv, rv);
174 
175  return sbBaseFileSystemWatcher::Init(aListener, aRootPath, aIsRecursive);
176 }
177 
178 NS_IMETHODIMP
179 sbWin32FileSystemWatcher::StopWatching(PRBool aShouldSaveSession)
180 {
181  TRACE("%s", __FUNCTION__);
182 
183  mShuttingDown = PR_TRUE;
184 
185  if (!mIsWatching) {
186  return NS_OK;
187  }
188 
189  mShouldRunThread = PR_FALSE;
190 
191  if (mTimer) {
192  mTimer->Cancel();
193  }
194 
195  if (mRebuildThread) {
196  mRebuildThread->Shutdown();
197  }
198 
199  return sbBaseFileSystemWatcher::StopWatching(aShouldSaveSession);
200 }
201 
202 void
204 {
205  TRACE("%s", __FUNCTION__);
206  if (mBuffer) {
207  nsMemory::Free(mBuffer);
208  mBuffer = NULL;
209  }
210 
211  if (mRootDirHandle != INVALID_HANDLE_VALUE) {
212  CloseHandle(mRootDirHandle);
213  mRootDirHandle = INVALID_HANDLE_VALUE;
214  }
215 
216 }
217 
218 void
220 {
221  TRACE("%s", __FUNCTION__);
222  nsresult rv;
223 
224  NS_ENSURE_TRUE(!mShuttingDown, /* void */);
225 
226  mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
227  NS_ENSURE_SUCCESS(rv, /* void */);
228 
229  rv = mTimer->InitWithCallback(this, 100, nsITimer::TYPE_REPEATING_SLACK);
230  NS_ENSURE_SUCCESS(rv, /* void */);
231 }
232 
233 void
235 {
236  TRACE("%s", __FUNCTION__);
237  DWORD const flags =
238  FILE_NOTIFY_CHANGE_FILE_NAME |
239  FILE_NOTIFY_CHANGE_DIR_NAME |
240  FILE_NOTIFY_CHANGE_LAST_WRITE |
241  FILE_NOTIFY_CHANGE_CREATION;
242 
243  if (mRootDirHandle != INVALID_HANDLE_VALUE) {
244  BOOL result =
245  ReadDirectoryChangesW(mRootDirHandle,
246  mBuffer,
247  BUFFER_LEN,
248  TRUE, // watch subdirs
249  flags,
250  NULL,
251  &mOverlapped,
253  if (!result) {
254  NS_WARNING("ERROR: Could not ReadDirectoryChangesW()");
255  if (mRootDirHandle != INVALID_HANDLE_VALUE) {
256  CloseHandle(mRootDirHandle);
257  mRootDirHandle = INVALID_HANDLE_VALUE;
258  }
259  }
260  }
261 }
262 
263 PRBool
265 {
266  return mShouldRunThread;
267 }
268 
269 PRBool
271 {
272  return mIsThreadRunning;
273 }
274 
275 void
277 {
278  mIsThreadRunning = aIsThreadRunning;
279 }
280 
281 void*
283 {
284  return mBuffer;
285 }
286 
289 {
290  return &mEventPathsSet;
291 }
292 
293 PRLock*
295 {
296  return mEventPathsSetLock;
297 }
298 
299 //------------------------------------------------------------------------------
300 // sbFileSystemTreeListener
301 
302 NS_IMETHODIMP
303 sbWin32FileSystemWatcher::OnTreeReady(const nsAString & aTreeRootPath,
304  sbStringArray & aDirPathArray)
305 {
306  TRACE("%s: root path %s",
307  __FUNCTION__, NS_ConvertUTF16toUTF8(aTreeRootPath).get());
308  if (mWatchPath.Equals(EmptyString())) {
309  // If the watch path is empty here, this means that the tree was loaded
310  // from a previous session. Set the watch path now.
311  mWatchPath.Assign(aTreeRootPath);
312  }
313 
314  // Setup the timer callback
315  nsresult rv;
316  NS_ENSURE_STATE(!mRebuildThread);
317 
318  // Setup the timer callback, on a background thread
319  nsCOMPtr<nsIRunnable> initRebuildEvent =
320  NS_NEW_RUNNABLE_METHOD(sbWin32FileSystemWatcher, this, InitRebuildThread);
321  NS_ENSURE_TRUE(initRebuildEvent, NS_ERROR_OUT_OF_MEMORY);
322 
323  rv = NS_NewThread(getter_AddRefs(mRebuildThread), initRebuildEvent);
324  NS_ENSURE_SUCCESS(rv, rv);
325 
326  // Get a handle to the root directory.
327  mRootDirHandle =
328  CreateFileW(mWatchPath.get(), // path
329  GENERIC_READ, // desired access
330  FILE_SHARE_READ | // shared mode
331  FILE_SHARE_WRITE,
332  NULL, // security attributes
333  OPEN_EXISTING, // creation disposition
334  FILE_FLAG_BACKUP_SEMANTICS | // flags and attributes
335  FILE_FLAG_OVERLAPPED,
336  NULL); // template file
337 
338  NS_ENSURE_TRUE(mRootDirHandle != INVALID_HANDLE_VALUE, NS_ERROR_UNEXPECTED);
339 
340  memset(&mOverlapped, 0, sizeof(mOverlapped));
341  mOverlapped.hEvent = (HANDLE)this;
342 
343  if (!mBuffer) {
344  mBuffer = nsMemory::Alloc(BUFFER_LEN);
345  }
346 
347  // Start the watcher thread.
348  if (!mIsThreadRunning) {
349  mShouldRunThread = PR_TRUE;
350  mWatcherThread = CreateThread(NULL, 0, BackgroundThreadProc, this, 0, NULL);
351  NS_ENSURE_TRUE(mWatcherThread != INVALID_HANDLE_VALUE, NS_ERROR_OUT_OF_MEMORY);
352  }
353 
354  mIsWatching = PR_TRUE;
355 
356  rv = mListener->OnWatcherStarted();
357  NS_ENSURE_SUCCESS(rv, rv);
358 
359  return NS_OK;
360 }
361 
362 //------------------------------------------------------------------------------
363 // nsIObserver
364 
365 NS_IMETHODIMP
366 sbWin32FileSystemWatcher::Observe(nsISupports *aObject,
367  const char *aTopic,
368  const PRUnichar *aData)
369 {
370  LOG("%s: observing %s", __FUNCTION__, aTopic);
371  if (strcmp(aTopic, "quit-application") == 0) {
372  if (mIsWatching) {
373  // Pass in PR_FALSE - the owner of the file system watcher should stop
374  // the class themselves in order to save the current tree to disk.
375  StopWatching(PR_FALSE);
376  }
377 
378  // Remove observer hook
379  nsresult rv;
380  nsCOMPtr<nsIObserverService> obsService =
381  do_GetService("@mozilla.org/observer-service;1", &rv);
382  NS_ENSURE_SUCCESS(rv, rv);
383 
384  obsService->RemoveObserver(this, "quit-application");
385  NS_ENSURE_SUCCESS(rv, rv);
386  }
387 
388  return NS_OK;
389 }
390 
391 //------------------------------------------------------------------------------
392 // nsITimerCallback
393 
394 NS_IMETHODIMP
395 sbWin32FileSystemWatcher::Notify(nsITimer *aTimer)
396 {
397  TRACE("%s", __FUNCTION__);
398  {
399  nsAutoLock lock(mEventPathsSetLock);
400 
401  sbStringSet folderEventPaths;
402 
403  // Read the event paths out of the set.
404  // The event paths in |mEventPathsSet| contain the full absolute path
405  // (including leaf name) of each event. Clear out the set and build a new
406  // set of unique folder paths.
407  while (!mEventPathsSet.empty()) {
408  sbStringSetIter begin = mEventPathsSet.begin();
409 
410  nsString curEventPath(mTree->EnsureTrailingPath(mWatchPath));
411  curEventPath.Append(*begin);
412 
413  // Remove the string and bump the iterator.
414  mEventPathsSet.erase(begin);
415 
416  nsresult rv;
417  nsCOMPtr<nsILocalFile> curEventFile =
418  do_CreateInstance("@mozilla.org/file/local;1", &rv);
419  if (NS_FAILED(rv)) {
420  continue;
421  }
422 
423  rv = curEventFile->InitWithPath(curEventPath);
424  if (NS_FAILED(rv)) {
425  continue;
426  }
427 
428  // Since the file/folder might no longer be present on the file system,
429  // manually cut out the leaf name for the file-spec to get the parent
430  // folder path.
431  nsString curEventFileLeafName;
432  rv = curEventFile->GetLeafName(curEventFileLeafName);
433  if (NS_FAILED(rv)) {
434  continue;
435  }
436 
437  curEventPath.Cut(curEventPath.Length() - curEventFileLeafName.Length(),
438  curEventFileLeafName.Length());
439  folderEventPaths.insert(curEventPath);
440  }
441 
442  // Now update the tree with the unique folder event paths.
443  while (!folderEventPaths.empty()) {
444  sbStringSetIter begin = folderEventPaths.begin();
445  mTree->Update(*begin);
446  folderEventPaths.erase(begin);
447  }
448  }
449 
450  return NS_OK;
451 }
452 
NS_IMETHOD StopWatching(PRBool aShouldSaveSession)
nsCOMPtr< sbIFileSystemListener > mListener
NS_IMPL_ISUPPORTS_INHERITED2(sbWin32FileSystemWatcher, sbBaseFileSystemWatcher, nsIObserver, nsITimerCallback) sbWin32FileSystemWatcher
#define SB_PRLOG_SETUP(x)
Definition: sbDebugUtils.h:115
return NS_OK
#define LOG(args)
NS_IMETHOD OnTreeReady(const nsAString &aTreeRootPath, sbStringArray &aDirPathArray)
nsTArray< nsString > sbStringArray
sbDeviceFirmwareAutoCheckForUpdate prototype flags
friend DWORD WINAPI BackgroundThreadProc(void *p)
nsRefPtr< sbFileSystemTree > mTree
function TRACE(s)
function Init()
sbStringSet::iterator sbStringSetIter
std::set< nsString > sbStringSet
NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIOBSERVER NS_DECL_NSITIMERCALLBACK NS_IMETHOD Init(sbIFileSystemListener *aListener, const nsAString &aRootPath, PRBool aIsRecursive)
if(DEBUG_DATAREMOTES)
DWORD WINAPI BackgroundThreadProc(void *p)
void SetIsThreadRunning(PRBool aIsThreadRunning)
#define BUFFER_LEN
nsITimerCallback
VOID CALLBACK ReadDirectoryChangesWCallbackRoutine(__in DWORD dwErrorCode, __in DWORD dwNumberOfBytesTransfered, __in LPOVERLAPPED lpOverlapped)
_updateTextAndScrollDataForFrame aData