Thumbnail file hits. Based on a patch from D Bera
[beagle.git] / beagled / FileSystemQueryable / DirectoryModel.cs
blobe4e0df9b481ea8c078e678551bdad8826abbf94b
1 //
2 // DirectoryModel.cs
3 //
4 // Copyright (C) 2005 Novell, Inc.
5 //
7 //
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.
27 using System;
28 using System.Collections;
29 using System.IO;
31 using Beagle.Daemon;
32 using Beagle.Util;
34 namespace Beagle.Daemon.FileSystemQueryable {
36 public class DirectoryModel : IComparable {
38 // The ExpireEvent is fired whenever a directory's name
39 // changes.
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;
51 private string name;
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;
69 if (attr != null) {
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);
87 DirectoryModel root;
88 root = new DirectoryModel (attr);
90 root.big_lock = big_lock;
91 root.rooted_to = Path.GetDirectoryName (path);
92 root.name = Path.GetFileName (path);
94 return root;
97 public bool IsRoot {
98 get { return rooted_to != null; }
101 ///////////////////////////////////////////////////////////
103 public string Name {
104 get { return name; }
106 set {
107 lock (big_lock) {
108 if (name == value)
109 return;
110 if (parent != null) {
111 if (parent.children.Contains (value)) {
112 string msg;
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);
120 name = value;
121 ExpireCached_Unlocked ();
122 if (parent != null)
123 parent.children [name] = this;
128 public string FullName {
129 get {
130 if (!IsAttached)
131 return "_HUH_NO_WAY_ARE_YOU_KIDDING_ME_";
132 lock (big_lock) {
133 if (cached_full_name == null) {
134 string directly_above;
135 if (IsRoot)
136 directly_above = rooted_to;
137 else
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; }
148 set {
149 if (unique_id != Guid.Empty)
150 throw new Exception ("Don't change the unique id!");
151 unique_id = value;
155 public int Depth {
156 get {
157 lock (big_lock) {
158 if (cached_depth < 0) {
159 if (IsRoot)
160 cached_depth = 0;
161 else
162 cached_depth = 1 + parent.Depth;
164 return cached_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;
213 else
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;
227 cached_depth = -1;
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 ()
248 if (IsRoot)
249 rooted_to = null;
251 if (parent != null)
252 parent.children.Remove (name);
254 big_lock = null;
255 parent = null;
256 ExpireCached_Unlocked ();
259 private void Attach_Unlocked (DirectoryModel child)
261 string msg;
263 if (child.IsRoot) {
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;
284 child.parent = this;
286 this.children [child.name] = child;
289 ///////////////////////////////////////////////////////////
291 // Dealing with the children
293 public bool HasChildWithName (string child_name)
295 lock (big_lock)
296 return children != null && children.Contains (child_name);
299 public DirectoryModel GetChildByName (string child_name)
301 lock (big_lock) {
302 if (children != null)
303 return children [child_name] as DirectoryModel;
304 return null;
308 public int ChildCount {
309 get {
310 lock (big_lock)
311 return children != null ? children.Count : 0;
315 static private object [] empty_collection = new object [0];
316 public ICollection Children {
317 get {
318 lock (big_lock) {
319 if (children == null)
320 return empty_collection;
321 return children.Values;
326 public ICollection ChildrenCopy {
327 get {
328 lock (big_lock) {
329 if (children == null)
330 return empty_collection;
331 return new ArrayList (children.Values);
336 public DirectoryModel AddChild (string child_name, FileAttributes attr)
338 lock (big_lock) {
339 DirectoryModel child;
340 child = new DirectoryModel (attr);
342 child.name = child_name;
343 Attach_Unlocked (child);
345 return child;
349 public void Remove ()
351 lock (big_lock)
352 Detatch_Recursively_Unlocked ();
355 ///////////////////////////////////////////////////////////
357 // Moving stuff around
359 public void MoveTo (DirectoryModel new_parent, string new_name)
361 lock (big_lock)
362 Detatch_Unlocked ();
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)
382 lock (big_lock) {
384 path = StringFu.SanitizePath (path);
386 if (IsRoot) {
388 if (! Path.IsPathRooted (path)) {
389 string msg;
390 msg = String.Format ("Attempt to walk non-rooted path '{0}' on root node '{1}'",
391 path, FullName);
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
397 // this root.
398 if (! path.StartsWith (FullName))
399 return null;
401 if (path == FullName)
402 return this;
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)
408 return null;
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)) {
414 string msg;
415 msg = String.Format ("Attempt to walk rooted path '{0}' on non-root node '{1}'",
416 path, FullName);
417 throw new Exception (msg);
420 if (path.Length == 0)
421 return this;
423 // If we don't actually have any children, why bother?
424 if (children == null)
425 return null;
427 int i;
428 i = path.IndexOf (Path.DirectorySeparatorChar);
430 string child_name, rest_of_path;
431 if (i == -1) {
432 // This is the last path component
433 child_name = path;
434 rest_of_path = null;
435 } else {
436 child_name = path.Substring (0, i);
437 rest_of_path = path.Substring (i+1);
440 DirectoryModel dir;
441 dir = children [child_name] as DirectoryModel;
443 // No such child, so we give up.
444 if (dir == null)
445 return null;
447 if (rest_of_path != null)
448 dir = dir.WalkTree (rest_of_path);
450 return dir;
454 ///////////////////////////////////////////////////////////
456 // Our implementation of IComparable
458 public int CompareTo_Unlocked (object obj)
460 DirectoryModel other = obj as DirectoryModel;
461 if (other == null)
462 return 1;
464 int cmp;
466 cmp = DateTime.Compare (this.last_activity_time,
467 other.last_activity_time);
468 if (cmp != 0)
469 return cmp;
471 cmp = this.state - other.state;
472 if (cmp != 0)
473 return cmp;
475 cmp = DateTime.Compare (other.last_crawl_time,
476 this.last_crawl_time);
477 if (cmp != 0)
478 return cmp;
480 cmp = other.Depth - this.Depth;
481 if (cmp != 0)
482 return cmp;
484 return other.Name.CompareTo (this.Name);
487 public int CompareTo (object obj)
489 lock (big_lock)
490 return CompareTo_Unlocked (obj);