* Monodoc/provider.cs, Monodoc/ecma-provider.cs: Improve cache support
[mono.git] / mcs / tools / monodoc / Monodoc / provider.cs
1 //
2 // Provider: shared code and interfaces for providers
3 //
4 // Author:
5 //   Miguel de Icaza (miguel@ximian.com)
6 //
7 // (C) 2002, Ximian, Inc.
8 //
9 // TODO:
10 //   Each node should have a provider link
11 //
12 //   Should encode numbers using a runlength encoding to save space
13 //
14 namespace Monodoc {
15 using System;
16 using System.IO;
17 using System.Text;
18 using System.Text.RegularExpressions;
19 using System.Collections;
20 using System.Diagnostics;
21 using System.Configuration;
22 using System.Text.RegularExpressions;
23 using System.Xml;
24 using System.Xml.XPath;
25 using ICSharpCode.SharpZipLib.Zip;
26
27 using Monodoc.Lucene.Net.Index;
28 using Monodoc.Lucene.Net.Analysis.Standard;
29
30 using Mono.Documentation;
31
32 /// <summary>
33 ///    This tree is populated by the documentation providers, or populated
34 ///    from a binary encoding of the tree.  The format of the tree is designed
35 ///    to minimize the need to load it in full.
36 /// </summary>
37 public class Tree : Node {
38
39 #region Loading the tree from a file
40
41         /// <summary>
42         ///   Our HelpSource container
43         /// </summary>
44         public readonly HelpSource HelpSource;
45         
46         internal FileStream InputStream;
47         internal BinaryReader InputReader;
48
49         /// <summary>
50         ///   Load from file constructor
51         /// </summary>
52         public Tree (HelpSource hs, string filename) : base (null, null)
53         {
54                 Encoding utf8 = new UTF8Encoding (false, true);
55
56                 if (!File.Exists (filename)){
57                         throw new FileNotFoundException ();
58                 }
59                 
60                 InputStream = File.OpenRead (filename);
61                 InputReader = new BinaryReader (InputStream, utf8);
62                 byte [] sig = InputReader.ReadBytes (4);
63                 
64                 if (!GoodSig (sig))
65                         throw new Exception ("Invalid file format");
66                 
67                 InputStream.Position = 4;
68                 position = InputReader.ReadInt32 ();
69
70                 LoadNode ();
71                 HelpSource = hs;
72         }
73
74         /// <summary>
75         ///    Tree creation and merged tree constructor
76         /// </summary>
77         public Tree (HelpSource hs, string caption, string url) : base (caption, url)
78         {
79                 HelpSource = hs;
80         }
81
82         public Tree (HelpSource hs, Node parent, string caption, string element) : base (parent, caption, element)
83         {
84                 HelpSource = hs;
85         }
86
87 #endregion
88
89         /// <summary>
90         ///    Saves the tree into the specified file using the help file format.
91         /// </summary>
92         public void Save (string file)
93         {
94                 Encoding utf8 = new UTF8Encoding (false, true);
95                 using (FileStream output = File.OpenWrite (file)){
96                         // Skip over the pointer to the first node.
97                         output.Position = 8;
98                         
99                         using (BinaryWriter writer = new BinaryWriter (output, utf8)){
100                                 // Recursively dump
101                                 Dump (output, writer);
102
103                                 output.Position = 0;
104                                 writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'H', (byte) 'P' });
105                                 writer.Write (position);
106                         }
107                 }
108         }
109
110         static bool GoodSig (byte [] sig)
111         {
112                 if (sig.Length != 4)
113                         return false;
114                 if (sig [0] != (byte) 'M' ||
115                     sig [1] != (byte) 'o' ||
116                     sig [2] != (byte) 'H' ||
117                     sig [3] != (byte) 'P')
118                         return false;
119                 return true;
120         }
121
122 }
123
124 public class Node : IComparable {
125         string caption, element;
126         public bool Documented;
127         public readonly Tree tree;
128         Node parent;
129         protected ArrayList nodes;
130         protected internal int position;
131
132         static ArrayList empty = ArrayList.ReadOnly(new ArrayList(0));
133
134         /// <summary>
135         ///    Creates a node, called by the Tree.
136         /// </summary>
137         public Node (string caption, string element)
138         {
139                 this.tree = (Tree) this;
140                 this.caption = caption;
141                 this.element = element;
142                 parent = null;
143         }
144
145         public Node (Node parent, string caption, string element)
146         {
147                 this.parent = parent;
148                 this.tree = parent.tree;
149                 this.caption = caption;
150                 this.element = element;
151         }
152         
153         /// <summary>
154         ///    Creates a node from an on-disk representation
155         /// </summary>
156         Node (Node parent, int address)
157         {
158                 this.parent = parent;
159                 position = address;
160                 this.tree = parent.tree;
161                 if (address > 0)
162                         LoadNode ();
163         }
164
165         public void AddNode (Node n)
166         {
167                 Nodes.Add (n);
168                 n.parent = this;
169                 n.Documented = true;
170         }
171
172         public void DelNode (Node n)
173         {
174                 Nodes.Remove (n);
175         }
176
177         public ArrayList Nodes {
178                 get {
179                         if (position < 0)
180                                 LoadNode ();
181                         return nodes != null ? nodes : empty;
182                 }
183         }
184
185         public string Element {
186                 get {
187                         if (position < 0)
188                                 LoadNode ();
189                         return element;
190                 }
191
192                 set {
193                         element = value;
194                 }
195         }
196
197         public string Caption {
198                 get {
199                         if (position < 0)
200                                 LoadNode ();
201                         return caption;
202                 }
203         }
204         
205         public Node Parent {
206                 get {
207                         return parent;
208                 }
209         }
210                 
211         public void LoadNode ()
212         {
213                 if (position < 0)
214                         position = -position;
215
216                 tree.InputStream.Position = position;
217                 BinaryReader reader = tree.InputReader;
218                 int count = DecodeInt (reader);
219                 element = reader.ReadString ();
220                 caption = reader.ReadString ();
221                 if (count == 0)
222                         return;
223                 
224                 nodes = new ArrayList (count);
225                 for (int i = 0; i < count; i++){
226                         int child_address = DecodeInt (reader);
227                                                               
228                         Node t = new Node (this, -child_address);
229                         nodes.Add (t);
230                 }
231         }
232         
233         /// <summary>
234         ///   Creates a new node, in the locator entry point, and with
235         ///   a user visible caption of @caption
236         /// </summary>
237         public Node CreateNode (string c_caption, string c_element)
238         {
239                 if (nodes == null)
240                         nodes = new ArrayList ();
241
242                 Node t = new Node (this, c_caption, c_element);
243                 nodes.Add (t);
244                 return t;
245         }
246
247         /// <summary>
248         ///   Looks up or creates a new node, in the locator entry point, and with
249         ///   a user visible caption of @caption.  This is different from
250         ///   CreateNode in that it will look up an existing node for the given @locator.
251         /// </summary>
252         public Node LookupNode (string c_caption, string c_element)
253         {
254                 if (nodes == null)
255                         return CreateNode (c_caption, c_element);
256
257                 foreach (Node n in nodes){
258                         if (n.element == c_element)
259                                 return n;
260                 }
261                 return CreateNode (c_caption, c_element);
262         }
263
264         public void EnsureNodes ()
265         {
266                 if (nodes == null)
267                         nodes = new ArrayList ();
268         }
269         
270         public bool IsLeaf {
271                 get {
272                         return nodes == null;
273                 }
274         }
275
276         void EncodeInt (BinaryWriter writer, int value)
277         {
278                 do {
279                         int high = (value >> 7) & 0x01ffffff;
280                         byte b = (byte)(value & 0x7f);
281
282                         if (high != 0) {
283                                 b = (byte)(b | 0x80);
284                         }
285                         
286                         writer.Write(b);
287                         value = high;
288                 } while(value != 0);
289         }
290
291         int DecodeInt (BinaryReader reader)
292         {
293                 int ret = 0;
294                 int shift = 0;
295                 byte b;
296                 
297                         do {
298                                 b = reader.ReadByte();
299
300                                 ret = ret | ((b & 0x7f) << shift);
301                                 shift += 7;
302                         } while ((b & 0x80) == 0x80);
303                         
304                         return ret;
305         }
306
307         internal void Dump (FileStream output, BinaryWriter writer)
308         {
309                 if (nodes != null){
310                         foreach (Node child in nodes){
311                                 child.Dump (output, writer);
312                         }
313                 }
314                 position = (int) output.Position;
315                 EncodeInt (writer, nodes == null ? 0 : (int) nodes.Count);
316                 writer.Write (element);
317                 writer.Write (caption);
318
319                 if (nodes != null){
320                         foreach (Node child in nodes){
321                                 EncodeInt (writer, child.position);
322                         }
323                 }
324         }
325
326         static int indent;
327
328         static void Indent ()
329         {
330                 for (int i = 0; i < indent; i++)
331                         Console.Write ("   ");
332         }
333         
334         public static void PrintTree (Node node)
335         {
336                 Indent ();
337                 Console.WriteLine ("{0},{1}\t[PublicUrl: {2}]", node.Element, node.Caption, node.PublicUrl);
338                 if (node.Nodes.Count == 0)
339                         return;
340
341                 indent++;
342                 foreach (Node n in node.Nodes)
343                         PrintTree (n);
344                 indent--;
345         }
346
347         public void Sort ()
348         {
349                 if (nodes != null)
350                         nodes.Sort ();
351         }
352
353         [Obsolete("Use PublicUrl")]
354         public string URL {
355                 get {
356                         if (position < 0)
357                                 LoadNode ();
358
359                         if (element.IndexOf (":") >= 0)
360                                 return element;
361
362                         if (parent != null){
363                                 string url = parent.URL;
364
365                                 if (url.EndsWith ("/"))
366                                         return url + element;
367                                 else
368                                         return parent.URL + "/" + element;
369                         } else
370                                 return element;
371                 }
372         }
373
374         public string PublicUrl {
375                 get {
376                         return tree.HelpSource != null
377                                 ? tree.HelpSource.GetPublicUrl (URL)
378                                 : URL;
379                 }
380         }
381
382         int IComparable.CompareTo (object obj)
383         {
384                 Node other = obj as Node;
385                 if (other == null)
386                         return -1;
387
388                 if (position < 0)
389                         LoadNode ();
390                 if (other.position < 0)
391                         other.LoadNode ();
392
393                 Regex digits = new Regex (@"([\d]+)|([^\d]+)");
394                 MatchEvaluator eval = delegate (Match m) {
395                         return (m.Value.Length > 0 && char.IsDigit (m.Value [0])) 
396                                 ? m.Value.PadLeft (System.Math.Max (caption.Length, other.caption.Length)) 
397                                 : m.Value;
398                 };
399                 return digits.Replace (caption, eval).CompareTo (digits.Replace (other.caption, eval));
400         }
401 }
402
403 //
404 // The HelpSource class keeps track of the archived data, and its
405 // tree
406 //
407 public class HelpSource {
408         static int id;
409         public static bool use_css = false;
410         public static string css_code;
411         public static string CssCode {
412                 get {
413                         if (css_code != null)
414                                 return css_code;
415
416                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
417                         Stream str_css = assembly.GetManifestResourceStream ("base.css");
418                         StringBuilder sb = new StringBuilder ((new StreamReader (str_css)).ReadToEnd());
419                         sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
420                         sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
421                         css_code = sb.ToString ();
422                         return css_code;
423                 }
424                 set { css_code = value; }
425         }
426
427         public virtual string InlineCss {
428                 get { return CssCode; }
429         }
430
431         public virtual string InlineJavaScript {
432                 get { return null; }
433         }
434
435         public static bool FullHtml = true;
436
437         // should only be enabled by ASP.NET webdoc
438         public static bool UseWebdocCache;
439
440         //
441         // The unique ID for this HelpSource.
442         //
443         int source_id;
444         DateTime zipFileWriteTime;
445         string name;
446         TraceLevel trace_level = TraceLevel.Warning;
447         protected bool nozip;
448         protected string base_dir;
449
450         public HelpSource (string base_filename, bool create)
451         {
452                 this.name = Path.GetFileName (base_filename);
453                 tree_filename = base_filename + ".tree";
454                 zip_filename = base_filename + ".zip";
455                 base_dir = XmlDocUtils.GetCacheDirectory (base_filename);
456                 if (UseWebdocCache && !create && Directory.Exists (base_dir)) {
457                         nozip = true;
458                 }
459
460                 if (create)
461                         SetupForOutput ();
462                 else 
463                         Tree = new Tree (this, tree_filename);
464
465                 source_id = id++;
466                 try {
467                         FileInfo fi = new FileInfo (zip_filename);
468                         zipFileWriteTime = fi.LastWriteTime;
469                 } catch {
470                         zipFileWriteTime = DateTime.Now;
471                 }
472         }
473         
474         public HelpSource() {
475                 Tree = new Tree (this, "Blah", "Blah");
476                 source_id = id++;
477         }
478
479         public DateTime ZipFileWriteTime {
480                 get {
481                         return zipFileWriteTime;
482                 }
483         }
484         
485         public int SourceID {
486                 get {
487                         return source_id;
488                 }
489         }
490         
491         public string Name {
492                 get {
493                         return name;
494                 }
495         }
496
497         public TraceLevel TraceLevel {
498                 get { return trace_level; }
499                 set { trace_level = value; }
500         }
501         
502         ZipFile zip_file;
503         
504         /// <summary>
505         ///   Returns a stream from the packaged help source archive
506         /// </summary>
507         public virtual Stream GetHelpStream (string id)
508         {
509                 if (nozip) {
510                         string path = XmlDocUtils.GetCachedFileName (base_dir, id);
511                         if (File.Exists (path))
512                                 return File.OpenRead (path);
513                         return null;
514                 }
515
516                 if (zip_file == null)
517                         zip_file = new ZipFile (zip_filename);
518
519                 ZipEntry entry = zip_file.GetEntry (id);
520                 if (entry != null)
521                         return zip_file.GetInputStream (entry);
522                 return null;
523         }
524         
525         public string GetRealPath (string file)
526         {
527                 if (zip_file == null)
528                         zip_file = new ZipFile (zip_filename);
529
530                 ZipEntry entry = zip_file.GetEntry (file);
531                 if (entry != null && entry.ExtraData != null)
532                         return ConvertToString (entry.ExtraData);
533                 return null;
534         }
535         
536         public XmlReader GetHelpXml (string id)
537         {
538                 if (nozip) {
539                         Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
540                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
541                         return new XmlTextReader (url, s);
542                 }
543
544                 if (zip_file == null)
545                         zip_file = new ZipFile (zip_filename);
546
547                 ZipEntry entry = zip_file.GetEntry (id);
548                 if (entry != null) {
549                         Stream s = zip_file.GetInputStream (entry);
550                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
551                         return new XmlTextReader (url, s);
552                 }
553                 return null;
554         }
555         
556         public virtual XmlDocument GetHelpXmlWithChanges (string id)
557         {
558                 if (nozip) {
559                         Stream s = File.OpenRead (XmlDocUtils.GetCachedFileName (base_dir, id));
560                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
561                         XmlReader r = new XmlTextReader (url, s);
562                         XmlDocument ret = new XmlDocument ();
563                         ret.Load (r);
564                         return ret;
565                 }
566
567                 if (zip_file == null)
568                         zip_file = new ZipFile (zip_filename);
569
570                 ZipEntry entry = zip_file.GetEntry (id);
571                 if (entry != null) {
572                         Stream s = zip_file.GetInputStream (entry);
573                         string url = "monodoc:///" + SourceID + "@" + System.Web.HttpUtility.UrlEncode (id) + "@";
574                         XmlReader r = new XmlTextReader (url, s);
575                         XmlDocument ret = new XmlDocument ();
576                         ret.Load (r);
577                         
578                         if (entry.ExtraData != null)
579                                 EditingUtils.AccountForChanges (ret, Name, ConvertToString (entry.ExtraData));
580                         
581                         return ret;
582                 }
583                 return null;    
584         }
585         
586         /// <summary>
587         ///   Get a nice, unique expression for any XPath node that you get.
588         ///   This function is used by editing to get the expression to put
589         ///   on to the file. The idea is to create an expression that is resistant
590         ///   to changes in the structure of the XML.
591         /// </summary>
592         public virtual string GetNodeXPath (XPathNavigator n)
593         {
594                 return EditingUtils.GetXPath (n.Clone ());
595         }
596         
597         public string GetEditUri (XPathNavigator n)
598         {
599                 return EditingUtils.FormatEditUri (n.BaseURI, GetNodeXPath (n));
600         }
601         
602         static string ConvertToString (byte[] data)
603         {
604                 return Encoding.UTF8.GetString(data);
605         }
606         
607         static byte[] ConvertToArray (string str)
608         {
609                 return Encoding.UTF8.GetBytes(str);
610         }
611
612         /// <summary>
613         ///   The tree that is being populated
614         /// </summary>
615         public Tree Tree;
616         public RootTree RootTree;
617
618         // Base filename used by this HelpSource.
619         string tree_filename, zip_filename;
620
621         // Used for ziping. 
622         const int buffer_size = 65536;
623         ZipOutputStream zip_output;
624         byte [] buffer;
625         
626         HelpSource (string base_filename)
627         {
628         }
629                 
630         void SetupForOutput ()
631         {
632                 Tree = new Tree (this, "", "");
633
634                 FileStream stream = File.Create (zip_filename);
635                 
636                 zip_output = new ZipOutputStream (stream);
637                 zip_output.SetLevel (9);
638
639                 buffer = new byte [buffer_size];
640         }               
641
642         /// <summary>
643         ///   Saves the tree and the archive
644         /// </summary>
645         public void Save ()
646         {
647                 Tree.Save (tree_filename);
648                 zip_output.Finish ();
649                 zip_output.Close ();
650         }
651
652         int code;
653
654         string GetNewCode ()
655         {
656                 return String.Format ("{0}", code++);
657         }
658
659         /// <summary>
660         ///   Providers call this to store a file they will need, and the return value
661         ///   is the name that was assigned to it
662         /// </summary>
663         public string PackFile (string file)
664         {
665                 string entry_name = GetNewCode ();
666                 return PackFile (file, entry_name);
667         }
668
669         public string PackFile (string file, string entry_name)
670         {
671                 using (FileStream input = File.OpenRead (file)) {
672                         PackStream (input, entry_name, file);
673                 }
674
675                 return entry_name;
676         }
677         
678         public void PackStream (Stream s, string entry_name)
679         {
680                 PackStream (s, entry_name, null);
681         }
682         
683         void PackStream (Stream s, string entry_name, string realPath)
684         {
685                 ZipEntry entry = new ZipEntry (entry_name);
686                                 
687                 if (realPath != null)
688                         entry.ExtraData = ConvertToArray (realPath);
689                 
690                 zip_output.PutNextEntry (entry);
691                 int n;
692                         
693                 while ((n = s.Read (buffer, 0, buffer_size)) > 0){
694                         zip_output.Write (buffer, 0, n);
695                 }       
696         }
697         
698         public void PackXml (string fname, XmlDocument doc, string real_path)
699         { 
700                 ZipEntry entry = new ZipEntry (fname); 
701                 if (real_path != null) 
702                         entry.ExtraData = ConvertToArray(real_path);
703
704                 zip_output.PutNextEntry (entry);
705                 XmlTextWriter xmlWriter = new XmlTextWriter (zip_output, Encoding.UTF8);
706                 doc.WriteContentTo (xmlWriter);
707                 xmlWriter.Flush ();
708         }
709         
710         public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
711         {
712                 throw new NotImplementedException ();
713         }
714
715         public virtual string GetPublicUrl (string id)
716         {
717                 return id;
718         }
719         
720         public virtual string GetText (string url, out Node n)
721         {
722                 n = null;
723                 return null;
724         }
725
726         protected string GetCachedText (string url)
727         {
728                 if (!nozip)
729                         return null;
730                 string file = XmlDocUtils.GetCachedFileName (base_dir, url);
731                 if (!File.Exists (file))
732                         return null;
733                 return File.OpenText (file).ReadToEnd ();
734         }
735
736         public virtual Stream GetImage (string url)
737         {
738                 return null;
739         }
740         
741         //
742         // Default method implementation does not satisfy the request
743         //
744         public virtual string RenderTypeLookup (string prefix, string ns, string type, string member, out Node n)
745         {
746                 n = null;
747                 return null;
748         }
749
750         public virtual string RenderNamespaceLookup (string nsurl, out Node n)
751         {
752                 n = null;
753                 return null;
754         }
755
756         //
757         // Populates the index.
758         //
759         public virtual void PopulateIndex (IndexMaker index_maker)
760         {
761         }
762         
763         //
764         // Build an html document
765         //
766         public static string BuildHtml (string css, string html_code)
767         {
768                 return BuildHtml (css, null, html_code);
769         }
770
771         internal static string BuildHtml (string css, string js, string html_code) {
772                 if (!FullHtml) {
773                         return html_code;
774                 }
775                 StringWriter output = new StringWriter ();
776                 output.Write ("<html><head>");
777                 output.Write ("<style type=\"text/css\">");
778                 output.Write (CssCode);
779                 output.Write (css);
780                 output.Write ("</style>");
781
782                 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
783                 Stream str_js = assembly.GetManifestResourceStream ("helper.js");
784                 StringBuilder sb = new StringBuilder ((new StreamReader (str_js)).ReadToEnd());
785                 output.Write ("<script type=\"text/JavaScript\">\n");
786                 output.Write (sb.ToString ());
787                 output.Write ("</script>\n");
788                 
789                 if (js != null) {
790                         output.Write ("<script type=\"text/JavaScript\">\n");
791                         output.Write (js);
792                         output.Write ("\n</script>");
793                 }
794
795                 output.Write ("</head><body>");
796                 output.Write (html_code);
797                 output.Write ("</body></html>");
798                 return output.ToString ();
799         }
800
801         //
802         // Create different Documents for adding to Lucene search index
803         // The default action is do nothing. Subclasses should add the docs
804         // 
805         public virtual void PopulateSearchableIndex (IndexWriter writer) {
806                 return;
807         }
808
809         public void Message (TraceLevel level, string format, params object[] args)
810         {
811                 if ((int) level <= (int) trace_level)
812                         Console.WriteLine (format, args);
813         }
814
815         public void Error (string format, params object[] args)
816         {
817                 Console.Error.WriteLine (format, args);
818         }
819 }
820
821 public abstract class Provider {
822         //
823         // This code is used to "tag" all the different sources
824         //
825         static short serial;
826
827         public int code;
828         
829         public Provider ()
830         {
831                 code = serial++;
832         }
833
834         public abstract void PopulateTree (Tree tree);
835
836         //
837         // Called at shutdown time after the tree has been populated to perform
838         // any fixups or final tasks.
839         //
840         public abstract void CloseTree (HelpSource hs, Tree tree);
841 }
842
843 public class RootTree : Tree {
844         string basedir;
845         
846         public static ArrayList UncompiledHelpSources = new ArrayList();
847         
848         public const int MonodocVersion = 1;
849         
850         public static RootTree LoadTree ()
851         {
852                 return LoadTree (null);
853         }
854         
855         //
856         // Loads the tree layout
857         //
858         public static RootTree LoadTree (string basedir)
859         {
860                 if (basedir == null) {
861                         string myPath = System.Reflection.Assembly.GetExecutingAssembly ().Location;
862                         string cfgFile = myPath + ".config";
863                         if (!File.Exists (cfgFile)) {
864                                 basedir = ".";
865                         }
866                         else {
867                                 XmlDocument d = new XmlDocument ();
868                                 d.Load (cfgFile);
869                                 basedir = d.SelectSingleNode ("config/path").Attributes ["docsPath"].Value;
870                         }
871                 }
872                 XmlDocument doc = new XmlDocument ();
873
874                 RootTree root = new RootTree ();
875                 root.basedir = basedir;
876                 
877                 //
878                 // Load the layout
879                 //
880                 string layout = Path.Combine (basedir, "monodoc.xml");
881                 doc.Load (layout);
882                 XmlNodeList nodes = doc.SelectNodes ("/node/node");
883
884                 root.name_to_node ["root"] = root;
885                 root.name_to_node ["libraries"] = root;
886                 root.Populate (root, nodes);
887
888                 Node third_party = root.LookupEntryPoint ("various");
889                 if (third_party == null) {
890                         Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
891                         third_party = root;
892                 }
893
894                 //
895                 // Load the sources
896                 //
897                 root.AddSource (Path.Combine (basedir, "sources"));
898                 
899                 foreach (string path in UncompiledHelpSources) {
900                         EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(path);
901                         hs.RootTree = root;
902                         root.help_sources.Add (hs);
903                         string epath = "extra-help-source-" + hs.Name;
904                         Node hsn = root.CreateNode (hs.Name, "root:/" + epath);
905                         root.name_to_hs [epath] = hs;
906                         hsn.EnsureNodes ();
907                         foreach (Node n in hs.Tree.Nodes){
908                                 hsn.AddNode (n);
909                         }
910                 }
911                 
912                 // Clean the tree
913                 PurgeNode(root);
914
915                 root.Sort ();
916
917                 return root;
918         }
919
920         public void AddSource (string sources_dir)
921         {
922                 Node third_party = LookupEntryPoint ("various") ?? this;
923
924                 string [] files = Directory.GetFiles (sources_dir);
925
926                 foreach (string file in files){
927                         if (!file.EndsWith (".source"))
928                                 continue;
929
930                         XmlDocument doc = new XmlDocument ();
931                         try {
932                                 doc.Load (file);
933                         } catch {
934                                 Console.Error.WriteLine ("Error: Could not load source file {0}", file);
935                                 continue;
936                         }
937
938                         XmlNodeList extra_nodes = doc.SelectNodes ("/monodoc/node");
939                         if (extra_nodes.Count > 0)
940                                 Populate (third_party, extra_nodes);
941
942                         XmlNodeList sources = doc.SelectNodes ("/monodoc/source");
943                         if (sources == null){
944                                 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", file);
945                                 continue;
946                         }
947                         foreach (XmlNode source in sources){
948                                 XmlAttribute a = source.Attributes ["provider"];
949                                 if (a == null){
950                                         Console.Error.WriteLine ("Error: no provider in <source>");
951                                         continue;
952                                 }
953                                 string provider = a.InnerText;
954                                 a = source.Attributes ["basefile"];
955                                 if (a == null){
956                                         Console.Error.WriteLine ("Error: no basefile in <source>");
957                                         continue;
958                                 }
959                                 string basefile = a.InnerText;
960                                 a = source.Attributes ["path"];
961                                 if (a == null){
962                                         Console.Error.WriteLine ("Error: no path in <source>");
963                                         continue;
964                                 }
965                                 string path = a.InnerText;
966
967                                 string basefilepath = Path.Combine (sources_dir, basefile);
968                                 HelpSource hs = GetHelpSource (provider, basefilepath);
969                                 if (hs == null)
970                                         continue;
971                                 hs.RootTree = this;
972                                 help_sources.Add (hs);
973                                 name_to_hs [path] = hs;
974
975                                 Node parent = LookupEntryPoint (path);
976                                 if (parent == null){
977                                         Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
978                                         parent = third_party;
979                                 }
980
981                                 foreach (Node n in hs.Tree.Nodes){
982                                         parent.AddNode (n);
983                                 }
984                                 parent.Sort ();
985                         }
986                 }
987         }
988         
989         // Delete nodes which does not have documentaiton (source)
990         static bool PurgeNode(Node node)
991         {
992                 bool purge = false;
993                 
994                 if (!node.Documented)
995                 {
996                         ArrayList del_child = new ArrayList();
997                         //Delete node unless any child has documentation
998                         bool purged_child = false;
999                         foreach (Node child in node.Nodes)
1000                         {
1001                                 purged_child = PurgeNode(child);
1002                                 if (purged_child) 
1003                                 {
1004                                         del_child.Add(child);
1005                                 }
1006                         }
1007                                 
1008                         // delete the node if all its children are to be deleted
1009                         purge = (node.Nodes.Count == del_child.Count); 
1010                                 
1011                         // delete children
1012                         foreach (Node child in del_child)
1013                         {
1014                                 node.DelNode(child);
1015                         }
1016                 }
1017                 
1018                 return purge;
1019         }
1020
1021         public static string[] GetSupportedFormats ()
1022         {
1023                 return new string[]{
1024                         "ecma", 
1025                         "ecmaspec", 
1026                         "error", 
1027                         "hb", 
1028                         "man", 
1029                         "simple", 
1030                         "xhtml"
1031                 };
1032         }
1033         
1034         public static HelpSource GetHelpSource (string provider, string basefilepath)
1035         {
1036                 try {
1037                         switch (provider){
1038                         case "ecma":
1039                                 return new EcmaHelpSource (basefilepath, false);
1040                         case "ecma-uncompiled":
1041                                 return new EcmaUncompiledHelpSource (basefilepath);
1042                         case "monohb":
1043                                 return new MonoHBHelpSource(basefilepath, false);
1044                         case "xhtml": case "hb":
1045                                 return new XhtmlHelpSource (basefilepath, false);
1046                         case "man":
1047                                 return new ManHelpSource (basefilepath, false);
1048                         case "simple":
1049                                 return new SimpleHelpSource (basefilepath, false);
1050                         case "error":
1051                                 return new ErrorHelpSource (basefilepath, false);
1052                         case "ecmaspec":
1053                                 return new EcmaSpecHelpSource (basefilepath, false);
1054                         case "addins":
1055                                 return new AddinsHelpSource (basefilepath, false);
1056                         default:
1057                                 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
1058                                 break;
1059                         }
1060                         return null;
1061                 }
1062                 catch (FileNotFoundException) {
1063                         Console.Error.WriteLine ("Error: did not find one of the files in sources/"+basefilepath);
1064                         return null;
1065                 }
1066         }
1067
1068         public static Provider GetProvider (string provider, params string[] basefilepaths)
1069         {
1070                 switch (provider) {
1071                 case "addins":
1072                         return new AddinsProvider (basefilepaths [0]);
1073                 case "ecma": {
1074                         EcmaProvider p = new EcmaProvider ();
1075                         foreach (string d in basefilepaths)
1076                                 p.AddDirectory (d);
1077                         return p;
1078                 }
1079                 case "ecmaspec":
1080                         return new EcmaSpecProvider (basefilepaths [0]);
1081                 case "error":
1082                         return new ErrorProvider (basefilepaths [0]);
1083                 case "man":
1084                         return new ManProvider (basefilepaths);
1085                 case "simple":
1086                         return new SimpleProvider (basefilepaths [0]);
1087                 case "xhtml":
1088                 case "hb":
1089                         return new XhtmlProvider (basefilepaths [0]);
1090                 default:
1091                         throw new NotSupportedException (provider);
1092                 }
1093         }
1094
1095         //
1096         // Maintains the name to node mapping
1097         //
1098         Hashtable name_to_node = new Hashtable ();
1099         Hashtable name_to_hs = new Hashtable ();
1100         
1101         void Populate (Node parent, XmlNodeList xml_node_list)
1102         {
1103                 foreach (XmlNode xml_node in xml_node_list){
1104                         XmlAttribute e = xml_node.Attributes ["parent"];
1105                         if (e != null && name_to_node.ContainsKey (e.InnerText)) {
1106                                 Node p = (Node) name_to_node [e.InnerText];
1107                                 xml_node.Attributes.Remove (e);
1108                                 Populate (p, xml_node.SelectNodes ("."));
1109                                 continue;
1110                         }
1111                         e = xml_node.Attributes ["label"];
1112                         if (e == null){
1113                                 Console.Error.WriteLine ("`label' attribute missing in <node>");
1114                                 continue;
1115                         }
1116                         string label = e.InnerText;
1117                         e = xml_node.Attributes ["name"];
1118                         if (e == null){
1119                                 Console.Error.WriteLine ("`name' attribute missing in <node>");
1120                                 continue;
1121                         }
1122                         string name = e.InnerText;
1123
1124                         Node n = parent.LookupNode (label, "root:/" + name);
1125                         n.EnsureNodes ();
1126                         name_to_node [name] = n;
1127                         XmlNodeList children = xml_node.SelectNodes ("./node");
1128                         if (children != null)
1129                                 Populate (n, children);
1130                 }
1131         }
1132
1133         public Node LookupEntryPoint (string name)
1134         {
1135                 return (Node) name_to_node [name];
1136         }
1137         
1138         ArrayList help_sources;
1139         DateTime lastHelpSourceTime;
1140         
1141         RootTree () : base (null, "Mono Documentation", "root:")
1142         {
1143                 nodes = new ArrayList ();
1144                 help_sources = new ArrayList ();
1145                 lastHelpSourceTime = DateTime.MinValue;
1146         }
1147
1148         public DateTime LastHelpSourceTime {
1149                 get {
1150                         return lastHelpSourceTime;
1151                 }
1152         }
1153         
1154         public static bool GetNamespaceAndType (string url, out string ns, out string type)
1155         {
1156                 int nsidx = -1;
1157                 int numLt = 0;
1158                 for (int i = 0; i < url.Length; ++i) {
1159                         char c = url [i];
1160                         switch (c) {
1161                         case '<':
1162                         case '{':
1163                                 ++numLt;
1164                                 break;
1165                         case '>':
1166                         case '}':
1167                                 --numLt;
1168                                 break;
1169                         case '.':
1170                                 if (numLt == 0)
1171                                         nsidx = i;
1172                                 break;
1173                         }
1174                 }
1175
1176                 if (nsidx == -1) {
1177                         Console.Error.WriteLine ("Did not find dot in: " + url);
1178                         ns = null;
1179                         type = null;
1180                         return false;
1181                 }
1182                 ns = url.Substring (0, nsidx);
1183                 type = url.Substring (nsidx + 1);
1184                 
1185                 //Console.Error.WriteLine ("GetNameSpaceAndType (ns={0}, type={1}", ns, type);
1186                 return true;
1187         }
1188
1189         public XmlDocument GetHelpXml (string url)
1190         {
1191                 string rest = url.Substring (2);
1192                 string ns, type;
1193
1194                 if (!GetNamespaceAndType (rest, out ns, out type))
1195                         return null;
1196
1197                 foreach (HelpSource hs in help_sources) {
1198                         EcmaHelpSource ehs = hs as EcmaHelpSource;
1199                         if (ehs == null)
1200                                 continue;
1201                         string id = ehs.GetIdFromUrl ("T:", ns, type);
1202                         if (id == null)
1203                                 continue;
1204                         XmlDocument doc = hs.GetHelpXmlWithChanges (id);
1205                         if (doc != null)
1206                                 return doc;
1207                 }
1208                 return null;
1209         }
1210         
1211         public string TypeLookup (string url, out Node match_node)
1212         {
1213                 string rest = Regex.Replace (url, @"^T:\s*", "");
1214                 string ns, type;
1215
1216                 if (!GetNamespaceAndType (rest, out ns, out type)){
1217                         match_node = null;
1218                         return null;
1219                 }
1220                 
1221                 foreach (HelpSource hs in help_sources){
1222                         string s = hs.RenderTypeLookup ("T:", ns, type, null, out match_node);
1223                         
1224                         if (s != null) {
1225                                 lastHelpSourceTime = hs.ZipFileWriteTime;
1226                                 return s;
1227                         }
1228                 }
1229                 match_node = null;
1230                 return null;
1231         }
1232
1233         public string MemberLookup (string prefix, string url, out Node match_node)
1234         {
1235                 string rest = Regex.Replace (url, @"^.:\s*", "");
1236                 
1237                 // Dots in the arg list (for methods) confuse this.
1238                 // Chop off the arg list for now and put it back later.
1239                 string arglist = "";
1240                 int argliststart = rest.IndexOf("(");
1241                 if (argliststart >= 0) {
1242                         arglist = rest.Substring(argliststart);
1243                         rest = rest.Substring(0, argliststart);
1244                 }
1245
1246                 string ns_type, member;
1247         
1248                 if (prefix != "C:") {
1249                         int member_idx = rest.LastIndexOf (".");
1250         
1251                         // The dot in .ctor (if it's a M: link) would confuse this.
1252                         if (rest.EndsWith("..ctor")) member_idx--;
1253         
1254                         ns_type = rest.Substring (0, member_idx);
1255                         member = rest.Substring (member_idx + 1);
1256                 } else {
1257                         // C: links don't have the .ctor member part as it would in a M: link
1258                         // Even though externally C: links are different from M: links,
1259                         // C: links get transformed into M:-style links (with .ctor) here.
1260                         ns_type = rest;
1261                         member = ".ctor";
1262                 }
1263  
1264
1265                 //Console.WriteLine ("NS_TYPE: {0}  MEMBER: {1}", ns_type, member);
1266
1267                 string ns, type;
1268                 if (!GetNamespaceAndType (ns_type, out ns, out type)){
1269                         match_node = null;
1270                         return null;
1271                 }
1272                 
1273                 foreach (HelpSource hs in help_sources){
1274                         string s = hs.RenderTypeLookup (prefix, ns, type, member + arglist, out match_node);
1275                         
1276                         if (s != null) {
1277                                 lastHelpSourceTime = hs.ZipFileWriteTime;
1278                                 return s;
1279                         }
1280                 }
1281                 match_node = null;
1282                 return null;
1283         }
1284
1285         public Stream GetImage (string url)
1286         {
1287                 if (url.StartsWith ("source-id:")){
1288                         string rest = url.Substring (10);
1289                         int p = rest.IndexOf (":");
1290                         string str_idx = rest.Substring (0, p);
1291                         int idx = 0;
1292
1293                         try {
1294                                 idx = Int32.Parse (str_idx);
1295                         } catch {
1296                                 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1297                                 return null;
1298                         }
1299
1300                         HelpSource hs = GetHelpSourceFromId (idx);
1301                         lastHelpSourceTime = hs.ZipFileWriteTime;
1302                         return hs.GetImage (rest.Substring (p + 1));
1303                 } else {
1304                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof(RootTree));                        
1305                         return assembly.GetManifestResourceStream (url);
1306                 }
1307                 lastHelpSourceTime = DateTime.MinValue;
1308                 return null;
1309         }
1310         
1311         public HelpSource GetHelpSourceFromId (int id)
1312         {
1313                 return (HelpSource) help_sources [id];
1314         }
1315         
1316         //
1317         // Fetches the node title
1318         //
1319         public string GetTitle (string url)
1320         {
1321                 Node match_node;
1322
1323                 if (url == null || url.StartsWith ("root:"))
1324                         return "Mono Documentation";
1325                 
1326                 if (url.Length > 2 && url [1] == ':'){
1327                         switch (url [0]){
1328                         case 'N':
1329                                 return url.Substring (2) + " Namespace";
1330
1331                         case 'T':
1332                                 string s = TypeLookup (url, out match_node);
1333                                 if (match_node != null)
1334                                         return match_node.Caption;
1335                                 return url.Substring (2) + " type";
1336
1337                 case 'M':
1338                 case 'F':
1339                 case 'P':
1340                 case 'E':
1341                 case 'C':
1342                 case 'O':
1343                         MemberLookup (url.Substring (0,2), url, out match_node);
1344                         if (match_node != null)
1345                                 return match_node.Caption;
1346                         break;
1347                         }
1348                 }
1349                 
1350                 return "Mono Documentation";
1351         }
1352         
1353         string home_cache;
1354         /// <summary>
1355         ///    Allows every HelpSource to try to provide the content for this
1356         ///    URL.
1357         /// </summary>
1358         public string RenderUrl (string url, out Node match_node)
1359         {
1360                 lastHelpSourceTime = DateTime.MinValue;
1361                 if (url == "root:") {
1362                         match_node = this;
1363
1364                         // look whether there are contribs
1365                         GlobalChangeset chgs = EditingUtils.changes;
1366                         StringBuilder con = new StringBuilder ();
1367                         
1368                         //add links to the contrib
1369                         int oldContrib = 0, contribs = 0;
1370                         con.Append ("<ul>");
1371                         foreach (DocSetChangeset dscs in chgs.DocSetChangesets) 
1372                                 foreach (FileChangeset fcs in dscs.FileChangesets) 
1373                                         foreach (Change c in fcs.Changes) {
1374                                                 if (c.NodeUrl == null) {
1375                                                         if (c.Serial == SettingsHandler.Settings.SerialNumber)
1376                                                                 oldContrib++;
1377                                                 } else if (c.Serial == SettingsHandler.Settings.SerialNumber) {
1378                                                         contribs++;
1379                                                         con.Append (String.Format ("<li><a href=\"{0}\">{0}</a></li>", c.NodeUrl));
1380                                                 }
1381                                         }
1382                         
1383                         string contrib = (oldContrib + contribs) == 1?"There is {0} contribution":"There are {0} contributions";
1384                         con.Insert (0, String.Format (contrib, oldContrib + contribs) + " pending upload <i>(Contributing--&gt; Upload)</i>", 1);
1385                         con.Append ("</ul>");
1386                         if (oldContrib == 1)
1387                                 con.Append ("<i>You have 1 contribution that is not listed below that will be sent the next time you upload contributions. Only contributions made from now on will be listed.</i>");
1388                         else if (oldContrib > 1)
1389                                 con.Append ("<i>You have " + oldContrib + "contributions that are not listed below and will be sent the next time you upload contributions. Only contributions made from now on will be listed.</i>");
1390
1391                         //start the rendering
1392                         if (!HelpSource.use_css) {
1393                                 StringBuilder sb = new StringBuilder ("<table bgcolor=\"#b0c4de\" width=\"100%\" cellpadding=\"5\"><tr><td><h3>Mono Documentation Library</h3></td></tr></table>");
1394                         
1395                                 foreach (Node n in Nodes)
1396                                         sb.AppendFormat ("<a href='{0}'>{1}</a><br/>", n.Element, n.Caption);
1397                         
1398                                 //contributions
1399                                 sb.Append ("<br><table bgcolor=\"#fff3f3\" width=\"100%\" cellpadding=\"5\"><tr><td>");
1400                                 sb.Append ("<h5>Contributions</h5><br>");
1401                                 if ((oldContrib + contribs) == 0) {
1402                                         sb.Append ("<p><b>You have not made any contributions yet.</b></p>");
1403                                         sb.Append ("<p>The Documentation of the libraries is not complete and your contributions would be greatly appreciated. The procedure is easy, browse to the part of the documentation you want to contribute to and click on the <font color=\"blue\">[Edit]</font> link to start writing the documentation.</p>");
1404                                         sb.Append ("<p>When you are happy with your changes, use the Contributing--&gt; Upload Contributions menu to send your contributions to our server.</p></div>");
1405                                 } else {
1406                                         sb.Append (con.ToString ());
1407                                 }
1408                                 sb.Append ("</td></tr></table>");
1409                                 return sb.ToString ();  
1410                         } else {
1411                                 if (home_cache == null) {
1412                                         System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly (typeof (HelpSource));
1413                                         Stream hp_stream = assembly.GetManifestResourceStream ("home.html");
1414                                         home_cache = (new StreamReader (hp_stream)).ReadToEnd ();
1415                                 }
1416                                 StringBuilder sb = new StringBuilder (home_cache);
1417                                 // adjust fonts
1418                                 sb.Replace ("@@FONT_FAMILY@@", SettingsHandler.Settings.preferred_font_family);
1419                                 sb.Replace ("@@FONT_SIZE@@", SettingsHandler.Settings.preferred_font_size.ToString());
1420                                 //contributions
1421                                 if ((oldContrib + contribs) == 0) {
1422                                         sb.Replace ("@@CONTRIB_DISP@@", "display: none;");
1423                                 } else {
1424                                         sb.Replace ("@@NO_CONTRIB_DISP@@", "display: none;");
1425                                         sb.Replace ("@@CONTRIBS@@", con.ToString ());
1426                                 }
1427                                         
1428                                 // load the url of nodes
1429                                 String add_str;
1430                                 StringBuilder urls = new StringBuilder ();
1431                                 foreach (Node n in Nodes) {
1432                                         add_str = String.Format ("<li><a href=\"{0}\">{1}</a></li>", n.Element, n.Caption);
1433                                         urls.Append (add_str);
1434                                 }
1435                                 sb.Replace ("@@API_DOCS@@", urls.ToString ());
1436                                                 
1437                                 return sb.ToString ();
1438                         }
1439                 } 
1440                 
1441                 if (url.StartsWith ("root:")) {
1442                         match_node = ((Node)name_to_node [url.Substring (6)]);
1443                         HelpSource hs = ((HelpSource)name_to_hs [url.Substring (6)]);
1444                         if (hs == null) 
1445                         {
1446                                 return GenerateNodeIndex(match_node);
1447                         }
1448                                 
1449                         Node dummy;
1450                         lastHelpSourceTime = hs.ZipFileWriteTime;
1451                         return hs.GetText ("root:", out dummy);
1452                 }
1453         
1454                 
1455                 if (url.StartsWith ("source-id:")){
1456                         string rest = url.Substring (10);
1457                         int p = rest.IndexOf (":");
1458                         string str_idx = rest.Substring (0, p);
1459                         int idx = 0;
1460
1461                         try {
1462                                 idx = Int32.Parse (str_idx);
1463                         } catch {
1464                                 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, str_idx);
1465                                 match_node = null;
1466                                 return null;
1467                         }
1468                         HelpSource hs = (HelpSource) help_sources [idx];
1469                         // Console.WriteLine ("Attempting to get docs from: " + rest.Substring (p + 1));
1470                         lastHelpSourceTime = hs.ZipFileWriteTime;
1471                         return hs.GetText (rest.Substring (p + 1), out match_node);
1472                 }
1473
1474                 if (url.Length < 2){
1475                         match_node = null;
1476                         return null;
1477                 }
1478                 
1479                 string prefix = url.Substring (0, 2);
1480                 
1481                 switch (prefix.ToUpper ()){
1482                 case "N:":
1483                         foreach (HelpSource hs in help_sources){
1484                                 string s = hs.RenderNamespaceLookup (url, out match_node);
1485                                 if (s != null) {
1486                                         lastHelpSourceTime = hs.ZipFileWriteTime;
1487                                         return s;
1488                                 }
1489                         }
1490                         match_node = null;
1491                         return null;
1492
1493                 case "T:":
1494                         return TypeLookup (url, out match_node);
1495
1496                 case "M:":
1497                 case "F:":
1498                 case "P:":
1499                 case "E:":
1500                 case "C:":
1501                 case "O:":
1502                         return MemberLookup (prefix, url, out match_node);
1503                 
1504                 default:
1505                         foreach (HelpSource hs in help_sources){
1506                                 string s = hs.GetText (url, out match_node);
1507                                 
1508                                 if (s != null) {
1509                                         lastHelpSourceTime = hs.ZipFileWriteTime;
1510                                         return s;
1511                                 }
1512                         }
1513                         match_node = null;
1514                         return null;
1515                 }
1516         }
1517         
1518         public string GenerateNodeIndex (Node node)
1519         {
1520                 StringBuilder buf = new StringBuilder();
1521                 buf.AppendFormat("<H3>{0}</H3>", node.Caption);
1522                 buf.Append("<ul>");
1523                 foreach (Node child in node.Nodes)
1524                 {
1525                         buf.AppendFormat("<li><a href=\"{0}\">{1}</a>", child.URL, child.Caption);
1526                 }
1527                 buf.Append("</ul>");
1528                 return buf.ToString();
1529         }
1530         
1531         public IndexReader GetIndex ()
1532         {
1533                 //try to load from basedir
1534                 string index_file = Path.Combine (basedir, "monodoc.index");
1535                 if (File.Exists (index_file))
1536                         return IndexReader.Load (index_file);
1537                 //then, try to load from config dir
1538                 index_file = Path.Combine (SettingsHandler.Path, "monodoc.index");
1539                 return IndexReader.Load (index_file);
1540                 
1541         }
1542
1543         public static void MakeIndex ()
1544         {
1545                 RootTree root = LoadTree ();
1546                 if (root == null)
1547                         return;
1548
1549                 IndexMaker index_maker = new IndexMaker ();
1550                 
1551                 foreach (HelpSource hs in root.help_sources){
1552                         hs.PopulateIndex (index_maker);
1553                 }
1554
1555                 // if the user has no write permissions use config dir
1556                 string path = Path.Combine (root.basedir, "monodoc.index");
1557                 try {
1558                         index_maker.Save (path);
1559                 } catch (System.UnauthorizedAccessException) {
1560                         path = Path.Combine (SettingsHandler.Path, "monodoc.index");
1561                         try {
1562                                 index_maker.Save (path);
1563                         } catch (System.UnauthorizedAccessException) {
1564                                 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (SettingsHandler.Path, "monodoc.index")); 
1565                                 return;
1566                         }
1567                 }
1568
1569                 if (IsUnix){
1570                         // No octal in C#, how lame is that
1571                         chmod (path, 0x1a4);
1572                 }
1573                 Console.WriteLine ("Documentation index updated");
1574         }
1575
1576         static bool IsUnix {
1577                 get {
1578                         int p = (int) Environment.OSVersion.Platform;
1579                         return ((p == 4) || (p == 128) || (p == 6));
1580                 }
1581         }
1582
1583         // Search Index
1584         public SearchableIndex GetSearchIndex ()
1585         {
1586                 //try to load from basedir
1587                 string index_file = Path.Combine (basedir, "search_index");
1588                 if (Directory.Exists (index_file))
1589                         return SearchableIndex.Load (index_file);
1590                 //then, try to load from config dir
1591                 index_file = Path.Combine (SettingsHandler.Path, "search_index");
1592                 return SearchableIndex.Load (index_file);
1593         }
1594
1595         public static void MakeSearchIndex ()
1596         {
1597                 // Loads the RootTree
1598                 Console.WriteLine ("Loading the monodoc tree...");
1599                 RootTree root = LoadTree ();
1600                 if (root == null)
1601                         return;
1602
1603                 string dir = Path.Combine (root.basedir, "search_index");
1604                 IndexWriter writer;
1605                 //try to create the dir to store the index
1606                 try {
1607                         if (!Directory.Exists (dir)) 
1608                                 Directory.CreateDirectory (dir);
1609
1610                         writer = new IndexWriter(Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1611                 } catch (UnauthorizedAccessException) {
1612                         //try in the .config directory
1613                         try {
1614                                 dir = Path.Combine (SettingsHandler.Path, "search_index");
1615                                 if (!Directory.Exists (dir)) 
1616                                         Directory.CreateDirectory (dir);
1617
1618                                 writer = new IndexWriter(Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
1619                         } catch (UnauthorizedAccessException) {
1620                                 Console.WriteLine ("You don't have permissions to write on " + dir);
1621                                 return;
1622                         }
1623                 }
1624
1625                 //Collect all the documents
1626                 Console.WriteLine ("Collecting and adding documents...");
1627                 foreach (HelpSource hs in root.HelpSources) 
1628                         hs.PopulateSearchableIndex (writer);
1629         
1630                 //Optimize and close
1631                 Console.WriteLine ("Closing...");
1632                 writer.Optimize();
1633                 writer.Close();
1634         }
1635
1636
1637         public ICollection HelpSources { get { return new ArrayList(help_sources); } }
1638
1639         [System.Runtime.InteropServices.DllImport ("libc")]
1640         static extern int chmod (string filename, int mode);
1641 }
1642 }