URI hostname is case-insensitive but path is case-sensitive. Be careful when generati...
[beagle.git] / BeagleClient / Hit.cs
blob19b9b921146608561149bb607c92bac0434067a5
1 //
2 // Hit.cs
3 //
4 // Copyright (C) 2004 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.
28 using System;
29 using System.Collections;
30 using System.Diagnostics;
31 using System.IO;
32 using System.Xml;
33 using System.Xml.Serialization;
35 using Beagle.Util;
37 namespace Beagle {
39 public class Hit: Versioned, IComparable {
41 // A URI we can use to locate the source of this match.
42 private Uri uri = null;
44 // A URI of this Hit's container element
45 private Uri parent_uri = null;
47 // File, Web, MailMessage, IMLog, etc.
48 private string type = null;
50 // If applicable, otherwise set to null.
51 private string mimeType = null;
53 // IndexUser, IndexSystem, Google, Addressbook, iFolder, etc.
54 private string source = null;
56 // This is used to hold a copy of the Queryable in the
57 // server-side copy of the Hit. It is always null
58 // on the client-side.
59 private object sourceObject = null;
61 // High scores imply greater relevance.
62 private double score = 0.0;
64 private ArrayList properties = new ArrayList ();
66 private enum SpecialType {
67 Unknown,
68 None,
69 Invalid,
70 File,
71 Directory
74 SpecialType special = SpecialType.Unknown;
76 private string path;
77 private FileInfo fileInfo = null;
78 private DirectoryInfo directoryInfo = null;
80 //////////////////////////
82 [XmlIgnore]
83 public Uri Uri {
84 get { return uri; }
85 set { uri = value; }
88 [XmlAttribute ("Uri")]
89 public string EscapedUri {
90 get {
91 return UriFu.UriToEscapedString (uri);
94 set {
95 uri = UriFu.EscapedStringToUri (value);
99 [XmlIgnore]
100 public Uri ParentUri {
101 get { return parent_uri; }
102 set { parent_uri = value; }
105 [XmlAttribute ("ParentUri")]
106 public string EscapedParentUri {
107 get {
108 if (parent_uri == null)
109 return null;
111 return UriFu.UriToEscapedString (parent_uri);
114 set {
115 if (value == null)
116 parent_uri = null;
117 else
118 parent_uri = UriFu.EscapedStringToUri (value);
122 // DEPRECATED: This is now stored as a property.
123 [XmlAttribute]
124 public string Type {
125 get { return type; }
126 set { type = value; }
129 // DEPRECATED: This is now stored as a property.
130 [XmlAttribute]
131 public string MimeType {
132 get { return mimeType; }
133 set { mimeType = value; }
136 [XmlAttribute]
137 public string Source {
138 get { return source; }
139 set { source = value; }
142 [XmlIgnore]
143 public object SourceObject {
144 get { return sourceObject; }
145 set { sourceObject = value; }
148 [XmlAttribute]
149 public double Score {
150 get { return score; }
151 set { score = value; }
154 //////////////////////////
156 private void SpecialHandling ()
158 if (special != SpecialType.Unknown)
159 return;
161 if (uri.IsFile) {
162 path = uri.LocalPath;
163 if (File.Exists (path))
164 special = SpecialType.File;
165 else if (Directory.Exists (path))
166 special = SpecialType.Directory;
167 else
168 special = SpecialType.Invalid;
171 if (special == SpecialType.Unknown)
172 special = SpecialType.None;
175 public bool IsValid {
176 get { SpecialHandling (); return special != SpecialType.Invalid; }
179 public bool IsFile {
180 get { SpecialHandling (); return special == SpecialType.File; }
183 public bool IsDirectory {
184 get { SpecialHandling (); return special == SpecialType.Directory; }
187 public bool IsFileSystem {
188 get { return IsFile || IsDirectory; }
191 public string Path {
192 get { SpecialHandling (); return path; }
195 public string PathQuoted {
196 get { return Path.Replace (" ", "\\ "); }
199 public string FileName {
200 get { return Path != null ? System.IO.Path.GetFileName (Path) : null; }
203 public string DirectoryName {
204 get { return Path != null ? System.IO.Path.GetDirectoryName (Path) : null; }
207 [XmlIgnore]
208 public FileSystemInfo FileSystemInfo {
209 get {
210 if (IsFile)
211 return (FileSystemInfo) FileInfo;
212 else if (IsDirectory)
213 return (FileSystemInfo) DirectoryInfo;
214 else
215 return null;
219 [XmlIgnore]
220 public FileInfo FileInfo {
221 get {
222 if (fileInfo == null && IsFile)
223 fileInfo = new FileInfo (Path);
224 return fileInfo;
228 [XmlIgnore]
229 public DirectoryInfo DirectoryInfo {
230 get {
231 if (directoryInfo == null && IsDirectory)
232 directoryInfo = new DirectoryInfo (Path);
233 return directoryInfo;
237 //////////////////////////
239 [XmlArray]
240 [XmlArrayItem (ElementName="Property", Type=typeof (Property))]
241 public ArrayList Properties {
242 get { return properties; }
245 public void AddProperty (Property prop)
247 int loc = properties.BinarySearch (prop);
249 // If the value is not in the array we get its position
250 // by taking bitwise complement.
251 if (loc < 0)
252 loc = ~loc;
254 properties.Insert (loc, prop);
257 public void AddProperty (ICollection props)
259 properties.AddRange (props);
260 properties.Sort ();
264 private bool FindProperty (string key, out int first, out int top)
267 first = 0;
268 top = 0;
269 int range = properties.Count - 1;
270 Property prop;
272 // O(log n + |range|)-time algorithm for 1-d range query
273 while (range > 1) {
274 // find middle index
275 int mid = first + (range/2);
277 prop = properties [mid] as Property;
278 // Properties are sorted first by Key, then by Value
279 // We only need to compare the key of the middle element to the key in parameter
280 if (String.Compare (key, prop.Key) > 0) {
281 // minimum item in right subtree is smaller
282 // move right
283 first = mid;
284 range -= (range/2);
285 } else
286 // move left
287 range = (range/2);
290 // first points to
291 // - either the previous item in the BST
292 // - or the next to the previous item in the BST
293 prop = (Property) properties [first];
294 if (prop.Key != key)
295 first = first + 1;
296 if (first >= properties.Count)
297 return false;
298 prop = (Property) properties [first];
299 if (prop.Key != key)
300 return false;
302 top = first + 1;
303 // Since range will be small, do a linear scan from here.
304 // We could do another BST traversal at the cost of O(log-n),
305 // but its not worth it.
306 while (top < properties.Count) {
307 prop = properties [top] as Property;
308 if (prop.Key != key)
309 break;
310 ++top;
313 return true;
316 public string this [string key] {
317 get {
318 int first, top;
319 if (! FindProperty (key, out first, out top))
320 return null;
322 if (top - first != 1) {
323 Logger.Log.Warn ("Accessed multi-property key with Hit's indexer.");
324 return null;
327 Property prop;
328 prop = properties [first] as Property;
329 return prop.Value;
332 set {
333 int first = 0, top = 0;
335 // If we've never heard of this property, add it.
336 if (! FindProperty (key, out first, out top)) {
337 AddProperty (Property.New (key, value));
338 return;
341 // If it has appeared once before, clobber the existing
342 // value. This emulates the previous (broken) semantics.
344 if (top - first == 1) {
345 properties [first] = Property.New (key, value);
346 return;
349 // Otherwise throw an exception (which sort of sucks,
350 // but we don't really know what to do there)
351 throw new Exception (String.Format ("Attempt to re-set multi-property '{0}' via the indexer", key));
355 public string GetFirstProperty (string key)
357 int first, top;
358 if (! FindProperty (key, out first, out top))
359 return null;
360 Property prop;
361 prop = properties [first] as Property;
362 return prop.Value;
365 public string[] GetProperties (string key)
367 int first, top;
368 if (! FindProperty (key, out first, out top))
369 return null;
371 string[] values = new string [top - first];
373 for (int i = 0; first + i < top; i++) {
374 Property prop = properties [first + i] as Property;
375 values [i] = prop.Value;
378 return values;
381 //////////////////////////
383 public override int GetHashCode ()
385 return (uri != null ? uri.ToString().GetHashCode () : 0)
386 ^ (type != null ? type.GetHashCode () : 0)
387 ^ (source != null ? source.GetHashCode () : 0);
390 //////////////////////////
392 public int CompareTo (object obj)
394 Hit otherHit = (Hit) obj;
395 // Notice that we sort by time from most to least recent
396 return DateTime.Compare (otherHit.Timestamp, this.Timestamp);