Thumbnail file hits. Based on a patch from D Bera
[beagle.git] / beagled / WebServices / WebServiceBackEnd.cs
blob8f1428e2c05ba58833553905dbf5ce7ea63d0d03
1 //
2 //WebServiceBackEnd.cs
3 //
4 // Copyright (C) 2005 Novell, Inc.
5 //
6 // Authors:
7 // Vijay K. Nanjundaswamy (knvijay@novell.com)
8 //
11 // Permission is hereby granted, free of charge, to any person obtaining a
12 // copy of this software and associated documentation files (the "Software"),
13 // to deal in the Software without restriction, including without limitation
14 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 // and/or sell copies of the Software, and to permit persons to whom the
16 // Software is furnished to do so, subject to the following conditions:
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 // DEALINGS IN THE SOFTWARE.
29 using System;
30 using System.IO;
31 using System.Net;
32 using System.Collections;
33 using System.Threading;
35 using System.Runtime.Remoting;
36 using System.Runtime.Remoting.Channels;
37 using System.Runtime.Remoting.Channels.Tcp;
39 using Beagle.Util;
40 using Beagle.Daemon;
42 using Mono.ASPNET;
44 namespace Beagle.WebService {
46 public class WebServiceBackEnd: MarshalByRefObject {
48 public static string hostname = "localhost";
49 public static string DEFAULT_XSP_ROOT = Path.Combine (ExternalStringsHack.PkgDataDir, "xsp");
50 public static string DEFAULT_XSP_PORT = "8888";
51 public static string web_rootDir = DEFAULT_XSP_ROOT;
52 public static string web_port = DEFAULT_XSP_PORT;
53 public static bool web_start = true;
54 public static bool web_global = false;
56 static Mono.ASPNET.ApplicationServer appServer = null;
57 //Both "/" and "/beagle" aliased to DEFAULT_XSP_ROOT only for BeagleXSP server
58 static string DEFAULT_APP_MAPPINGS = "/:" + DEFAULT_XSP_ROOT + ",/beagle:" + DEFAULT_XSP_ROOT;
59 static string[] xsp_param = {"--port", DEFAULT_XSP_PORT,
60 "--root", DEFAULT_XSP_ROOT,
61 "--applications", DEFAULT_APP_MAPPINGS,
62 "--nonstop"};
64 //static Logger log = Logger.Get ("WebServiceBackEnd");
65 static string BeagleHttpUriBase;
66 static string[] reserved_suffixes;
68 public static ExternalAccessFilter AccessFilter;
70 public static void Start()
72 if (! web_start) {
73 Logger.Log.Warn("Beagle running with WebServices DISABLED\n");
74 return;
77 try {
78 IPHostEntry hostInfo = Dns.GetHostByName(Dns.GetHostName());
79 //Fully qualified DNS name of host:
80 hostname = hostInfo.HostName;
81 Logger.Log.Info("This Computer Hostname: " + hostname);
83 catch (Exception ex)
85 Logger.Log.Error("Caught exception {0} in Dns.GetHostName: ", ex.Message);
86 Logger.Log.Error("Resetting hostname to \"localhost\"");
87 hostname = "localhost";
90 if (! web_global)
91 web_global = Conf.WebServices.AllowGlobalAccess;
93 //start web-access server first
94 Logger.Log.Debug ("Starting WebBackEnd");
95 WebBackEnd.init ();
97 //Next start web-service server
98 Logger.Log.Info ("Starting WebServiceBackEnd");
99 WebServiceBackEnd.init ();
101 Logger.Log.Debug ("Global WebServicesAccess {0}", web_global ? "Enabled" : "Disabled");
103 xsp_param[1] = web_port;
104 xsp_param[3] = web_rootDir;
106 //Check if web_rootDir_changed:
107 if (String.Compare(web_rootDir, DEFAULT_XSP_ROOT, true) != 0)
108 //Assuming "/beagle" exists as an explicit sub-folder under user specified xsp root directory:
109 xsp_param[5] = "/:" + web_rootDir + ",/beagle:" + web_rootDir + "/beagle";
111 try {
112 // Mapping /beagle/local to ExternalStringsHack.Prefix
113 if (Directory.Exists(ExternalStringsHack.Prefix))
114 xsp_param[5] += ",/beagle/local:" + ExternalStringsHack.Prefix;
116 //Mapping /beagle/gnome to ExternalStringsHack.GnomePrefix
117 if (Directory.Exists(ExternalStringsHack.GnomePrefix))
118 xsp_param[5] += ",/beagle/gnome:" + ExternalStringsHack.GnomePrefix;
120 //Mapping /beagle/kde3 to ExternalStringsHack.KdePrefix
121 if (Directory.Exists(ExternalStringsHack.KdePrefix))
122 xsp_param[5] += ",/beagle/kde3:" + ExternalStringsHack.KdePrefix;
124 //if (!hostname.Equals("localhost")) {
126 reserved_suffixes = new string[] {"beagle", "local", "gnome", "kde3"};
127 BeagleHttpUriBase = "http://" + hostname + ":" + xsp_param[1] + "/beagle/";
129 AccessFilter = new ExternalAccessFilter(BeagleHttpUriBase, reserved_suffixes);
131 ArrayList matchers = AccessFilter.Matchers;
132 foreach (SimpleMatcher sm in matchers)
133 xsp_param[5] += ",/beagle/" + sm.Rewrite +":" + sm.Match;
135 AccessFilter.Initialize();
138 catch (Exception e)
140 xsp_param[5] = DEFAULT_APP_MAPPINGS;
143 Logger.Log.Debug ("Starting Internal Web Server");
145 int retVal = 0;
146 try {
147 //Start beagled internal web server (BeagleXsp)
148 retVal = Mono.ASPNET.Server.initXSP(xsp_param, out appServer);
150 catch (ArgumentException e) {
151 //Retry with default application mappings:
152 xsp_param[5] = DEFAULT_APP_MAPPINGS;
153 retVal = Mono.ASPNET.Server.initXSP(xsp_param, out appServer);
156 if (retVal != 0) {
157 Logger.Log.Warn ("Error starting Internal Web Server (retVal={0})", retVal);
158 Logger.Log.Warn ("Check if there is another instance of Beagle running");
160 else
161 Logger.Log.Debug("BeagleXSP Applications list: " + xsp_param[5]);
164 public static void Stop()
166 Logger.Log.Info ("Stopping WebServiceBackEnd");
167 if (appServer != null) {
168 appServer.Stop();
169 appServer = null;
173 /////////////////////////////////////////////////////////////////////////////////////////
175 private void WebServicesConfigurationChanged (Conf.Section section)
177 Logger.Log.Info("WebServicesConfigurationChanged EventHandler invoked");
178 if (! (section is Conf.WebServicesConfig))
179 return;
181 Conf.WebServicesConfig wsc = (Conf.WebServicesConfig) section;
183 if (web_global != wsc.AllowGlobalAccess) {
184 // Update AllowGlobalAccess configuration:
185 web_global = wsc.AllowGlobalAccess;
186 Logger.Log.Info("WebServicesBackEnd: Global WebServicesAccess {0}", web_global ? "Enabled" : "Disabled");
189 if (wsc.PublicFolders.Count == 0)
190 return;
192 Logger.Log.Warn("WebServicesBackEnd: Changes in PublicFolders configuration doesn't take effect until Beagle daemon is restarted!");
194 /* Note: ExternalAccessFilter Matchers can be updated,
195 but app mapping changes in BeagleXsp Server require it to be restarted !
197 ArrayList newList = new ArrayList();
198 foreach (string pf in wsc.PublicFolders) {
199 if (pf == null || pf == "")
200 continue;
202 newList.Add (new NetBeagleHandler (host, port, this));
205 if (usingPublicFoldersDotCfgFile) {
206 usingPublicFoldersDotCfgFile = false;
207 log.Warn("NetBeagleQueryable: Duplicate configuration of PublicFolders in '~/.beagle/publicfolders.cfg' and '~/.beagle/config/webservices.xml' !");
208 log.Info("NetBeagleQueryable: Remove '~/.beagle/publicfolders.cfg' file. Use 'beagle-config' instead to setup public folders.");
209 log.Info("NetBeagleQueryable: Replacing PublicFoldersList with new list from \"webservices.xml\" having {0} node(s)", newList.Count);
212 AccessFilter.ReplaceAccessFilter(newList);
216 /////////////////////////////////////////////////////////////////////////////////////////
218 //KNV: If needed, we can convert this to a Singleton, adding a
219 // static Factory method to get the singleton instance reference,
220 // so that front-end code always gets hold of same instance.
222 static WebServiceBackEnd instance = null;
224 private Hashtable resultTable;
225 private Hashtable sessionTable;
227 public WebServiceBackEnd() {
229 resultTable = Hashtable.Synchronized(new Hashtable());
230 sessionTable = Hashtable.Synchronized(new Hashtable());
232 Conf.Subscribe (typeof (Conf.WebServicesConfig), new Conf.ConfigUpdateHandler (WebServicesConfigurationChanged));
235 ~WebServiceBackEnd() {
236 resultTable.Clear();
237 sessionTable.Clear();
240 public bool allowGlobalAccess {
241 get { return web_global; }
244 public static void init()
246 if (instance == null) {
248 instance = new WebServiceBackEnd();
250 //TCP Channel Listener registered in beagledWeb:init()
251 //ChannelServices.RegisterChannel(new TcpChannel(8347));
252 WellKnownServiceTypeEntry WKSTE =
253 new WellKnownServiceTypeEntry(typeof(WebServiceBackEnd),
254 "WebServiceBackEnd.rem", WellKnownObjectMode.Singleton);
255 RemotingConfiguration.ApplicationName="beagled";
256 RemotingConfiguration.RegisterWellKnownServiceType(WKSTE);
260 void OnHitsAdded (QueryResult qres, ICollection hits)
262 if (resultTable.Contains(qres)) {
264 SessionData sdata = ((SessionData) resultTable[qres]);
265 ArrayList results = sdata.results;
266 bool localReq = sdata.localRequest;
268 if (localReq){
269 lock (results.SyncRoot)
270 results.AddRange(hits);
272 else {
273 //Query query = sdata.query;
274 lock (results.SyncRoot) {
275 foreach (Hit h in hits)
276 if (h.Uri.ToString().StartsWith(NetworkedBeagle.BeagleNetPrefix) ||
277 AccessFilter.FilterHit(h))
278 results.Add(h);
284 void removeUris(ArrayList results, ICollection uris)
286 foreach(Uri u in uris)
287 foreach(Hit h in results)
288 if (h.Uri.Equals (u) && h.Uri.Fragment == u.Fragment) {
289 lock (results.SyncRoot) {
290 results.Remove(h);
292 break;
296 void OnHitsSubtracted (QueryResult qres, ICollection uris)
298 if (resultTable.Contains(qres)) {
299 SessionData sdata = ((SessionData) resultTable[qres]);
300 ArrayList results = sdata.results;
301 removeUris(results, uris);
305 void OnFinished (QueryResult qres)
307 DetachQueryResult (qres);
310 void OnCancelled (QueryResult qres)
312 DetachQueryResult (qres);
315 void AttachQueryResult (QueryResult qres, SessionData sdata)
317 if (qres != null) {
319 qres.HitsAddedEvent += OnHitsAdded;
320 qres.HitsSubtractedEvent += OnHitsSubtracted;
321 qres.FinishedEvent += OnFinished;
322 qres.CancelledEvent += OnCancelled;
324 resultTable.Add(qres, sdata);
328 void DetachQueryResult (QueryResult qres)
330 if (qres != null) {
332 if (resultTable.Contains(qres)) {
333 SessionData sdata = ((SessionData) resultTable[qres]);
334 sdata.results.Sort();
336 qres.HitsAddedEvent -= OnHitsAdded;
337 qres.HitsSubtractedEvent -= OnHitsSubtracted;
338 qres.FinishedEvent -= OnFinished;
339 qres.CancelledEvent -= OnCancelled;
341 resultTable.Remove(qres);
342 qres.Dispose ();
346 private class SessionData {
348 private bool _localRequest;
349 private ArrayList _results;
350 private Query _query;
352 public SessionData (Query _query, ArrayList _results, bool _localRequest)
354 this._localRequest = _localRequest;
355 this._results = _results;
356 this._query = _query;
359 public bool localRequest {
360 get { return _localRequest; }
363 public ArrayList results {
364 get { return _results; }
368 public Query query {
369 get { return _query; }
373 public string[] ICollection2StringList(ICollection il)
375 if (il == null)
376 return new string[0] ;
378 string[] sl = new string[il.Count];
380 il.CopyTo(sl, 0);
382 return sl;
385 private const int MAX_RESULTS_PER_CALL = 20;
387 public const int SC_QUERY_SUCCESS = 0;
388 public const int SC_INVALID_QUERY = -1;
389 public const int SC_UNAUTHORIZED_ACCESS = -2;
390 public const int SC_INVALID_SEARCH_TOKEN = -3;
391 public const int SC_DUPLICATE_QUERY = -4;
393 //Full beagledQuery
394 public SearchResult doQuery(SearchRequest sreq, bool isLocalReq)
396 SearchResult sr = null;
398 if (sreq.text == null || sreq.text.Length == 0 ||
399 (sreq.text.Length == 1 && sreq.text[0].Trim() == "") ) {
401 sr = new SearchResult();
402 sr.statusCode = SC_INVALID_QUERY;
403 sr.statusMsg = "Error: No search terms specified";
404 return sr;
407 Query query = new Query();
409 foreach (string text in sreq.text)
410 query.AddText(text);
412 Logger.Log.Info("WebServiceBackEnd: Received {0} WebService Query with search term: {1}", isLocalReq ? "Local":"External", query.QuotedText);
414 if (sreq.mimeType != null && sreq.mimeType[0] != null)
415 foreach (string mtype in sreq.mimeType)
416 query.AddMimeType(mtype);
418 if (sreq.searchSources != null && sreq.searchSources[0] != null)
419 foreach (string src in sreq.searchSources)
420 query.AddSource(src);
422 //If needed, check to restrict queries to System or Neighborhood domain, can be added here
423 if (sreq.qdomain > 0)
424 query.AddDomain(sreq.qdomain);
426 if (!isLocalReq) { //External Request, check if this Node is already processing it
428 lock (this) {
429 if ((sreq.searchId != 0) && NetworkedBeagle.IsCachedRequest(sreq.searchId)) {
431 sr = new SearchResult();
432 sr.numResults = sr.totalResults = sr.firstResultIndex = 0;
433 sr.hitResults = new HitResult[sr.numResults];
434 sr.searchToken = "";
436 sr.statusCode = SC_DUPLICATE_QUERY;
437 sr.statusMsg = "Error: Duplicate Query loopback";
438 Logger.Log.Warn("WebServiceBackEnd: Received duplicate Query for a query already in process!");
439 Logger.Log.Warn("WebServiceBackEnd: Check NetBeagle configuration on all nodes to remove possible loops");
442 if (sreq.hopCount >= 5) {
443 //If request has traversed 5 nodes in reaching here, stop cascading.
444 //Make it a Local Query.
445 query.RemoveDomain(sreq.qdomain);
446 query.AddDomain(QueryDomain.System);
449 if ((sr == null) && (sreq.searchId != 0) )
450 NetworkedBeagle.CacheRequest(query, sreq.searchId, sreq.hopCount + 1);
453 if (sr != null)
454 return sr;
456 //Logger.Log.Info("New external Query: searchId = {0}", sreq.searchId);
459 ArrayList results = ArrayList.Synchronized(new ArrayList());
461 QueryResult qres = new QueryResult ();
463 string searchToken = TokenGenerator();
465 SessionData sdata = new SessionData(query, results, isLocalReq);
467 AttachQueryResult (qres, sdata);
469 /* Include this code, if sessionID passed from front-end:
470 if (sessionTable.Contains(searchToken))
471 sessionTable[searchToken] = sdata;
472 else
474 sessionTable.Add(searchToken, sdata);
476 QueryDriver.DoQuery (query, qres);
478 while (resultTable.Contains(qres) && (results.Count < MAX_RESULTS_PER_CALL) )
479 Thread.Sleep(100);
481 //Console.WriteLine("WebServiceBackEnd: Got {0} results from beagled", results.Count);
482 sr = new SearchResult();
484 if (results.Count > 0)
486 lock (results.SyncRoot) { //Lock results ArrayList to prevent more Hits added till we've processed doQuery
488 sr.numResults = results.Count < MAX_RESULTS_PER_CALL ? results.Count: MAX_RESULTS_PER_CALL;
489 sr.hitResults = new HitResult[sr.numResults];
491 string hitUri;
492 for (int i = 0; i < sr.numResults; i++) {
494 Hit h = (Hit) results[i];
496 string snippet;
498 //Queryable queryable = h.SourceObject as Queryable;
499 Queryable queryable = QueryDriver.GetQueryable (h.SourceObjectName);
501 if (queryable == null)
502 snippet = "ERROR: hit.SourceObject is null, uri=" + h.Uri;
503 else
504 snippet = queryable.GetSnippet (ICollection2StringList(query.Text), h);
506 sr.hitResults[i] = new HitResult();
507 sr.hitResults[i].id = h.Id;
509 hitUri = h.Uri.ToString();
510 if (isLocalReq || hitUri.StartsWith(NetworkedBeagle.BeagleNetPrefix))
511 sr.hitResults[i].uri = hitUri;
512 else
513 sr.hitResults[i].uri = AccessFilter.TranslateHit(h);
515 sr.hitResults[i].resourceType = h.Type;
516 sr.hitResults[i].mimeType = h.MimeType;
517 sr.hitResults[i].source = h.Source;
518 sr.hitResults[i].scoreRaw = h.ScoreRaw;
519 sr.hitResults[i].scoreMultiplier = h.ScoreMultiplier;
521 int plen = h.Properties.Count;
522 sr.hitResults[i].properties = new HitProperty[plen];
523 for (int j = 0; j < plen; j++) {
524 Property p = (Property) h.Properties[j];
525 sr.hitResults[i].properties[j] = new HitProperty();
526 sr.hitResults[i].properties[j].PKey = p.Key;
527 sr.hitResults[i].properties[j].PVal = p.Value;
528 sr.hitResults[i].properties[j].IsMutable = p.IsMutable;
529 sr.hitResults[i].properties[j].IsSearched = p.IsSearched;
532 if (snippet != null)
533 sr.hitResults[i].snippet = snippet.Trim();
535 } //end lock
536 }// end if
537 else {
539 sr.numResults = 0;
540 sr.hitResults = new HitResult[sr.numResults];
543 sr.totalResults = results.Count;
545 sr.firstResultIndex = 0;
546 sr.searchToken = "";
548 if (sr.totalResults > 0)
549 sr.searchToken = searchToken;
551 sr.statusCode = SC_QUERY_SUCCESS;
552 sr.statusMsg = "Success";
553 Logger.Log.Info("WebServiceBackEnd: Total Results = " + sr.totalResults);
554 return sr;
557 public SearchResult getMoreResults(string searchToken, int startIndex, bool isLocalReq)
560 SearchResult sr = new SearchResult();
561 sr.numResults = 0;
563 if (!sessionTable.ContainsKey(searchToken)) {
564 sr.statusCode = SC_INVALID_SEARCH_TOKEN;
565 sr.statusMsg = "Error: Invalid Search Token";
566 Logger.Log.Warn("GetMoreResults: Invalid Search Token received ");
567 return sr;
570 ArrayList results = ((SessionData)sessionTable[searchToken]).results;
571 if (results == null) {
572 sr.statusCode = SC_INVALID_SEARCH_TOKEN;
573 sr.statusMsg = "Error: Invalid Search Token";
574 Logger.Log.Warn("GetMoreResults: Invalid Search Token received ");
575 return sr;
578 lock (results.SyncRoot) { //Lock results ArrayList to prevent more Hits getting added till we've processed doQuery
580 int i = 0;
582 if (startIndex < results.Count)
583 sr.numResults = (results.Count < startIndex + MAX_RESULTS_PER_CALL) ? (results.Count - startIndex): MAX_RESULTS_PER_CALL;
585 sr.hitResults = new HitResult[sr.numResults];
587 string hitUri;
588 for (int k = startIndex; (i < sr.numResults) && (k < results.Count); k++) {
590 Hit h = (Hit) results[k];
592 sr.hitResults[i] = new HitResult();
594 // GetMoreResults will NOT return Snippets by default. Client must make explicit GetSnippets request to get snippets for these hits.
595 // Not initializing sr.hitResults[i].snippet implies there is no <snippets> element in HitResult XML response.
597 sr.hitResults[i].id = h.Id;
599 hitUri = h.Uri.ToString();
600 if (isLocalReq || hitUri.StartsWith(NetworkedBeagle.BeagleNetPrefix))
601 sr.hitResults[i].uri = hitUri;
602 else
603 sr.hitResults[i].uri = AccessFilter.TranslateHit(h);
605 sr.hitResults[i].resourceType = h.Type;
606 sr.hitResults[i].mimeType = h.MimeType;
607 sr.hitResults[i].source = h.Source;
608 sr.hitResults[i].scoreRaw = h.ScoreRaw;
609 sr.hitResults[i].scoreMultiplier = h.ScoreMultiplier;
611 int plen = h.Properties.Count;
612 sr.hitResults[i].properties = new HitProperty[plen];
613 for (int j = 0; j < plen; j++) {
614 Property p = (Property) h.Properties[j];
615 sr.hitResults[i].properties[j] = new HitProperty();
616 sr.hitResults[i].properties[j].PKey = p.Key;
617 sr.hitResults[i].properties[j].PVal = p.Value;
618 sr.hitResults[i].properties[j].IsMutable = p.IsMutable;
619 sr.hitResults[i].properties[j].IsSearched = p.IsSearched;
622 i++;
624 } //end lock
626 sr.totalResults = results.Count;
628 sr.firstResultIndex = startIndex;
629 sr.searchToken = "";
631 if (sr.totalResults > 0)
632 sr.searchToken = searchToken;
634 sr.statusCode = SC_QUERY_SUCCESS;
635 sr.statusMsg = "Success";
636 //Console.WriteLine("WebServiceQuery: Total Results = " + sr.totalResults);
637 return sr;
640 public static string InvalidHitSnippetError = "ERROR: Invalid or Duplicate Hit Id";
641 public HitSnippet[] getSnippets(string searchToken, int[] hitIds)
643 HitSnippet[] response;
645 if (!sessionTable.ContainsKey(searchToken)) {
647 response = new HitSnippet[0];
648 Logger.Log.Warn("GetSnippets: Invalid Search Token received ");
649 return response;
652 ArrayList results = ((SessionData)sessionTable[searchToken]).results;
653 if ((results == null) || (results.Count == 0)) {
655 response = new HitSnippet[0];
656 Logger.Log.Warn("GetSnippets: Invalid Search Token received ");
657 return response;
660 int i = 0;
661 ArrayList IdList = new ArrayList();
662 IdList.AddRange(hitIds);
663 response = new HitSnippet[hitIds.Length];
664 Logger.Log.Debug("GetSnippets invoked with {0} hitIds", hitIds.Length);
666 Query query = ((SessionData)sessionTable[searchToken]).query;
668 lock (results.SyncRoot) {
670 string snippet = null;
671 foreach (Hit h in results) {
673 if (IdList.Contains(h.Id)) {
675 IdList.Remove(h.Id);
677 //Queryable queryable = h.SourceObject as Queryable;
678 Queryable queryable = QueryDriver.GetQueryable (h.SourceObjectName);
680 if (queryable == null)
681 snippet = "ERROR: hit.SourceObject is null, uri=" + h.Uri;
682 else
683 snippet = queryable.GetSnippet (ICollection2StringList(query.Text), h);
685 //GetSnippets always invoked on Target Beagle Node where hits originate:
686 if (snippet == null)
687 snippet = "";
689 HitSnippet hs = new HitSnippet();
690 hs.hitId = h.Id;
691 hs.snippet = snippet.Trim();
692 response[i++] = hs;
694 if (i == hitIds.Length)
695 return response;
697 } //end foreach
698 } //end lock
700 foreach (int hitId in IdList) {
701 HitSnippet hs = new HitSnippet();
702 hs.hitId = hitId;
703 hs.snippet = InvalidHitSnippetError;
704 response[i++] = hs;
706 if (i == hitIds.Length)
707 break;
709 Logger.Log.Warn("GetSnippets invoked some invalid hitIds");
711 return response;
714 //Returns a 15-char random alpha-numeric string similar to ASP.NET sessionId
715 private string TokenGenerator()
717 const int TOKEN_LEN = 15;
719 Random r = new Random();
720 string token = ((Char)((int)((26 * r.NextDouble()) + 'a')) + System.Guid.NewGuid().ToString()).Substring (0, TOKEN_LEN);
722 char alpha = (Char)((int)((26 * r.NextDouble()) + 'a'));
724 return (token.Replace('-', alpha));
728 ////////////////////////////////////////////////////////////////////////////////////////////
729 ///////////// WebService Request-Response Data Structures
730 ////////////////////////////////////////////////////////////////////////////////////////////
732 [Serializable()]
733 public class SearchRequest {
735 public string[] text;
736 public string[] mimeType;
737 public string[] searchSources;
738 public QueryDomain qdomain;
739 //Unique searchId across network
740 public int searchId;
741 public int hopCount;
744 [Serializable()]
745 public class HitProperty {
747 private string pKey="";
748 public string PKey {
749 get {return pKey;}
750 set {pKey = value;}
753 private string pVal="";
754 public string PVal {
755 get {return pVal;}
756 set {pVal = value;}
759 private bool isKeyword;
760 public bool IsMutable {
761 get { return isKeyword; }
762 set { isKeyword = value; }
765 private bool isSearched;
766 public bool IsSearched {
767 get { return isSearched; }
768 set { isSearched = value; }
772 [Serializable()]
773 public class HitResult {
775 public int id;
776 public string uri;
777 public string resourceType;
778 public string mimeType;
779 public string source;
780 public double scoreRaw;
781 public double scoreMultiplier;
782 public HitProperty[] properties;
783 //FIXME: public xxx[] data;
784 public string snippet;
787 [Serializable()]
788 public class SearchResult {
790 public int statusCode; //ReturnCode for programmatic processing
791 public string statusMsg; //User-friendly return message
793 public string searchToken; //Token identifying the query,
794 //to enable follow-up queries
795 public int firstResultIndex; //Index of first result in this response
796 public int numResults; //No. of results in this response
797 public int totalResults; //Total no. of results from the query
798 public HitResult[] hitResults;
801 [Serializable()]
802 public class HitSnippet {
803 public int hitId;
804 public string snippet;