Remove debug statements from KCal backends.
[beagle.git] / beagled / FileSystemQueryable / DirectoryModel.cs
blobbbcdba8836384030011767d21e1d130458d69bf5
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 = FileSystem.GetDirectoryNameRootOk (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 {
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;
216 else
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;
235 cached_depth = -1;
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 ()
256 if (IsRoot)
257 rooted_to = null;
259 if (parent != null)
260 parent.children.Remove (name);
262 big_lock = null;
263 parent = null;
264 ExpireCached_Unlocked ();
267 private void Attach_Unlocked (DirectoryModel child)
269 string msg;
271 if (child.IsRoot) {
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;
292 child.parent = this;
294 this.children [child.name] = child;
297 ///////////////////////////////////////////////////////////
299 // Dealing with the children
301 public bool HasChildWithName (string child_name)
303 lock (big_lock)
304 return children != null && children.Contains (child_name);
307 public DirectoryModel GetChildByName (string child_name)
309 lock (big_lock) {
310 if (children != null)
311 return children [child_name] as DirectoryModel;
312 return null;
316 public int ChildCount {
317 get {
318 lock (big_lock)
319 return children != null ? children.Count : 0;
323 static private object [] empty_collection = new object [0];
324 public ICollection Children {
325 get {
326 lock (big_lock) {
327 if (children == null)
328 return empty_collection;
329 return children.Values;
334 public ICollection ChildrenCopy {
335 get {
336 lock (big_lock) {
337 if (children == null)
338 return empty_collection;
339 return new ArrayList (children.Values);
344 public DirectoryModel AddChild (string child_name, FileAttributes attr)
346 lock (big_lock) {
347 DirectoryModel child;
348 child = new DirectoryModel (attr);
350 child.name = child_name;
351 Attach_Unlocked (child);
353 return child;
357 public void Remove ()
359 lock (big_lock)
360 Detatch_Recursively_Unlocked ();
363 ///////////////////////////////////////////////////////////
365 // Moving stuff around
367 public void MoveTo (DirectoryModel new_parent, string new_name)
369 lock (big_lock)
370 Detatch_Unlocked ();
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)
390 lock (big_lock) {
392 path = StringFu.SanitizePath (path);
394 if (IsRoot) {
396 if (! Path.IsPathRooted (path)) {
397 string msg;
398 msg = String.Format ("Attempt to walk non-rooted path '{0}' on root node '{1}'",
399 path, FullName);
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
405 // this root.
406 if (! path.StartsWith (FullName))
407 return null;
409 if (path == FullName)
410 return this;
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)
421 return null;
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)) {
427 string msg;
428 msg = String.Format ("Attempt to walk rooted path '{0}' on non-root node '{1}'",
429 path, FullName);
430 throw new Exception (msg);
433 if (path.Length == 0)
434 return this;
436 // If we don't actually have any children, why bother?
437 if (children == null)
438 return null;
440 int i;
441 i = path.IndexOf (Path.DirectorySeparatorChar);
443 string child_name, rest_of_path;
444 if (i == -1) {
445 // This is the last path component
446 child_name = path;
447 rest_of_path = null;
448 } else {
449 child_name = path.Substring (0, i);
450 rest_of_path = path.Substring (i+1);
453 DirectoryModel dir;
454 dir = children [child_name] as DirectoryModel;
456 // No such child, so we give up.
457 if (dir == null)
458 return null;
460 if (rest_of_path != null)
461 dir = dir.WalkTree (rest_of_path);
463 return dir;
467 ///////////////////////////////////////////////////////////
469 // Our implementation of IComparable
471 public int CompareTo_Unlocked (object obj)
473 DirectoryModel other = obj as DirectoryModel;
474 if (other == null)
475 return 1;
477 int cmp;
479 cmp = DateTime.Compare (this.last_activity_time,
480 other.last_activity_time);
481 if (cmp != 0)
482 return cmp;
484 cmp = this.state - other.state;
485 if (cmp != 0)
486 return cmp;
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;
493 if (cmp != 0)
494 return cmp;
497 cmp = DateTime.Compare (other.last_crawl_time,
498 this.last_crawl_time);
499 if (cmp != 0)
500 return cmp;
502 cmp = other.Depth - this.Depth;
503 if (cmp != 0)
504 return cmp;
506 return other.Name.CompareTo (this.Name);
509 public int CompareTo (object obj)
511 lock (big_lock)
512 return CompareTo_Unlocked (obj);