cvsimport
[beagle.git] / bludgeon / FileModel.cs
blobc1f77b059a5f5e93e2d3cb942f5862a3f3f25a3e
2 using System;
3 using System.Collections;
4 using System.IO;
6 using Beagle.Util;
7 using Beagle;
9 namespace Bludgeon {
11 public class FileModel {
13 static Random random = new Random ();
14 static private ArrayList roots = new ArrayList ();
16 static public ICollection Roots { get { return roots; } }
18 static public void AddRoot (FileModel root)
20 if (! root.IsRoot)
21 throw new Exception ("Attempted to add non-root as root");
23 roots.Add (root);
26 static public FileModel PickRoot ()
28 if (roots.Count == 0)
29 return null;
30 else if (roots.Count == 1)
31 return roots [0] as FileModel;
33 return roots [random.Next (roots.Count)] as FileModel;
37 // Note that the probability of a FileModel being picked
38 // by any of these "picker" methods is not uniform.
39 // The two-step process where we first pick a root messes
40 // that up.
44 // Can be either a file or directory
45 static public FileModel PickNonRoot ()
47 FileModel root;
48 root = PickRoot ();
49 return root.PickDescendant ();
52 static public FileModel PickFile ()
54 FileModel root;
55 root = PickRoot ();
56 return root.PickFileDescendant ();
59 static public FileModel PickAnyDirectory ()
61 FileModel root;
62 root = PickRoot ();
63 return root.PickDirectory ();
66 static public FileModel PickNonRootDirectory ()
68 FileModel root;
69 root = PickRoot ();
70 return root.PickDirectoryDescendant ();
73 //////////////////////////////////////////////////////////////
75 // Properties of the root directory:
76 // name contains full path of BEAGLE_HOME
77 // parent is null
78 // body is null
79 // children is non-null
81 // Properties of a non-root directory:
82 // name is a token
83 // parent is non-null
84 // body is null
85 // children is non-null
87 // Properties of a file:
88 // name is a token
89 // parent is non-null, and is a directory
90 // body is non-null
91 // children is null
93 private string name = null;
94 private DateTime mtime;
95 private FileModel parent = null;
96 private string [] body = null;
97 private Hashtable children = null;
99 //////////////////////////////////////////////////////////////
101 public bool IsRoot {
102 get { return parent == null; }
105 public bool IsDirectory {
106 get { return body == null; }
109 public bool IsFile {
110 get { return body != null; }
113 public string Type {
114 get { return IsDirectory ? "directory" : "file"; }
117 public string Name {
118 get { return name; }
121 public DateTime Mtime {
122 get { return mtime; }
125 public string FullName {
126 get { return IsRoot ? Name : Path.Combine (parent.FullName, name); }
129 public string ShortName {
130 get { return IsRoot ? Path.GetFileName (Name) : Path.Combine (parent.ShortName, name); }
133 public Uri Uri {
134 get { return UriFu.PathToFileUri (FullName); }
137 public string [] Body {
138 get { return body; }
141 public FileModel Parent {
142 get { return parent; }
145 public ICollection Children {
146 get { return children.Values; }
149 public int Size {
150 get {
151 if (IsFile)
152 return 1;
153 int sum = 0;
154 if (IsDirectory)
155 sum = 1; // count ourselves
156 foreach (FileModel child in children.Values)
157 sum += child.Size;
158 return sum;
162 // IsAbove is easier to think about than IsAncestorOf
163 public bool IsAbove (FileModel other)
165 return this == other.parent
166 || (other.parent != null && IsAbove (other.parent));
169 // IsBelow is easier to thik about than IsDescendantOf
170 public bool IsBelow (FileModel other)
172 return other.IsAbove (this);
175 //////////////////////////////////////////////////////////////
177 private void RecursiveListAdd (ArrayList list,
178 bool add_self,
179 bool add_dirs,
180 bool add_files)
182 if (add_self && ((add_dirs && IsDirectory) || (add_files && IsFile)))
183 list.Add (this);
185 if (children != null)
186 foreach (FileModel file in children.Values)
187 file.RecursiveListAdd (list, true, add_dirs, add_files);
190 public ArrayList GetDescendants ()
192 ArrayList list = new ArrayList ();
193 RecursiveListAdd (list, false, true, true);
194 return list;
197 public ArrayList GetFileDescendants ()
199 ArrayList list = new ArrayList ();
200 RecursiveListAdd (list, false, false, true);
201 return list;
204 // Includes ourself
205 public ArrayList GetDirectories ()
207 ArrayList list = new ArrayList ();
208 RecursiveListAdd (list, true, true, false);
209 return list;
212 // Never includes ourself
213 public ArrayList GetDirectoryDescendants ()
215 ArrayList list = new ArrayList ();
216 RecursiveListAdd (list, false, true, false);
217 return list;
220 //////////////////////////////////////////////////////////////
222 public FileModel PickDescendant ()
224 ArrayList all;
225 all = GetDescendants ();
226 if (all.Count == 0)
227 return null;
228 return all [random.Next (all.Count)] as FileModel;
231 public FileModel PickFileDescendant ()
233 ArrayList all;
234 all = GetFileDescendants ();
235 if (all.Count == 0)
236 return null;
237 return all [random.Next (all.Count)] as FileModel;
240 public FileModel PickDirectoryDescendant ()
242 ArrayList all;
243 all = GetDirectoryDescendants ();
244 if (all.Count == 0)
245 return null;
246 return all [random.Next (all.Count)] as FileModel;
249 // This can return the root
250 public FileModel PickDirectory ()
252 ArrayList all;
253 all = GetDirectories ();
254 return all [random.Next (all.Count)] as FileModel;
257 //////////////////////////////////////////////////////////////
259 public bool BodyContains (string token)
261 if (body == null)
262 return false;
264 // FIXME: Do a binary search (or something smarter)
265 // instead
266 for (int i = 0; i < body.Length; ++i)
267 if (body [i] == token)
268 return true;
269 return false;
272 public bool Contains (string token)
274 return name == token || BodyContains (token);
277 //////////////////////////////////////////////////////////////
279 static DateTime base_time;
280 const int seconds_per_year = 60 * 60 * 24 * 365;
282 // Picks a random time in the last year
283 static public DateTime PickDateTime ()
285 return base_time.AddSeconds (- random.Next (seconds_per_year));
288 //////////////////////////////////////////////////////////////
290 static FileModel ()
292 base_time = DateTime.Now;
295 private FileModel ()
297 // Pick a random timestamp for every file.
298 mtime = PickDateTime ();
301 public static FileModel NewRoot ()
303 FileModel root = new FileModel ();
304 root.name = PathFinder.HomeDir;
305 root.children = new Hashtable ();
306 root.mtime = Directory.GetLastWriteTimeUtc (root.FullName);
307 return root;
310 // Creates a randomly-named new directory.
311 // Avoid name collisions with existing files.
312 public FileModel NewDirectory ()
314 if (! IsDirectory)
315 throw new ArgumentException ("parent must be a directory");
317 // no more names left
318 if (children.Count == Token.Count)
319 return null;
321 FileModel child;
322 child = new FileModel ();
323 child.name = PickName (this);
324 child.children = new Hashtable ();
326 child.parent = this;
327 children [child.name] = child;
329 // Actually create the directory
330 Directory.CreateDirectory (child.FullName);
331 child.mtime = Directory.GetLastWriteTimeUtc (child.FullName);
333 return child;
336 public FileModel NewFile ()
338 if (! IsDirectory)
339 throw new ArgumentException ("parent must be a directory");
341 // no more names left
342 if (children.Count == Token.Count)
343 return null;
345 FileModel child;
346 child = new FileModel ();
347 child.name = PickName (this);
348 child.body = NewBody (10);
350 child.parent = this;
351 children [child.name] = child;
353 // Create the file
354 child.Write ();
356 return child;
359 //////////////////////////////////////////////////////////////
361 // Mutate the tree
363 public void Grow (int depth)
365 const int num_dirs = 2;
366 const int num_files = 5;
368 if (depth > 0) {
369 for (int i = 0; i < num_dirs; ++i) {
370 FileModel file;
371 file = NewDirectory ();
372 if (file != null)
373 file.Grow (depth - 1);
377 for (int i = 0; i < num_files; ++i)
378 NewFile ();
381 //////////////////////////////////////////////////////////////
383 // Basic file system operations
385 public void Touch ()
387 if (IsFile) {
388 body = NewBody (10);
389 Write ();
393 public void Delete ()
395 if (IsRoot)
396 throw new Exception ("Can't delete the root!");
398 if (IsDirectory)
399 Directory.Delete (FullName, true); // recursive
400 else
401 File.Delete (FullName);
403 parent.children.Remove (name);
404 parent = null;
407 // If the move would cause a filename collision or is
408 // otherwise impossible, return false. Return true if the
409 // move actually happens.
410 public bool MoveTo (FileModel new_parent, // or null, to just rename
411 string new_name) // or null, to just move
413 if (! new_parent.IsDirectory)
414 throw new ArgumentException ("Parent must be a directory");
416 if (this.IsRoot)
417 throw new ArgumentException ("Can't move a root");
419 // Impossible
420 if (this == new_parent || this.IsAbove (new_parent))
421 return false;
423 string old_path;
424 old_path = this.FullName;
426 if (new_parent == null)
427 new_parent = this.parent;
428 if (new_name == null)
429 new_name = this.name;
431 // check for a filename collision
432 if (new_parent.children.Contains (new_name))
433 return false;
435 // modify the data structure
436 this.parent.children.Remove (this.name);
437 this.parent = new_parent;
438 this.name = new_name;
439 this.parent.children [this.name] = this;
441 string new_path;
442 new_path = Path.Combine (new_parent.FullName, new_name);
444 if (this.IsDirectory)
445 Directory.Move (old_path, new_path);
446 else
447 File.Move (old_path, new_path);
449 return true;
453 //////////////////////////////////////////////////////////////
455 // Useful utility functions
457 static private string PickName (FileModel p)
459 string pick;
460 do {
461 pick = Token.GetRandom ();
462 } while (p.children.Contains (pick));
463 return pick;
466 static private string [] NewBody (int size)
468 string [] body;
469 body = new string [size];
470 for (int i = 0; i < size; ++i)
471 body [i] = Token.GetRandom ();
472 Array.Sort (body);
473 return body;
476 private void Write ()
478 TextWriter writer;
479 writer = new StreamWriter (FullName);
480 for (int i = 0; i < body.Length; ++i)
481 writer.WriteLine (body [i]);
482 writer.Close ();
484 FileInfo info;
485 info = new FileInfo (FullName);
486 info.LastWriteTime = Mtime;
489 //////////////////////////////////////////////////////////////
492 // Code to determine a a file will match a particular query
495 private bool MatchesQueryPart (QueryPart abstract_part)
497 bool is_match;
498 is_match = false;
500 if (abstract_part is QueryPart_Text) {
501 QueryPart_Text part;
502 part = abstract_part as QueryPart_Text;
504 if ((part.SearchTextProperties && Name == part.Text)
505 || (part.SearchFullText && BodyContains (part.Text)))
506 is_match = true;
508 } else if (abstract_part is QueryPart_Or) {
509 QueryPart_Or part;
510 part = abstract_part as QueryPart_Or;
512 foreach (QueryPart sub_part in part.SubParts) {
513 if (MatchesQueryPart (sub_part)) {
514 is_match = true;
515 break;
518 } else if (abstract_part is QueryPart_Property) {
519 QueryPart_Property part;
520 part = abstract_part as QueryPart_Property;
522 if (part.Key == "beagle:MimeType") {
523 if (part.Value == "inode/directory")
524 is_match = IsDirectory;
525 else if (part.Value == "text/plain")
526 is_match = IsFile;
527 else
528 is_match = false;
529 } else if (part.Key == "beagle:ExactFilename") {
530 is_match = (Name == part.Value);
531 } else {
532 throw new Exception ("Unsupported property " + part.Key);
534 } else if (abstract_part is QueryPart_DateRange) {
535 QueryPart_DateRange part;
536 part = abstract_part as QueryPart_DateRange;
538 // FIXME: We assume that the query refers to the file timestamp.
539 // Instead, we should explicitly check part.Key.
540 is_match = (part.StartDate <= Mtime && Mtime <= part.EndDate);
542 } else {
543 throw new Exception ("Unsupported part");
546 if (abstract_part.Logic == QueryPartLogic.Prohibited)
547 is_match = ! is_match;
549 return is_match;
552 public bool MatchesQuery (Query query)
554 // We assume the root node never matches any query.
555 if (IsRoot)
556 return false;
558 foreach (QueryPart part in query.Parts) {
559 if (! MatchesQueryPart (part))
560 return false;
563 return true;
566 private void RecursiveQueryCheck (ArrayList match_list, Query query)
568 if (MatchesQuery (query))
569 match_list.Add (this);
571 if (children != null)
572 foreach (FileModel file in children.Values)
573 file.RecursiveQueryCheck (match_list, query);
576 public ArrayList GetMatchingDescendants (Query query)
578 ArrayList match_list;
579 match_list = new ArrayList ();
580 RecursiveQueryCheck (match_list, query);
581 return match_list;