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
= Path
.GetDirectoryName (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
{
175 get { return state != DirectoryState.Clean; }
178 public object WatchHandle
{
179 get { return watch_handle; }
180 set { watch_handle = value; }
183 public bool IsWatched
{
184 get { return watch_handle != null; }
187 public DirectoryModel Parent
{
188 get { return parent; }
191 public DateTime LastCrawlTime
{
192 get { return last_crawl_time; }
193 set { last_crawl_time = value; }
196 public DateTime LastActivityTime
{
197 get { return last_activity_time; }
198 set { last_activity_time = value; }
201 public bool IsAttached
{
202 get { return parent != null || rooted_to != null; }
205 ///////////////////////////////////////////////////////////
207 public void MarkAsClean ()
209 // If we aren't being watched, the "cleanest"
210 // state we can be in is PossiblyClean.
211 if (watch_handle
== null)
212 state
= DirectoryState
.PossiblyClean
;
214 state
= DirectoryState
.Clean
;
217 ///////////////////////////////////////////////////////////
219 private void ExpireCached_Unlocked ()
221 if (cached_full_name
!= null || cached_depth
>= 0) {
223 if (cached_full_name
!= null && ExpireEvent
!= null)
224 ExpireEvent (cached_full_name
, unique_id
);
226 cached_full_name
= null;
229 if (children
!= null)
230 foreach (DirectoryModel child
in children
.Values
)
231 child
.ExpireCached_Unlocked ();
235 ///////////////////////////////////////////////////////////
237 private void Detatch_Recursively_Unlocked ()
239 if (this.children
!= null)
240 foreach (DirectoryModel child
in new ArrayList (this.children
.Values
))
241 child
.Detatch_Recursively_Unlocked ();
243 this.Detatch_Unlocked ();
246 private void Detatch_Unlocked ()
252 parent
.children
.Remove (name
);
256 ExpireCached_Unlocked ();
259 private void Attach_Unlocked (DirectoryModel child
)
264 msg
= String
.Format ("Can't attach root node '{0}' to '{1}'",
265 child
.Name
, this.FullName
);
266 throw new Exception (msg
);
269 if (child
.parent
!= null) {
270 msg
= String
.Format ("Can't attach non-detatched node '{0}' to '{1}'",
271 child
.Name
, this.FullName
);
272 throw new Exception (msg
);
275 if (children
== null) {
276 children
= new Hashtable ();
277 } else if (children
.Contains (child
.Name
)) {
278 msg
= String
.Format ("'{0}' already contains a child named '{1}'",
279 this.FullName
, child
.Name
);
280 throw new Exception (msg
);
283 child
.big_lock
= this.big_lock
;
286 this.children
[child
.name
] = child
;
289 ///////////////////////////////////////////////////////////
291 // Dealing with the children
293 public bool HasChildWithName (string child_name
)
296 return children
!= null && children
.Contains (child_name
);
299 public DirectoryModel
GetChildByName (string child_name
)
302 if (children
!= null)
303 return children
[child_name
] as DirectoryModel
;
308 public int ChildCount
{
311 return children
!= null ? children
.Count
: 0;
315 static private object [] empty_collection
= new object [0];
316 public ICollection Children
{
319 if (children
== null)
320 return empty_collection
;
321 return children
.Values
;
326 public ICollection ChildrenCopy
{
329 if (children
== null)
330 return empty_collection
;
331 return new ArrayList (children
.Values
);
336 public DirectoryModel
AddChild (string child_name
, FileAttributes attr
)
339 DirectoryModel child
;
340 child
= new DirectoryModel (attr
);
342 child
.name
= child_name
;
343 Attach_Unlocked (child
);
349 public void Remove ()
352 Detatch_Recursively_Unlocked ();
355 ///////////////////////////////////////////////////////////
357 // Moving stuff around
359 public void MoveTo (DirectoryModel new_parent
, string new_name
)
364 // No need to lock anything here, since this node
365 // is just floating out in space.
366 if (new_name
!= null)
367 this.name
= new_name
;
369 lock (new_parent
.big_lock
)
370 new_parent
.Attach_Unlocked (this);
373 public void MoveTo (DirectoryModel new_parent
)
375 MoveTo (new_parent
, null);
378 ///////////////////////////////////////////////////////////
380 public DirectoryModel
WalkTree (string path
)
384 path
= StringFu
.SanitizePath (path
);
388 if (! Path
.IsPathRooted (path
)) {
390 msg
= String
.Format ("Attempt to walk non-rooted path '{0}' on root node '{1}'",
392 throw new Exception (msg
);
395 // If this is a root, we have to make sure
396 // that the path actually describes a file below
398 if (! path
.StartsWith (FullName
))
401 if (path
== FullName
)
404 // This is safe: Since we know that path != FullName but
405 // that path.StartsWith (FullName), we can conclude that
406 // path.Length > FullName.Length.
407 if (path
[FullName
.Length
] != Path
.DirectorySeparatorChar
)
410 // Drop the part of the path containing the root's path.
411 path
= path
.Substring (FullName
.Length
+1);
413 } else if (Path
.IsPathRooted (path
)) {
415 msg
= String
.Format ("Attempt to walk rooted path '{0}' on non-root node '{1}'",
417 throw new Exception (msg
);
420 if (path
.Length
== 0)
423 // If we don't actually have any children, why bother?
424 if (children
== null)
428 i
= path
.IndexOf (Path
.DirectorySeparatorChar
);
430 string child_name
, rest_of_path
;
432 // This is the last path component
436 child_name
= path
.Substring (0, i
);
437 rest_of_path
= path
.Substring (i
+1);
441 dir
= children
[child_name
] as DirectoryModel
;
443 // No such child, so we give up.
447 if (rest_of_path
!= null)
448 dir
= dir
.WalkTree (rest_of_path
);
454 ///////////////////////////////////////////////////////////
456 // Our implementation of IComparable
458 public int CompareTo_Unlocked (object obj
)
460 DirectoryModel other
= obj
as DirectoryModel
;
466 cmp
= DateTime
.Compare (this.last_activity_time
,
467 other
.last_activity_time
);
471 cmp
= this.state
- other
.state
;
475 cmp
= DateTime
.Compare (other
.last_crawl_time
,
476 this.last_crawl_time
);
480 cmp
= other
.Depth
- this.Depth
;
484 return other
.Name
.CompareTo (this.Name
);
487 public int CompareTo (object obj
)
490 return CompareTo_Unlocked (obj
);