sbFileSystemTree.cpp
Go to the documentation of this file.
1 /*
2 //
3 // Copyright(c) 2005-2009 POTI, Inc.
4 // http://songbirdnest.com
5 //
6 // This file may be licensed under the terms of of the
7 // GNU General Public License Version 2 (the "GPL").
8 //
9 // Software distributed under the License is distributed
10 // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
11 // express or implied. See the GPL for the specific language
12 // governing rights and limitations.
13 //
14 // You should have received a copy of the GPL along with this
15 // program. If not, go to http://www.gnu.org/licenses/gpl.html
16 // or write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 //
19 // END SONGBIRD GPL
20 //
21 */
22 
23 #include "sbFileSystemTree.h"
24 
25 #include <nsComponentManagerUtils.h>
26 #include <nsServiceManagerUtils.h>
27 #include <nsCRT.h>
28 #include <nsThreadUtils.h>
29 #include <nsAutoLock.h>
30 
31 #include <nsISimpleEnumerator.h>
32 #include <nsIProxyObjectManager.h>
33 #include <nsIRunnable.h>
34 #include <nsIThreadManager.h>
35 #include <nsIThreadPool.h>
36 
38 #include <sbStringUtils.h>
39 #include "sbFileSystemChange.h"
40 
41 // Save ourselves some pain by getting the path seperator char.
42 // NOTE: FILE_PATH_SEPARATOR is always going to one char long.
43 #define PATH_SEPERATOR_CHAR \
44  NS_LITERAL_STRING(FILE_PATH_SEPARATOR).CharAt(0)
45 
46 // Logging
47 #ifdef PR_LOGGING
48 static PRLogModuleInfo* gFSTreeLog = nsnull;
49 #define TRACE(args) PR_LOG(gFSTreeLog, PR_LOG_DEBUG, args)
50 #define LOG(args) PR_LOG(gFSTreeLog, PR_LOG_WARN, args)
51 #else
52 #define TRACE(args) /* nothing */
53 #define LOG(args) /* nothing */
54 #endif /* PR_LOGGING */
55 
56 #ifdef __GNUC__
57 #define __FUNCTION__ __PRETTY_FUNCTION__
58 #endif /* __GNUC__ */
59 
60 //------------------------------------------------------------------------------
61 // Utility container, helps prevent running up the tree to find the
62 // ful path of a node.
63 
64 struct NodeContext
65 {
66  NodeContext(const nsAString & aFullPath, sbFileSystemNode *aNode)
67  : fullPath(aFullPath), node(aNode)
68  {
69  }
70 
71  nsString fullPath;
72  nsRefPtr<sbFileSystemNode> node;
73 };
74 
75 //------------------------------------------------------------------------------
76 
78 
80  : mListener(nsnull)
81  , mShouldLoadSession(PR_FALSE)
82  , mIsIntialized(PR_FALSE)
83  , mRootNodeLock(nsAutoLock::NewLock("sbFileSystemTree::mRootNodeLock"))
84  , mListenerLock(nsAutoLock::NewLock("sbFileSystemTree::mListenerLock"))
85 {
86 #ifdef PR_LOGGING
87  if (!gFSTreeLog) {
88  gFSTreeLog = PR_NewLogModule("sbFSTree");
89  }
90 #endif
91  NS_ASSERTION(mRootNodeLock, "Failed to create mRootNodeLock!");
92  NS_ASSERTION(mListenerLock, "Failed to create mListenerLock!");
93 }
94 
96 {
97  if (mRootNodeLock) {
98  nsAutoLock::DestroyLock(mRootNodeLock);
99  }
100  if (mListenerLock) {
101  nsAutoLock::DestroyLock(mListenerLock);
102  }
103 }
104 
105 nsresult
106 sbFileSystemTree::Init(const nsAString & aPath, PRBool aIsRecursive)
107 {
108  if (mIsIntialized) {
109  return NS_ERROR_ALREADY_INITIALIZED;
110  }
111 
112  mIsIntialized = PR_TRUE;
113  mShouldLoadSession = PR_FALSE;
114  mRootPath.Assign(aPath);
115  mIsRecursiveBuild = aIsRecursive;
116 
117  return InitTree();
118 }
119 
120 nsresult
122 {
123  if (mIsIntialized) {
124  return NS_ERROR_ALREADY_INITIALIZED;
125  }
126 
127  mSavedSessionID = aSessionID;
128  mShouldLoadSession = PR_TRUE;
129  mIsIntialized = PR_FALSE; // not really initialized until session is loaded.
130 
131  return InitTree();
132 }
133 
134 nsresult
136 {
137  // This method just sets up the initial build thread. All pre-build
138  // initialization should have been done before calling this method.
139  nsresult rv;
140  nsCOMPtr<nsIThreadManager> threadMgr =
141  do_GetService("@mozilla.org/thread-manager;1", &rv);
142  NS_ENSURE_SUCCESS(rv, rv);
143 
144  // Save the threading context for notifying the listeners on the current
145  // thread once the build operation has completed.
146  rv = threadMgr->GetCurrentThread(getter_AddRefs(mOwnerContextThread));
147  NS_ENSURE_SUCCESS(rv, rv);
148 
149  nsCOMPtr<nsIThreadPool> threadPoolService =
150  do_GetService("@songbirdnest.com/Songbird/ThreadPoolService;1", &rv);
151  NS_ENSURE_SUCCESS(rv, rv);
152 
153  nsCOMPtr<nsIRunnable> runnable =
154  NS_NEW_RUNNABLE_METHOD(sbFileSystemTree, this, RunBuildThread);
155  NS_ENSURE_TRUE(runnable, NS_ERROR_FAILURE);
156 
157  rv = threadPoolService->Dispatch(runnable, NS_DISPATCH_NORMAL);
158  NS_ENSURE_SUCCESS(rv, rv);
159 
160  return NS_OK;
161 }
162 
163 void
165 {
166  nsresult rv;
167 
168  // If the tree should compare itself from a previous state - load that now.
169  nsRefPtr<sbFileSystemNode> savedRootNode;
170  if (mShouldLoadSession) {
171  nsRefPtr<sbFileSystemTreeState> savedTreeState =
172  new sbFileSystemTreeState();
173  NS_ASSERTION(savedTreeState, "Could not create a sbFileSystemTreeState!");
174 
175  rv = savedTreeState->LoadTreeState(mSavedSessionID,
176  mRootPath,
177  &mIsRecursiveBuild,
178  getter_AddRefs(savedRootNode));
179  if (NS_FAILED(rv)) {
180  NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to load saved tree session!");
181 
182  // In the event that a session failed to load from disk, inform all the
183  // listeners of the error and return. The tree can not be built w/o a
184  // root watch path, which is stored in the stored session data.
185  nsCOMPtr<nsIRunnable> runnable =
186  NS_NEW_RUNNABLE_METHOD(sbFileSystemTree, this, NotifySessionLoadError);
187  NS_ASSERTION(runnable,
188  "Could not create a runnable for NotifySessionLoadError()!");
189  rv = mOwnerContextThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
190  NS_ASSERTION(NS_SUCCEEDED(rv),
191  "Could not dispatch NotifySessionLoadError()!");
192 
193  return;
194  }
195  else {
196  mIsIntialized = PR_TRUE;
197  }
198  }
199 
200  mRootFile = do_CreateInstance("@mozilla.org/file/local;1", &rv);
201  NS_ASSERTION(NS_SUCCEEDED(rv), "Could not create a nsILocalFile!");
202 
203  rv = mRootFile->InitWithPath(mRootPath);
204  NS_ASSERTION(NS_SUCCEEDED(rv), "Could not InitWithPath a nsILocalFile!");
205 
206  // Before building the tree, ensure that root file does exist.
207  PRBool exists = PR_FALSE;
208  if ((NS_FAILED(mRootFile->Exists(&exists))) || !exists) {
209  nsCOMPtr<nsIRunnable> runnable =
210  NS_NEW_RUNNABLE_METHOD(sbFileSystemTree, this, NotifyRootPathIsMissing);
211  NS_ASSERTION(runnable,
212  "Could not create a runnable for NotifyRootPathIsMissing()!");
213 
214  rv = mOwnerContextThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
215  NS_ASSERTION(NS_SUCCEEDED(rv),
216  "Could not Dispatch NotifyRootPathIsMissing()!");
217 
218  // Don't bother trying to build the rest of the tree
219  return;
220  }
221 
222  {
223  // Don't allow any changes to structure
224  nsAutoLock rootNodeLock(mRootNodeLock);
225 
226  rv = CreateNode(mRootFile, nsnull, getter_AddRefs(mRootNode));
227  NS_ASSERTION(NS_SUCCEEDED(rv), "Could not create a sbFileSystemNode!");
228 
229  // Build the tree and get the added dir paths to report back to the
230  // tree listener once the build finishes.
231  rv = AddChildren(mRootPath, mRootNode, PR_TRUE, PR_FALSE);
232  NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to add children to root node!");
233  }
234 
235  if (mShouldLoadSession && savedRootNode) {
236  // Now that the saved tree has been reloaded, and the current tree has
237  // been built, build a change list.
238  rv = GetTreeChanges(savedRootNode, mSessionChanges);
239  if (NS_FAILED(rv)) {
240  NS_WARNING("Could not get the old session tree changes!");
241  }
242  }
243 
244  // Now notify our listeners on the main thread
245  nsCOMPtr<nsIRunnable> runnable =
246  NS_NEW_RUNNABLE_METHOD(sbFileSystemTree, this, NotifyBuildComplete);
247  NS_ASSERTION(runnable,
248  "Could not create a runnable for NotifyBuildComplete()!!");
249 
250  rv = mOwnerContextThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
251  NS_ASSERTION(NS_SUCCEEDED(rv), "Could not dispatch NotifyBuildComplete()!");
252 }
253 
254 void
256 {
257  TRACE(("%s: build for [%s] complete",
258  __FUNCTION__,
259  NS_ConvertUTF16toUTF8(mRootPath).get()));
260  // If the tree was initialized from a previous session, inform the listener
261  // of all the changes that have been detected from between sessions before
262  // noitifying the tree is ready. This is the documented behavior of
263  // |sbIFileSystemWatcher|.
264  if (mShouldLoadSession && mSessionChanges.Length() > 0) {
265  nsresult rv;
266  for (PRUint32 i = 0; i < mSessionChanges.Length(); i++) {
267  nsRefPtr<sbFileSystemPathChange> curPathChange(mSessionChanges[i]);
268  if (!curPathChange) {
269  NS_WARNING("Could not get current path change!");
270  continue;
271  }
272 
273  nsString curEventPath;
274  rv = curPathChange->GetChangePath(curEventPath);
275  if (NS_FAILED(rv)) {
276  NS_WARNING("Could not get the current change event path!");
277  continue;
278  }
279 
280  EChangeType curChangeType;
281  rv = curPathChange->GetChangeType(&curChangeType);
282  if (NS_FAILED(rv)) {
283  NS_WARNING("Could not get current change type!");
284  continue;
285  }
286 
287  rv = NotifyChanges(curEventPath, curChangeType);
288  if (NS_FAILED(rv)) {
289  NS_WARNING("Could not notify listeners of changes!");
290  }
291  }
292 
293  mSessionChanges.Clear();
294  }
295 
296  {
297  nsAutoLock listenerLock(mListenerLock);
298  if (mListener) {
299  mListener->OnTreeReady(mRootPath, mDiscoveredDirs);
300  }
301  }
302 
303  // Don't hang on to the values in |mDiscoveredDirs|.
304  mDiscoveredDirs.Clear();
305 }
306 
307 void
309 {
310  nsAutoLock listenerLock(mListenerLock);
311 
312  if (mListener) {
313  mListener->OnRootPathMissing();
314  }
315 }
316 
317 void
319 {
320  nsAutoLock listenerLock(mListenerLock);
321 
322  if (mListener) {
323  mListener->OnTreeSessionLoadError();
324  }
325 }
326 
327 nsresult
328 sbFileSystemTree::Update(const nsAString & aPath)
329 {
330  nsRefPtr<sbFileSystemNode> pathNode;
331  nsresult rv;
332  { /* scope */
333  nsAutoLock rootLock(mRootNodeLock);
334  rv = GetNode(aPath, mRootNode, getter_AddRefs(pathNode));
335  }
336  if (NS_FAILED(rv)) {
337  TRACE(("%s: Could not update the tree at path '%s'!!!",
338  __FUNCTION__, NS_ConvertUTF16toUTF8(aPath).get()));
339  return rv;
340  }
341  NS_ENSURE_SUCCESS(rv, rv);
342 
343  sbNodeChangeArray pathChangesArray;
344  rv = GetNodeChanges(pathNode, aPath, pathChangesArray);
345  NS_ENSURE_SUCCESS(rv, rv);
346 
347  nsString path(aPath);
348  PRUint32 numChanges = pathChangesArray.Length();
349  for (PRUint32 i = 0; i < numChanges; i++) {
350  nsRefPtr<sbFileSystemNodeChange> curChange =
351  static_cast<sbFileSystemNodeChange *>(pathChangesArray[i].get());
352 
353  EChangeType curChangeType;
354  rv = curChange->GetChangeType(&curChangeType);
355  NS_ENSURE_SUCCESS(rv, rv);
356 
357  nsRefPtr<sbFileSystemNode> curChangeNode;
358  rv = curChange->GetNode(getter_AddRefs(curChangeNode));
359  NS_ENSURE_SUCCESS(rv, rv);
360 
361  nsString curLeafName;
362  rv = curChangeNode->GetLeafName(curLeafName);
363  NS_ENSURE_SUCCESS(rv, rv);
364 
365  nsString curNodeFullPath = EnsureTrailingPath(aPath);
366  curNodeFullPath.Append(curLeafName);
367 
368  PRBool isDir;
369  rv = curChangeNode->GetIsDir(&isDir);
370  NS_ENSURE_SUCCESS(rv, rv);
371 
372  switch (curChangeType) {
373  case eChanged:
374  // Since the the native file system hooks are supposed to watch
375  // only directories - do not handle directory changes. The event
376  // will be processed later (possibly next).
377  if (!isDir) {
378  // Files are simple, just replace the the current node with the
379  // change node.
380  nsString curChangeLeafName;
381  rv = curChangeNode->GetLeafName(curChangeLeafName);
382  NS_ENSURE_SUCCESS(rv, rv);
383 
384  // Replace the node
385  { /* scope */
386  nsAutoLock rootLock(mRootNodeLock);
387  rv = pathNode->ReplaceNode(curChangeLeafName, curChangeNode);
388  }
389  NS_ENSURE_SUCCESS(rv, rv);
390  }
391  break;
392 
393  case eAdded:
394  if (isDir) {
395  rv = NotifyDirAdded(curChangeNode, curNodeFullPath);
396  NS_ENSURE_SUCCESS(rv, rv);
397  }
398 
399  // Simply add this child to the path node's children.
400  { /* scope */
401  nsAutoLock rootLock(mRootNodeLock);
402  rv = pathNode->AddChild(curChangeNode);
403  }
404  NS_ENSURE_SUCCESS(rv, rv);
405  break;
406 
407  case eRemoved:
408  if (isDir) {
409  rv = NotifyDirRemoved(curChangeNode, curNodeFullPath);
410  NS_ENSURE_SUCCESS(rv, rv);
411  }
412 
413  { /* scope */
414  nsAutoLock rootLock(mRootNodeLock);
415  rv = pathNode->RemoveChild(curChangeNode);
416  }
417  NS_ENSURE_SUCCESS(rv, rv);
418  break;
419  }
420 
421  // Inform the listeners of the change
422  rv = NotifyChanges(curNodeFullPath, curChangeType);
423  NS_ENSURE_SUCCESS(rv, rv);
424  }
425 
426  return NS_OK;
427 }
428 
429 nsresult
431 {
432  NS_ENSURE_ARG_POINTER(aListener);
433 
434  nsAutoLock listenerLock(mListenerLock);
435 
436  mListener = aListener;
437  return NS_OK;
438 }
439 
440 nsresult
442 {
443  nsAutoLock listeners(mListenerLock);
444 
445  mListener = nsnull;
446  return NS_OK;
447 }
448 
449 nsresult
450 sbFileSystemTree::SaveTreeSession(const nsID & aSessionID)
451 {
452  if (!mRootNode) {
453  return NS_ERROR_UNEXPECTED;
454  }
455 
456  // XXX Move this off of the main thread
457  nsAutoLock rootNodeLock(mRootNodeLock);
458 
459  nsRefPtr<sbFileSystemTreeState> treeState = new sbFileSystemTreeState();
460  NS_ENSURE_TRUE(treeState, NS_ERROR_OUT_OF_MEMORY);
461 
462  nsresult rv;
463  rv = treeState->SaveTreeState(this, aSessionID);
464  NS_ENSURE_SUCCESS(rv, rv);
465 
466  return NS_OK;
467 }
468 
469 nsresult
471  sbFileSystemNode *aParentNode,
472  PRBool aBuildDiscoveredDirArray,
473  PRBool aNotifyListeners)
474 {
475  std::stack<NodeContext> nodeContextStack;
476  nodeContextStack.push(NodeContext(aPath, aParentNode));
477 
478  while (!nodeContextStack.empty()) {
479  NodeContext curNodeContext = nodeContextStack.top();
480  nodeContextStack.pop();
481 
482  sbNodeMap childNodes;
483  nsresult rv = GetChildren(curNodeContext.fullPath,
484  curNodeContext.node,
485  childNodes);
486 
487  sbNodeMapIter begin = childNodes.begin();
488  sbNodeMapIter end = childNodes.end();
490  for (next = begin; next != end; ++next) {
491  nsRefPtr<sbFileSystemNode> curNode(next->second);
492  if (!curNode) {
493  continue;
494  }
495 
496  rv = curNodeContext.node->AddChild(curNode);
497  if (NS_FAILED(rv)) {
498  continue;
499  }
500 
501  PRBool isDir = PR_FALSE;
502  rv = curNode->GetIsDir(&isDir);
503  if (NS_FAILED(rv)) {
504  continue;
505  }
506 
507  if (aNotifyListeners || isDir) {
508  nsString curNodeLeafName(next->first);
509 
510  // Format the next child path
511  nsString curNodePath = EnsureTrailingPath(curNodeContext.fullPath);
512  curNodePath.Append(curNodeLeafName);
513 
514  if (mIsRecursiveBuild && isDir) {
515  nodeContextStack.push(NodeContext(curNodePath, curNode));
516 
517  if (aBuildDiscoveredDirArray) {
518  mDiscoveredDirs.AppendElement(curNodePath);
519  }
520  }
521 
522  if (aNotifyListeners) {
523  rv = NotifyChanges(curNodePath, eAdded);
524  if (NS_FAILED(rv)) {
525  NS_WARNING("Could not notify listener of change!");
526  }
527  }
528  }
529  }
530  }
531 
532  return NS_OK;
533 }
534 
535 nsresult
537  sbFileSystemNode *aParentNode,
538  sbNodeMap & aNodeMap)
539 {
540  nsresult rv;
541  nsCOMPtr<nsISimpleEnumerator> pathEnum;
542  rv = GetPathEntries(aPath, getter_AddRefs(pathEnum));
543  NS_ENSURE_SUCCESS(rv, rv);
544 
545  PRBool hasMore = PR_FALSE;
546  while ((NS_SUCCEEDED(pathEnum->HasMoreElements(&hasMore))) && hasMore) {
547  nsCOMPtr<nsISupports> curItem;
548  rv = pathEnum->GetNext(getter_AddRefs(curItem));
549  if (NS_FAILED(rv) || !curItem)
550  continue;
551 
552  nsCOMPtr<nsIFile> curFile = do_QueryInterface(curItem, &rv);
553  if (NS_FAILED(rv) || !curFile)
554  continue;
555 
556  // Don't track symlinks
557  PRBool isSymlink;
558  rv = curFile->IsSymlink(&isSymlink);
559  if (NS_FAILED(rv) || isSymlink)
560  continue;
561 
562 #if defined(XP_WIN)
563  // This check only needs to be done on windows
564  PRBool isSpecial;
565  rv = curFile->IsSpecial(&isSpecial);
566  if (NS_FAILED(rv) || isSpecial)
567  continue;
568 #endif
569 
570  nsRefPtr<sbFileSystemNode> curNode;
571  rv = CreateNode(curFile, aParentNode, getter_AddRefs(curNode));
572  if (NS_FAILED(rv) || !curNode)
573  continue;
574 
575  nsString curNodeLeafName;
576  rv = curNode->GetLeafName(curNodeLeafName);
577  if (NS_FAILED(rv))
578  continue;
579 
580  aNodeMap.insert(sbNodeMapPair(curNodeLeafName, curNode));
581  }
582 
583  return NS_OK;
584 }
585 
586 nsresult
588  sbFileSystemNode * aRootSearchNode,
589  sbFileSystemNode **aNodeRetVal)
590 {
591  NS_ENSURE_ARG_POINTER(aRootSearchNode);
592  NS_ENSURE_ARG_POINTER(aNodeRetVal);
593  NS_ENSURE_ARG(StringBeginsWith(aPath, mRootPath));
594 
595  *aNodeRetVal = nsnull;
596  nsresult rv;
597 
598  // Trim off the trailing '/' if one exists
599  nsString path(aPath);
600  if (StringEndsWith(path, NS_LITERAL_STRING(FILE_PATH_SEPARATOR))) {
601  path.Cut(path.Length() - 1, 1);
602  }
603 
604  // If this is the root path, simply return the root node.
605  if (path.Equals(mRootPath)) {
606  NS_IF_ADDREF(*aNodeRetVal = aRootSearchNode);
607  return NS_OK;
608  }
609 
610  // Only search path components that |aPath| and |mRootPath| don't have.
611  PRInt32 strRange = path.Find(mRootPath);
612  NS_ENSURE_TRUE(strRange >= 0, NS_ERROR_FAILURE);
613  strRange += mRootPath.Length();
614 
615  // If |searchPath| starts with a '/', remove it
616  nsString searchPath(Substring(path, strRange, path.Length() - strRange));
617  if (searchPath[0] == PATH_SEPERATOR_CHAR) {
618  searchPath.Cut(0, 1);
619  }
620 
621  // Get a list of each patch component to search the tree with
622  nsTArray<nsString> pathComponents;
623  nsString_Split(searchPath,
624  NS_LITERAL_STRING(FILE_PATH_SEPARATOR),
625  pathComponents);
626 
627  // Start searching at the passed in root node
628  nsRefPtr<sbFileSystemNode> curSearchNode = aRootSearchNode;
629 
630  PRBool foundTargetNode = PR_TRUE; // assume true, prove below
631  PRUint32 numComponents = pathComponents.Length();
632  for (PRUint32 i = 0; i < numComponents; i++) {
633  nsString curPathComponent(pathComponents[i]);
634 
635  sbNodeMap *curChildren = curSearchNode->GetChildren();
636  if (!curChildren) {
637  continue;
638  }
639 
640  // If the current component was not found in the child nodes, bail.
641  sbNodeMapIter foundNodeIter = curChildren->find(curPathComponent);
642  if (foundNodeIter == curChildren->end()) {
643  foundTargetNode = PR_FALSE;
644  break;
645  }
646 
647  // This is the found component node
648  curSearchNode = foundNodeIter->second;
649  } // end for
650 
651  if (foundTargetNode) {
652  NS_ADDREF(*aNodeRetVal = curSearchNode);
653  rv = NS_OK;
654  }
655  else {
656  rv = NS_ERROR_FAILURE;
657  }
658 
659  return rv;
660 }
661 
662 nsresult
664  sbFileSystemNode *aParentNode,
665  sbFileSystemNode **aNodeRetVal)
666 {
667  NS_ENSURE_ARG_POINTER(aFile);
668 
669  nsresult rv;
670 
671 #if DEBUG
672  // Sanity checks to make sure that the passed in parent makes sense.
673  if (aParentNode) {
674  nsString parentNodeLeafName;
675  rv = aParentNode->GetLeafName(parentNodeLeafName);
676  NS_ENSURE_SUCCESS(rv, rv);
677 
678  nsCOMPtr<nsIFile> parentFile;
679  rv = aFile->GetParent(getter_AddRefs(parentFile));
680  if (NS_SUCCEEDED(rv) && parentFile) {
681  nsString parentFileLeafName;
682  rv = parentFile->GetLeafName(parentFileLeafName);
683  NS_ENSURE_SUCCESS(rv, rv);
684  NS_ASSERTION(parentFileLeafName.Equals(parentNodeLeafName),
685  "ERROR: CreateNode() Potential invalid parent used!");
686  }
687  }
688 #endif
689 
690  nsString leafName;
691  rv = aFile->GetLeafName(leafName);
692  NS_ENSURE_SUCCESS(rv, rv);
693 
694  PRBool isDir;
695  rv = aFile->IsDirectory(&isDir);
696  NS_ENSURE_SUCCESS(rv, rv);
697 
698  PRInt64 lastModify;
699  rv = aFile->GetLastModifiedTime(&lastModify);
700  NS_ENSURE_SUCCESS(rv, rv);
701 
702  nsRefPtr<sbFileSystemNode> node = new sbFileSystemNode();
703  NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY);
704 
705  rv = node->Init(leafName, isDir, lastModify);
706  NS_ENSURE_SUCCESS(rv, rv);
707 
708  NS_ADDREF(*aNodeRetVal = node);
709  return NS_OK;
710 }
711 
712 nsresult
714  const nsAString & aNodePath,
715  sbNodeChangeArray & aOutChangeArray)
716 {
717  // This is a copy-constructor:
718  sbNodeMap childSnapshot(*aNode->GetChildren());
719 
720  nsresult rv;
721  nsCOMPtr<nsISimpleEnumerator> pathEnum;
722  rv = GetPathEntries(aNodePath, getter_AddRefs(pathEnum));
723  NS_ENSURE_SUCCESS(rv, rv);
724 
725  PRBool hasMore = PR_FALSE;
726  while ((NS_SUCCEEDED(pathEnum->HasMoreElements(&hasMore))) && hasMore) {
727  nsCOMPtr<nsISupports> curItem;
728  rv = pathEnum->GetNext(getter_AddRefs(curItem));
729  if (NS_FAILED(rv) || !curItem) {
730  NS_WARNING("ERROR: Could not GetNext() item in enumerator!");
731  continue;
732  }
733 
734  nsCOMPtr<nsIFile> curFile = do_QueryInterface(curItem, &rv);
735  if (NS_FAILED(rv) || !curFile) {
736  NS_WARNING("ERROR: Could not QI to a nsIFile!");
737  continue;
738  }
739 
740  nsString curFileLeafName;
741  rv = curFile->GetLeafName(curFileLeafName);
742  if (NS_FAILED(rv)) {
743  NS_WARNING("ERROR: Could not get the leaf name from the file-spec!");
744  continue;
745  }
746 
747  // See if a node exists with this file name in the child snapshot.
748  sbNodeMapIter foundNodeIter = childSnapshot.find(curFileLeafName);
749  if (foundNodeIter == childSnapshot.end()) {
750  // The current file entry is not in the child snapshot, which means
751  // that it is a new addition to the file path. Add a change entry
752  // for this event.
753  nsRefPtr<sbFileSystemNode> newFileNode;
754  rv = CreateNode(curFile, aNode, getter_AddRefs(newFileNode));
755  if (NS_FAILED(rv) || !newFileNode) {
756  NS_WARNING("ERROR: Could not create a sbFileSystemNode!");
757  continue;
758  }
759 
760  rv = AppendCreateNodeChangeItem(newFileNode, eAdded, aOutChangeArray);
761  if (NS_FAILED(rv)) {
762  NS_WARNING("ERROR: Could not add create change item!");
763  continue;
764  }
765  }
766  else {
767  // Found a node that matches the leaf name in the child snapshot.
768  // Now look to see if the item has changed by comparing the last
769  // modify time stamps.
770  nsRefPtr<sbFileSystemNode> curChildNode(foundNodeIter->second);
771  if (!curChildNode) {
772  NS_WARNING("ERROR: Could not get node from sbNodeMapIter!");
773  continue;
774  }
775 
776  // Now compare the timestamps
777  PRInt64 curFileLastModify;
778  rv = curFile->GetLastModifiedTime(&curFileLastModify);
779  if (NS_FAILED(rv)) {
780  NS_WARNING("ERROR: Could not get file last modify time!");
781  continue;
782  }
783  PRInt64 curChildNodeLastModify;
784  rv = curChildNode->GetLastModify(&curChildNodeLastModify);
785  if (NS_FAILED(rv)) {
786  NS_WARNING("ERROR: Could not get node last modify time!");
787  continue;
788  }
789 
790  if (curFileLastModify != curChildNodeLastModify) {
791  // The original node has been modified, create a change object for
792  // this mutation event.
793  nsRefPtr<sbFileSystemNode> changedNode;
794  rv = CreateNode(curFile, aNode, getter_AddRefs(changedNode));
795  if (NS_FAILED(rv) || !changedNode) {
796  NS_WARNING("ERROR: Could not create a sbFileSystemNode!");
797  continue;
798  }
799 
800  rv = AppendCreateNodeChangeItem(changedNode, eChanged, aOutChangeArray);
801  if (NS_FAILED(rv)) {
802  NS_WARNING("ERROR: Could not add create change item!");
803  continue;
804  }
805  }
806 
807  // It's ok to fall-through since we want to remove the matched node
808  // from the child snapshot. This determines which nodes have been removed.
809  childSnapshot.erase(curFileLeafName);
810  }
811  }
812 
813  // Now that the current directory entries have been traversed, loop through
814  // and report all the nodes still in the child snapshot as removed events.
815  sbNodeMapIter begin = childSnapshot.begin();
816  sbNodeMapIter end = childSnapshot.end();
818  for (next = begin; next != end; ++next) {
819  nsRefPtr<sbFileSystemNode> curNode(next->second);
820  NS_ASSERTION(curNode, "Could not get the current child snapshot node!");
821  if (!curNode) {
822  NS_WARNING("ERROR: Could not get node from sbNodeMapIter!");
823  continue;
824  }
825 
826  rv = AppendCreateNodeChangeItem(curNode, eRemoved, aOutChangeArray);
827  NS_ASSERTION(NS_SUCCEEDED(rv), "Error: Could not add a change event!");
828  }
829 
830  return NS_OK;
831 }
832 
833 nsresult
835  sbPathChangeArray & aOutChangeArray)
836 {
837  NS_ENSURE_ARG_POINTER(mRootNode);
838  NS_ENSURE_ARG_POINTER(aOldRootNode);
839 
840  // This method is called from a background thread, prevent changes
841  // to the root node until the changes have been found.
842  nsAutoLock rootNodeLock(mRootNodeLock);
843 
844  // Both |mRootNode| and |aOldRootNode| are guarenteed, compare them and than
845  // start the tree search.
846  PRBool isSame = PR_FALSE;
847  nsresult rv;
848  rv = CompareNodes(mRootNode, aOldRootNode, &isSame);
849  NS_ENSURE_SUCCESS(rv, rv);
850 
851  if (!isSame) {
852  rv = AppendCreatePathChangeItem(mRootPath, eChanged, aOutChangeArray);
853  NS_ENSURE_SUCCESS(rv, rv);
854  }
855 
856  // Need to keep the context of the node for building the path changes.
857  std::stack<NodeContext> nodeContextStack;
858  nodeContextStack.push(NodeContext(mRootPath, mRootNode));
859 
860  while (!nodeContextStack.empty()) {
861  NodeContext curNodeContext = nodeContextStack.top();
862  nodeContextStack.pop();
863 
864  // Lookup the old node in the tree (if it is not there, something is
865  // really wrong!).
866  nsRefPtr<sbFileSystemNode> oldNodeContext;
867  rv = GetNode(curNodeContext.fullPath,
868  aOldRootNode,
869  getter_AddRefs(oldNodeContext));
870  if (NS_FAILED(rv) || !oldNodeContext) {
871  NS_WARNING("Could not find old context node!!! Something is really bad!");
872  continue;
873  }
874 
875  sbNodeMap *curNodeChildren = curNodeContext.node->GetChildren();
876  sbNodeMap oldNodeChildSnapshot(*oldNodeContext->GetChildren());
877 
878  nsString curContextRootPath = EnsureTrailingPath(curNodeContext.fullPath);
879 
880  // Loop through all the added children to find changes.
881  sbNodeMapIter begin = curNodeChildren->begin();
882  sbNodeMapIter end = curNodeChildren->end();
884  for (next = begin; next != end; ++next) {
885  nsString curChildPath(curContextRootPath);
886  curChildPath.Append(next->first);
887 
888  // See if the current child is in the old child snapshot.
889  sbNodeMapIter found = oldNodeChildSnapshot.find(next->first);
890  if (found == oldNodeChildSnapshot.end()) {
891  // The current child node is not in the current snapshot. Report
892  // this node and all of its children as added events.
893  std::stack<NodeContext> addedNodeContext;
894  addedNodeContext.push(NodeContext(curChildPath, next->second));
895 
896  rv = CreateTreeEvents(addedNodeContext, eAdded, aOutChangeArray);
897  if (NS_FAILED(rv)) {
898  NS_WARNING("Could not report tree added events!");
899  continue;
900  }
901  }
902  else {
903  // The current child node has a match in the old snapshot. Look to see
904  // if the nodes have changed. If so, report an event.
905  isSame = PR_FALSE;
906  rv = CompareNodes(next->second, found->second, &isSame);
907  if (NS_FAILED(rv)) {
908  NS_WARNING("Could not compare child nodes!");
909  continue;
910  }
911 
912  if (!isSame) {
913  rv = AppendCreatePathChangeItem(curChildPath,
914  eChanged,
915  aOutChangeArray);
916  if (NS_FAILED(rv)) {
917  NS_WARNING("could not create change item!");
918  continue;
919  }
920  }
921 
922  // Remove this node from the old child node snapshot so that removed
923  // nodes can be determined below. Also push the current node onto the
924  // context stack so that the next batch of children can be compared.
925  oldNodeChildSnapshot.erase(found->first);
926 
927  // Push the current node into the node context.
928  nsRefPtr<sbFileSystemNode> curChildNode(next->second);
929  nodeContextStack.push(NodeContext(curChildPath, curChildNode));
930  }
931  }
932 
933  // If there are remaining children in the old node child snapshot, report
934  // all of them and their children as deleted events.
935  if (oldNodeChildSnapshot.size() > 0) {
936  // Push all changes into the remove context stack.
937  sbNodeContextStack removedNodeContext;
938 
939  sbNodeMapIter removeBegin = oldNodeChildSnapshot.begin();
940  sbNodeMapIter removeEnd = oldNodeChildSnapshot.end();
941  sbNodeMapIter removeNext;
942  for (removeNext = removeBegin; removeNext != removeEnd; ++removeNext) {
943  nsString curRemoveChildPath(curContextRootPath);
944  curRemoveChildPath.Append(removeNext->first);
945 
946  removedNodeContext.push(NodeContext(curRemoveChildPath,
947  removeNext->second));
948  }
949 
950  rv = CreateTreeEvents(removedNodeContext, eRemoved, aOutChangeArray);
951  NS_ENSURE_SUCCESS(rv, rv);
952  }
953 
954  } // end while
955 
956  return NS_OK;
957 }
958 
959 nsresult
961  nsAString & aFullPath)
962 {
963  NS_ENSURE_ARG_POINTER(aAddedDirNode);
964 
965  // This is a little different than removal, since I have to add the new
966  // child nodes.
967  nsString fullPath = EnsureTrailingPath(aFullPath);
968 
969  // This is a shortcut for calling |AddChildren()| but toggling the
970  // notification flag to inform listeners. Keeping this here in case
971  // this needs to be threaded. If performance becomes a problem
972  // (i.e. a large folder was moved into the watch path) this should be
973  // moved to a background thread.
974  nsresult rv = AddChildren(fullPath, aAddedDirNode, PR_FALSE, PR_TRUE);
975  NS_ENSURE_SUCCESS(rv, rv);
976 
977  return NS_OK;
978 }
979 
980 nsresult
982  nsAString & aFullPath)
983 {
984  NS_ENSURE_ARG_POINTER(aRemovedDirNode);
985 
986  nsString fullPath = EnsureTrailingPath(aFullPath);
987 
988  // Loop through all the children in |aRemovedDirNode| and notify of
989  // removed events for each node.
990  sbNodeMap *dirChildren = aRemovedDirNode->GetChildren();
991  NS_ENSURE_TRUE(dirChildren, NS_ERROR_UNEXPECTED);
992 
993  sbNodeMapIter begin = dirChildren->begin();
994  sbNodeMapIter end = dirChildren->end();
996  for (next = begin; next != end; ++next) {
997  nsRefPtr<sbFileSystemNode> curNode(next->second);
998  if (!curNode) {
999  continue;
1000  }
1001 
1002  nsString curNodeLeafName(next->first);
1003 
1004  nsString curNodePath(fullPath);
1005  curNodePath.Append(curNodeLeafName);
1006 
1007  PRBool isDir;
1008  nsresult rv = curNode->GetIsDir(&isDir);
1009  NS_ENSURE_SUCCESS(rv, rv);
1010 
1011  // This is a dir, call out to have its children notified of their removal.
1012  if (isDir) {
1013  rv = NotifyDirRemoved(curNode, curNodePath);
1014  NS_ENSURE_SUCCESS(rv, rv);
1015  }
1016 
1017  // Now notify the listeners
1018  rv = NotifyChanges(curNodePath, eRemoved);
1019  NS_ENSURE_SUCCESS(rv, rv);
1020  }
1021 
1022  return NS_OK;
1023 }
1024 
1025 NS_IMETHODIMP
1026 sbFileSystemTree::NotifyChanges(const nsAString & aChangePath,
1027  PRUint32 aChangeType)
1028 {
1029  NS_ENSURE_TRUE(aChangeType == eChanged ||
1030  aChangeType == eAdded ||
1031  aChangeType == eRemoved,
1032  NS_ERROR_INVALID_ARG);
1033 
1034  nsCOMPtr<nsIThread> currentThread;
1035  nsresult rv = NS_GetCurrentThread(getter_AddRefs(currentThread));
1036  NS_ENSURE_SUCCESS(rv, rv);
1037  if (currentThread != mOwnerContextThread) {
1038  nsCOMPtr<sbPIFileSystemTree> proxiedThis;
1039  nsresult rv = do_GetProxyForObject(mOwnerContextThread,
1040  NS_GET_IID(sbPIFileSystemTree),
1041  this,
1042  NS_PROXY_SYNC | NS_PROXY_ALWAYS,
1043  getter_AddRefs(proxiedThis));
1044  NS_ENSURE_SUCCESS(rv, rv);
1045  rv = proxiedThis->NotifyChanges(aChangePath, aChangeType);
1046  return rv;
1047  }
1048 
1049  nsAutoLock listenerLock(mListenerLock);
1050  if (mListener) {
1051  mListener->OnChangeFound(aChangePath, EChangeType(aChangeType));
1052  }
1053 
1054  return NS_OK;
1055 }
1056 
1057 /* static */ nsresult
1059  nsISimpleEnumerator **aResultEnum)
1060 {
1061  NS_ENSURE_ARG_POINTER(aResultEnum);
1062 
1063  nsresult rv;
1064  nsCOMPtr<nsILocalFile> pathFile =
1065  do_CreateInstance("@mozilla.org/file/local;1", &rv);
1066  NS_ENSURE_SUCCESS(rv, rv);
1067 
1068  rv = pathFile->InitWithPath(aPath);
1069  NS_ENSURE_SUCCESS(rv, rv);
1070 
1071  return pathFile->GetDirectoryEntries(aResultEnum);
1072 }
1073 
1074 /* static */ nsresult
1076  sbFileSystemNode *aNode2,
1077  PRBool *aIsSame)
1078 {
1079  NS_ENSURE_ARG_POINTER(aNode1);
1080  NS_ENSURE_ARG_POINTER(aNode2);
1081 
1082  nsresult rv;
1083 #if DEBUG
1084  // Sanity check the node leaf names.
1085  nsString node1Name;
1086  rv = aNode1->GetLeafName(node1Name);
1087  NS_ENSURE_SUCCESS(rv, rv);
1088 
1089  nsString node2Name;
1090  rv = aNode2->GetLeafName(node2Name);
1091  NS_ENSURE_SUCCESS(rv, rv);
1092 
1093  if (!node1Name.Equals(node2Name)) {
1094  NS_WARNING("CompareNodes() was given two nodes w/o the same leaf name!");
1095  return NS_ERROR_FAILURE;
1096  }
1097 #endif
1098 
1099  PRInt64 node1Modify;
1100  rv = aNode1->GetLastModify(&node1Modify);
1101  NS_ENSURE_SUCCESS(rv, rv);
1102 
1103  PRInt64 node2Modify;
1104  rv = aNode2->GetLastModify(&node2Modify);
1105  NS_ENSURE_SUCCESS(rv, rv);
1106 
1107  *aIsSame = (node1Modify == node2Modify);
1108  return NS_OK;
1109 }
1110 
1111 nsresult
1113  EChangeType aChangeType,
1114  sbPathChangeArray & aChangeArray)
1115 {
1116  nsresult rv;
1117  while (!aContextStack.empty()) {
1118  NodeContext curNodeContext = aContextStack.top();
1119  aContextStack.pop();
1120 
1121  // First, create the change item.
1122  rv = AppendCreatePathChangeItem(curNodeContext.fullPath,
1123  aChangeType,
1124  aChangeArray);
1125  if (NS_FAILED(rv)) {
1126  NS_WARNING("Could not create a change item!");
1127  continue;
1128  }
1129 
1130  // Next, push all the child nodes of the current path onto the stack.
1131  sbNodeMap *childNodes = curNodeContext.node->GetChildren();
1132  if (!childNodes || childNodes->size() == 0) {
1133  continue;
1134  }
1135 
1136  nsString curContextPath = EnsureTrailingPath(curNodeContext.fullPath);
1137 
1138  sbNodeMapIter begin = childNodes->begin();
1139  sbNodeMapIter end = childNodes->end();
1141  for (next = begin; next != end; ++next) {
1142  nsString curChildPath(curContextPath);
1143  curChildPath.Append(next->first);
1144 
1145  aContextStack.push(NodeContext(curChildPath, next->second));
1146  }
1147  }
1148 
1149  return NS_OK;
1150 }
1151 
1152 /* static */ nsresult
1154  EChangeType aChangeType,
1155  sbNodeChangeArray & aChangeArray)
1156 {
1157  NS_ENSURE_ARG_POINTER(aChangedNode);
1158 
1159  nsRefPtr<sbFileSystemNodeChange> changedItem =
1160  new sbFileSystemNodeChange(aChangedNode, aChangeType);
1161  NS_ENSURE_TRUE(changedItem, NS_ERROR_OUT_OF_MEMORY);
1162 
1163  nsRefPtr<sbFileSystemNodeChange> *appendResult =
1164  aChangeArray.AppendElement(changedItem);
1165 
1166  return (appendResult ? NS_OK : NS_ERROR_FAILURE);
1167 }
1168 
1169 /* static */ nsresult
1171  EChangeType aChangeType,
1172  sbPathChangeArray & aChangeArray)
1173 {
1174  nsRefPtr<sbFileSystemPathChange> changedItem =
1175  new sbFileSystemPathChange(aEventPath, aChangeType);
1176  NS_ENSURE_TRUE(changedItem, NS_ERROR_OUT_OF_MEMORY);
1177 
1178  nsRefPtr<sbFileSystemPathChange> *appendResult =
1179  aChangeArray.AppendElement(changedItem);
1180 
1181  return (appendResult ? NS_OK : NS_ERROR_FAILURE);
1182 }
1183 
1184 nsString
1185 sbFileSystemTree::EnsureTrailingPath(const nsAString & aFilePath)
1186 {
1187  nsString resultPath(aFilePath);
1188  PRUint32 length = resultPath.Length();
1189  if (length > 0 && (resultPath[length - 1] != PATH_SEPERATOR_CHAR)) {
1190  resultPath.AppendLiteral(FILE_PATH_SEPARATOR);
1191  }
1192 
1193  return resultPath;
1194 }
1195 
GeneratorThread currentThread
nsresult SetListener(sbFileSystemTreeListener *aListener)
nsresult NotifyDirAdded(sbFileSystemNode *aAddedDirNode, nsAString &aFullPath)
nsresult GetNode(const nsAString &aPath, sbFileSystemNode *aRootSearchNode, sbFileSystemNode **aNodeRetVal)
NS_IMETHOD OnTreeSessionLoadError()=0
return NS_OK
_updateCookies aPath
nsresult SaveTreeSession(const nsID &aSessionID)
static nsresult GetPathEntries(const nsAString &aPath, nsISimpleEnumerator **aResultEnum)
nsresult GetChildren(const nsAString &aPath, sbFileSystemNode *aParentNode, sbNodeMap &aNodeMap)
nsresult InitWithTreeSession(nsID &aSessionID)
sbNodeMap * GetChildren()
nsresult do_GetProxyForObject(nsIEventTarget *aTarget, REFNSIID aIID, nsISupports *aObj, PRInt32 aProxyType, void **aProxyObject)
nsresult GetNodeChanges(sbFileSystemNode *aNode, const nsAString &aNodePath, sbNodeChangeArray &aOutChangeArray)
sbNodeMap::value_type sbNodeMapPair
nsresult GetTreeChanges(sbFileSystemNode *aOldRootNode, sbPathChangeArray &aOutChangeArray)
friend class sbFileSystemTreeState
nsresult GetLeafName(nsAString &aLeafName)
NS_DECL_ISUPPORTS NS_DECL_SBPIFILESYSTEMTREE nsresult Init(const nsAString &aPath, PRBool aIsRecursive)
NodeContext(const nsAString &aFullPath, sbFileSystemNode *aNode)
nsresult AddChildren(const nsAString &aPath, sbFileSystemNode *aParentNode, PRBool aBuildDiscoveredDirArray, PRBool aNotifyListener)
NS_IMPL_THREADSAFE_ISUPPORTS1(sbDeviceCapsCompatibility, sbIDeviceCapsCompatibility) sbDeviceCapsCompatibility
nsresult CreateNode(nsIFile *aFile, sbFileSystemNode *aParentNode, sbFileSystemNode **aNodeRetVal)
static nsresult AppendCreatePathChangeItem(const nsAString &aEventPath, EChangeType aChangeType, sbPathChangeArray &aChangeArray)
#define PATH_SEPERATOR_CHAR
std::map< nsString, nsRefPtr< sbFileSystemNode > > sbNodeMap
nsresult NotifyDirRemoved(sbFileSystemNode *aRemovedDirNode, nsAString &aFullPath)
void nsString_Split(const nsAString &aString, const nsAString &aDelimiter, nsTArray< nsString > &aSubStringArray)
nsRefPtr< sbFileSystemNode > node
sbNodeMap::const_iterator sbNodeMapIter
nsTArray< nsRefPtr< sbFileSystemPathChange > > sbPathChangeArray
virtual ~sbFileSystemTree()
nsTArray< nsRefPtr< sbFileSystemNodeChange > > sbNodeChangeArray
#define TRACE(args)
NS_IMETHOD OnRootPathMissing()=0
let node
std::stack< NodeContext > sbNodeContextStack
NS_IMETHOD OnTreeReady(const nsAString &aTreeRootPath, sbStringArray &aDirPathArray)=0
StringArrayEnumerator prototype hasMore
static nsresult CompareNodes(sbFileSystemNode *aNode1, sbFileSystemNode *aNode2, PRBool *aIsSame)
NS_IMETHOD GetChangeType(EChangeType *aChangeType)
nsresult Update(const nsAString &aPath)
nsresult GetLastModify(PRInt64 *aLastModify)
NS_IMETHOD OnChangeFound(const nsAString &aChangePath, EChangeType aChangeType)=0
_getSelectedPageStyle s i
nsString EnsureTrailingPath(const nsAString &aFilePath)
function next()
nsresult CreateTreeEvents(sbNodeContextStack &aContextStack, EChangeType aChangeType, sbPathChangeArray &aChangeArray)
static nsresult AppendCreateNodeChangeItem(sbFileSystemNode *aChangedNode, EChangeType aChangeType, sbNodeChangeArray &aChangeArray)