4 // Copyright (C) 2005 Novell, Inc.
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
28 using System
.Collections
;
34 namespace Beagle
.Daemon
.FileSystemQueryable
{
36 public class DirectoryModel
: IComparable
{
38 // The ExpireEvent is fired whenever a directory's name
40 public delegate void ExpireHandler (string expired_path
, Guid unique_id
);
41 public static ExpireHandler ExpireEvent
;
43 private object big_lock
;
45 // Exactly one of the following two must be non-null.
46 // If parent != null, this is a subdirectory of another DirectoryModel.
47 // If rooted_to != null, this is the root for a directory tree.
48 private DirectoryModel parent
;
49 private string rooted_to
;
52 private Guid unique_id
;
54 private DirectoryState state
;
55 private object watch_handle
;
56 private DateTime last_crawl_time
;
57 private DateTime last_activity_time
;
59 private string cached_full_name
;
60 private int cached_depth
;
62 private Hashtable children
= null;
64 private DirectoryModel (FileAttributes attr
)
66 // Always assume an unknown state
67 this.state
= DirectoryState
.Unknown
;
70 this.unique_id
= attr
.UniqueId
;
72 // Since we don't use the Mtime on directories,
73 // we can safely store the last crawl time in it.
74 this.last_crawl_time
= attr
.LastWriteTime
;
77 this.cached_full_name
= null;
78 this.cached_depth
= -1;
81 ///////////////////////////////////////////////////////////
83 static public DirectoryModel
NewRoot (object big_lock
, string path
, FileAttributes attr
)
85 path
= StringFu
.SanitizePath (path
);
88 root
= new DirectoryModel (attr
);
90 root
.big_lock
= big_lock
;
91 root
.rooted_to
= FileSystem
.GetDirectoryNameRootOk (path
);
92 root
.name
= Path
.GetFileName (path
);
98 get { return rooted_to != null; }
101 ///////////////////////////////////////////////////////////
110 if (parent
!= null) {
111 if (parent
.children
.Contains (value)) {
113 msg
= String
.Format ("'{0}' already contains a child named '{1}'",
114 parent
.FullName
, value);
115 throw new Exception (msg
);
118 parent
.children
.Remove (name
);
121 ExpireCached_Unlocked ();
123 parent
.children
[name
] = this;
128 public string FullName
{
131 return "_HUH_NO_WAY_ARE_YOU_KIDDING_ME_";
133 if (cached_full_name
== null) {
134 string directly_above
;
136 directly_above
= rooted_to
;
138 directly_above
= parent
.FullName
;
139 cached_full_name
= Path
.Combine (directly_above
, name
);
141 return cached_full_name
;
146 public Guid UniqueId
{
147 get { return unique_id; }
149 if (unique_id
!= Guid
.Empty
)
150 throw new Exception ("Don't change the unique id!");
158 if (cached_depth
< 0) {
162 cached_depth
= 1 + parent
.Depth
;
169 public DirectoryState State
{
170 get { return state; }
171 set { state = value; }
174 public bool NeedsCrawl
{
176 return state
!= DirectoryState
.Clean
177 && state
!= DirectoryState
.Uncrawlable
;
181 public object WatchHandle
{
182 get { return watch_handle; }
183 set { watch_handle = value; }
186 public bool IsWatched
{
187 get { return watch_handle != null; }
190 public DirectoryModel Parent
{
191 get { return parent; }
194 public DateTime LastCrawlTime
{
195 get { return last_crawl_time; }
196 set { last_crawl_time = value; }
199 public DateTime LastActivityTime
{
200 get { return last_activity_time; }
201 set { last_activity_time = value; }
204 public bool IsAttached
{
205 get { return parent != null || rooted_to != null; }
208 ///////////////////////////////////////////////////////////
210 public void MarkAsClean ()
212 // If we aren't being watched, the "cleanest"
213 // state we can be in is PossiblyClean.
214 if (watch_handle
== null)
215 state
= DirectoryState
.PossiblyClean
;
217 state
= DirectoryState
.Clean
;
220 public void MarkAsUncrawlable ()
222 state
= DirectoryState
.Uncrawlable
;
225 ///////////////////////////////////////////////////////////
227 private void ExpireCached_Unlocked ()
229 if (cached_full_name
!= null || cached_depth
>= 0) {
231 if (cached_full_name
!= null && ExpireEvent
!= null)
232 ExpireEvent (cached_full_name
, unique_id
);
234 cached_full_name
= null;
237 if (children
!= null)
238 foreach (DirectoryModel child
in children
.Values
)
239 child
.ExpireCached_Unlocked ();
243 ///////////////////////////////////////////////////////////
245 private void Detatch_Recursively_Unlocked ()
247 if (this.children
!= null)
248 foreach (DirectoryModel child
in new ArrayList (this.children
.Values
))
249 child
.Detatch_Recursively_Unlocked ();
251 this.Detatch_Unlocked ();
254 private void Detatch_Unlocked ()
260 parent
.children
.Remove (name
);
264 ExpireCached_Unlocked ();
267 private void Attach_Unlocked (DirectoryModel child
)
272 msg
= String
.Format ("Can't attach root node '{0}' to '{1}'",
273 child
.Name
, this.FullName
);
274 throw new Exception (msg
);
277 if (child
.parent
!= null) {
278 msg
= String
.Format ("Can't attach non-detatched node '{0}' to '{1}'",
279 child
.Name
, this.FullName
);
280 throw new Exception (msg
);
283 if (children
== null) {
284 children
= new Hashtable ();
285 } else if (children
.Contains (child
.Name
)) {
286 msg
= String
.Format ("'{0}' already contains a child named '{1}'",
287 this.FullName
, child
.Name
);
288 throw new Exception (msg
);
291 child
.big_lock
= this.big_lock
;
294 this.children
[child
.name
] = child
;
297 ///////////////////////////////////////////////////////////
299 // Dealing with the children
301 public bool HasChildWithName (string child_name
)
304 return children
!= null && children
.Contains (child_name
);
307 public DirectoryModel
GetChildByName (string child_name
)
310 if (children
!= null)
311 return children
[child_name
] as DirectoryModel
;
316 public int ChildCount
{
319 return children
!= null ? children
.Count
: 0;
323 static private object [] empty_collection
= new object [0];
324 public ICollection Children
{
327 if (children
== null)
328 return empty_collection
;
329 return children
.Values
;
334 public ICollection ChildrenCopy
{
337 if (children
== null)
338 return empty_collection
;
339 return new ArrayList (children
.Values
);
344 public DirectoryModel
AddChild (string child_name
, FileAttributes attr
)
347 DirectoryModel child
;
348 child
= new DirectoryModel (attr
);
350 child
.name
= child_name
;
351 Attach_Unlocked (child
);
357 public void Remove ()
360 Detatch_Recursively_Unlocked ();
363 ///////////////////////////////////////////////////////////
365 // Moving stuff around
367 public void MoveTo (DirectoryModel new_parent
, string new_name
)
372 // No need to lock anything here, since this node
373 // is just floating out in space.
374 if (new_name
!= null)
375 this.name
= new_name
;
377 lock (new_parent
.big_lock
)
378 new_parent
.Attach_Unlocked (this);
381 public void MoveTo (DirectoryModel new_parent
)
383 MoveTo (new_parent
, null);
386 ///////////////////////////////////////////////////////////
388 public DirectoryModel
WalkTree (string path
)
392 path
= StringFu
.SanitizePath (path
);
396 if (! Path
.IsPathRooted (path
)) {
398 msg
= String
.Format ("Attempt to walk non-rooted path '{0}' on root node '{1}'",
400 throw new Exception (msg
);
403 // If this is a root, we have to make sure
404 // that the path actually describes a file below
406 if (! path
.StartsWith (FullName
))
409 if (path
== FullName
)
412 // The root directory is special in that it's the only
413 // path that contains a trailing slash. It has to be
414 // handled specially. What a pain.
415 string fn
= FullName
== "/" ? "" : FullName
;
417 // This is safe: Since we know that path != FullName but
418 // that path.StartsWith (FullName), we can conclude that
419 // path.Length > FullName.Length.
420 if (path
[fn
.Length
] != Path
.DirectorySeparatorChar
)
423 // Drop the part of the path containing the root's path.
424 path
= path
.Substring (fn
.Length
+1);
426 } else if (Path
.IsPathRooted (path
)) {
428 msg
= String
.Format ("Attempt to walk rooted path '{0}' on non-root node '{1}'",
430 throw new Exception (msg
);
433 if (path
.Length
== 0)
436 // If we don't actually have any children, why bother?
437 if (children
== null)
441 i
= path
.IndexOf (Path
.DirectorySeparatorChar
);
443 string child_name
, rest_of_path
;
445 // This is the last path component
449 child_name
= path
.Substring (0, i
);
450 rest_of_path
= path
.Substring (i
+1);
454 dir
= children
[child_name
] as DirectoryModel
;
456 // No such child, so we give up.
460 if (rest_of_path
!= null)
461 dir
= dir
.WalkTree (rest_of_path
);
467 ///////////////////////////////////////////////////////////
469 // Our implementation of IComparable
471 public int CompareTo_Unlocked (object obj
)
473 DirectoryModel other
= obj
as DirectoryModel
;
479 cmp
= DateTime
.Compare (this.last_activity_time
,
480 other
.last_activity_time
);
484 cmp
= this.state
- other
.state
;
488 // Special case: index all directories at depth 1
489 // before any others.
490 if (this.Depth
<= 1 || other
.Depth
<= 1) {
491 cmp
= other
.Depth
- this.Depth
;
497 cmp
= DateTime
.Compare (other
.last_crawl_time
,
498 this.last_crawl_time
);
502 cmp
= other
.Depth
- this.Depth
;
506 return other
.Name
.CompareTo (this.Name
);
509 public int CompareTo (object obj
)
512 return CompareTo_Unlocked (obj
);