3 using System
.Collections
;
12 Changes to the file system are made when the object is added to
13 a rooted tree. Roots are automatically instantiated when they
14 are created, and are never associated with any events.
19 abstract public class FileSystemObject
{
21 // TURN THIS ON TO TEST THE ARCHIVE PATCH!
22 static public bool SearchInArchives
= false;
24 private static int next_id
= 0;
26 private FileSystemObject parent
;
27 private string base_name
;
30 protected Hashtable children
;
31 protected DateTime timestamp
;
33 ////////////////////////////////////////////////////
35 static DateTime base_time
;
36 const int seconds_per_year
= 60 * 60 * 24 * 365;
38 static FileSystemObject ()
40 base_time
= DateTime
.Now
;
43 static public DateTime
PickTimestamp ()
45 Random random
= new Random ();
46 return base_time
.AddSeconds (- random
.Next (seconds_per_year
));
49 static public void PickTimestampRange (out DateTime a
, out DateTime b
)
60 protected FileSystemObject ()
65 protected void PickName ()
70 base_name
= Token
.GetRandomWithUnicode ();
75 protected void ClearName ()
80 ////////////////////////////////////////////////////
82 public FileSystemObject Parent
{
83 get { return parent; }
86 virtual public bool IsRoot
{
90 // Returns true if this object is part of a tree with
92 public bool IsRooted
{
94 FileSystemObject curr
;
96 while (curr
!= null) {
105 // By definition, an object is an ancestor of itself.
106 public bool IsAncestorOf (FileSystemObject fso
)
108 if (! this.HasChildren
)
110 while (fso
!= null) {
118 // Is this an archive?
119 public bool IsArchive
{
121 return HasChildren
&& (this is FileObject
);
125 // Is this FileSystemObject actually a child of an archive?
126 public bool IsInArchive
{
128 return Parent
!= null && (Parent
is FileObject
|| Parent
.IsInArchive
);
132 public bool IsReadable
{
134 Mono
.Unix
.Native
.Stat stat
;
136 Mono
.Unix
.Native
.Syscall
.stat (Name
, out stat
);
138 if ((stat
.st_mode
& Mono
.Unix
.Native
.FilePermissions
.S_IRUSR
) != 0)
145 public bool IsWritable
{
147 Mono
.Unix
.Native
.Stat stat
;
149 Mono
.Unix
.Native
.Syscall
.stat (Name
, out stat
);
151 if ((stat
.st_mode
& Mono
.Unix
.Native
.FilePermissions
.S_IWUSR
) != 0)
158 virtual public string Name
{
161 name
= String
.Format ("{0}.{1}{2}",
162 base_name
, id
, Extension
!= null ? Extension
: "");
167 virtual public string ShortName
{
171 FileSystemObject fso
= this;
172 StringBuilder sb
= new StringBuilder ();
173 while (fso
!= null && ! fso
.IsRoot
) {
176 sb
.Insert (0, fso
.Name
);
179 return sb
.ToString ();
183 virtual public string Extension
{
187 virtual protected string GetChildUri (FileSystemObject child
)
189 throw new Exception ("Invalid GetChildUri call");
192 virtual public string Uri
{
195 return "floating://" + Name
;
196 return parent
.GetChildUri (this);
200 // This should return null for objects that are not directly
201 // represented on the file system (i.e. files inside an archive)
202 virtual protected string GetChildFullName (FileSystemObject child
)
204 return Path
.Combine (this.FullName
, child
.Name
);
207 virtual public string FullName
{
211 return parent
.GetChildFullName (this);
215 abstract public string MimeType { get; }
217 public DateTime Timestamp
{
218 get { return timestamp; }
221 public bool HasChildren
{
222 get { return children != null; }
225 public int ChildCount
{
226 get { return children != null ? children.Count : 0; }
229 public ICollection Children
{
231 if (children
== null)
232 throw new Exception ("Invalid request for children on " + Uri
);
233 return children
.Values
;
237 public FileSystemObject
GetChild (string name
)
239 if (children
== null)
240 throw new Exception ("Invalid request for child '" + name
+ "' on " + Uri
);
241 return children
[name
] as FileSystemObject
;
244 public virtual void AddChild (FileSystemObject child
, EventTracker tracker
)
246 if (children
== null)
247 throw new Exception ("Can't add a child to " + Uri
);
248 if (child
.parent
!= null)
249 throw new Exception ("Can't add parented child " + child
.Uri
+ " to " + Uri
);
251 // FIXME: Need to handle the case of the added child
252 // clobbering another w/ the same name.
255 children
[child
.Name
] = child
;
258 child
.AddOnDisk (tracker
);
261 public virtual void ClobberingAddChild (FileSystemObject child
, FileSystemObject victim
, EventTracker tracker
)
263 if (children
== null)
264 throw new Exception ("Can't add a child to " + Uri
);
265 if (child
.parent
!= null)
266 throw new Exception ("Can't add parented child " + child
.Uri
+ " to " + Uri
);
267 if (victim
.parent
!= this)
268 throw new Exception ("Victim " + victim
.Uri
+ " is not a child of " + Uri
);
269 if (child
.Extension
!= victim
.Extension
)
270 throw new Exception ("Extension mismatch: " + child
.Extension
+ " vs. " + victim
.Extension
);
272 victim
.parent
= null;
274 child
.id
= victim
.id
;
275 child
.base_name
= victim
.base_name
;
277 children
[child
.Name
] = child
;
280 child
.AddOnDisk (tracker
);
283 public virtual void RemoveChild (FileSystemObject child
, EventTracker tracker
)
285 if (child
.parent
!= this)
286 throw new Exception (child
.Uri
+ " is not a child of " + Uri
);
289 child
.DeleteOnDisk (tracker
);
292 children
.Remove (child
.Name
);
295 public virtual void MoveChild (FileSystemObject child
, FileSystemObject new_parent
, EventTracker tracker
)
297 if (child
.parent
!= this)
298 throw new Exception (child
.Uri
+ " is not a child of " + Uri
);
300 if (new_parent
== null || new_parent
== child
.parent
)
303 // We can't move child into new_parent if child is
304 // already above new_parent in the tree.
305 if (child
.IsAncestorOf (new_parent
))
306 throw new Exception ("Can't move " + child
.Uri
+ " to " + new_parent
.Uri
);
308 string old_full_name
;
309 old_full_name
= child
.FullName
;
311 // FIXME: We need to handle the case of the moved
312 // child clobbering another w/ the same name.
314 child
.parent
= new_parent
;
315 this.children
.Remove (child
.Name
);
316 new_parent
.children
[child
.Name
] = child
;
318 // FIXME: What if this is not rooted, but new_parent is?
319 if (new_parent
.IsRooted
)
320 child
.MoveOnDisk (old_full_name
, tracker
);
323 ////////////////////////////////////////////////////
325 protected void AllowChildren ()
327 children
= new Hashtable ();
330 ////////////////////////////////////////////////////
332 // n.b. These shouldn't be public, but they have to be so that directories
333 // can manipulate their children.
335 // We assume that the FileSystemObject is in the tree when this is called.
336 virtual public void AddOnDisk (EventTracker tracker
)
338 throw new Exception ("AddOnDisk undefined for " + FullName
);
341 // We assume that the FileSystemObject is still in the tree (has a .parent
342 // set, etc.) when we call this.
343 virtual public void DeleteOnDisk (EventTracker tracker
)
345 throw new Exception ("DeleteOnDisk undefined for " + FullName
);
348 // We assume that the FileSystemObject is in the tree, it its new position
349 // when we call this.
350 virtual public void MoveOnDisk (string old_full_name
, EventTracker tracker
)
352 throw new Exception ("MoveOnDisk undefined for " + FullName
);
355 // This checks that our on-disk state matches our tree state.
356 virtual public bool VerifyOnDisk ()
358 throw new Exception ("VerifyOnDisk undefined for " + FullName
);
361 ////////////////////////////////////////////////////
363 abstract protected bool MatchesQueryPart (QueryPart part
);
366 // 1 if it is a match
367 // 0 if it doesn't apply
368 // -1 if it doesn't match
369 private int MatchesMetadata (QueryPart abstract_part
)
373 if (abstract_part
is QueryPart_Text
) {
376 part
= (QueryPart_Text
) abstract_part
;
378 if (part
.SearchTextProperties
&& part
.Text
== base_name
)
381 } else if (abstract_part
is QueryPart_Property
) {
383 QueryPart_Property part
;
384 part
= (QueryPart_Property
) abstract_part
;
386 if (part
.Key
== "beagle:MimeType") {
387 is_match
= (part
.Value
== this.MimeType
) ? 1 : -1;
388 } else if (part
.Key
== "beagle:Filename") {
389 is_match
= (part
.Value
== base_name
) ? 1 : -1;
390 } else if (part
.Key
== "beagle:ExactFilename") {
391 is_match
= (part
.Value
== Name
) ? 1 : -1;
394 } else if (abstract_part
is QueryPart_DateRange
) {
396 QueryPart_DateRange part
;
397 part
= (QueryPart_DateRange
) abstract_part
;
399 is_match
= (part
.StartDate
<= Timestamp
&& Timestamp
<= part
.EndDate
) ? 1 : -1;
405 virtual public bool MatchesQuery (Query query
)
407 foreach (QueryPart abstract_part
in query
.Parts
) {
411 // Note that this works because we don't
412 // allow nested or queries.
413 if (abstract_part
is QueryPart_Or
) {
415 part
= (QueryPart_Or
) abstract_part
;
418 foreach (QueryPart sub_part
in part
.SubParts
) {
419 if (MatchesMetadata (sub_part
) == 1
420 || MatchesQueryPart (sub_part
)) {
426 // Handle certain query parts related to file system metadata.
427 is_match
= MatchesMetadata (abstract_part
);
430 is_match
= MatchesQueryPart (abstract_part
) ? 1 : -1;
433 if (abstract_part
.Logic
== QueryPartLogic
.Prohibited
)
434 is_match
= - is_match
;
438 else if (is_match
== 0)
439 throw new Exception ("This will never happen");
445 private void DoRecursiveQuery (Query query
, ArrayList matches
)
447 if (this.MatchesQuery (query
))
450 if (IsArchive
&& !SearchInArchives
)
453 if (this.HasChildren
)
454 foreach (FileSystemObject child
in this.Children
)
455 child
.DoRecursiveQuery (query
, matches
);
458 public ICollection
RecursiveQuery (Query query
)
461 matches
= new ArrayList ();
462 DoRecursiveQuery (query
, matches
);