1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Collections.Specialized; 5 using System.Configuration; 6 using System.IO; 7 using System.Linq; 8 using System.Reflection; 9 using System.Runtime.InteropServices; 10 using System.Xml; 11 12 using Monodoc.Providers; 13 using Lucene.Net.Analysis.Standard; 14 using Lucene.Net.Index; 15 16 namespace Monodoc 17 { 18 public 19 #if LEGACY_MODE 20 partial 21 #endif 22 class RootTree : Tree 23 { 24 public const int MonodocVersion = 2; 25 const string RootNamespace = "root:/"; 26 string basedir; 27 static List<string> uncompiledHelpSourcePaths = new List<string>(); 28 HashSet<string> loadedSourceFiles = new HashSet<string>(); 29 List<HelpSource> helpSources = new List<HelpSource>(); 30 Dictionary<string, Node> nameToNode = new Dictionary<string, Node>(); 31 Dictionary<string, HelpSource> nameToHelpSource = new Dictionary<string, HelpSource>(); 32 33 public IList<HelpSource> HelpSources { 34 get { 35 return this.helpSources.AsReadOnly(); 36 } 37 } 38 39 public DateTime LastHelpSourceTime { 40 get; 41 set; 42 } 43 44 static bool IsUnix { 45 get { 46 int platform = (int)Environment.OSVersion.Platform; 47 return platform == 4 || platform == 128 || platform == 6; 48 } 49 } 50 RootTree()51 RootTree () : base (null, "Mono Documentation", "root:") 52 { 53 base.RootNode.EnsureNodes(); 54 this.LastHelpSourceTime = DateTime.Now; 55 } 56 AddUncompiledSource(string path)57 public static void AddUncompiledSource (string path) 58 { 59 uncompiledHelpSourcePaths.Add (path); 60 } 61 LoadTree()62 public static RootTree LoadTree () 63 { 64 return RootTree.LoadTree (RootTree.ProbeBaseDirectories ()); 65 } 66 ProbeBaseDirectories()67 static string ProbeBaseDirectories () 68 { 69 string result = "."; 70 try { 71 result = Config.Get ("docPath") ?? "."; 72 } catch {} 73 74 return result; 75 } 76 LoadTree(string basedir, bool includeExternal = true)77 public static RootTree LoadTree (string basedir, bool includeExternal = true) 78 { 79 if (string.IsNullOrEmpty (basedir)) 80 throw new ArgumentNullException ("basedir"); 81 if (!Directory.Exists (basedir)) 82 throw new ArgumentException ("basedir", string.Format ("Base documentation directory at '{0}' doesn't exist", basedir)); 83 84 XmlDocument xmlDocument = new XmlDocument (); 85 string filename = Path.Combine (basedir, "monodoc.xml"); 86 xmlDocument.Load (filename); 87 IEnumerable<string> sourceFiles = Directory.EnumerateFiles (Path.Combine (basedir, "sources"), "*.source"); 88 if (includeExternal) 89 sourceFiles = sourceFiles.Concat (RootTree.ProbeExternalDirectorySources ()); 90 return RootTree.LoadTree (basedir, xmlDocument, sourceFiles); 91 } 92 ProbeExternalDirectorySources()93 static IEnumerable<string> ProbeExternalDirectorySources () 94 { 95 IEnumerable<string> enumerable = Enumerable.Empty<string> (); 96 try { 97 string path = Config.Get ("docExternalPath"); 98 enumerable = enumerable.Concat (System.IO.Directory.EnumerateFiles (path, "*.source")); 99 } 100 catch {} 101 102 if (Directory.Exists ("/Library/Frameworks/Mono.framework/External/monodoc")) 103 enumerable = enumerable.Concat (Directory.EnumerateFiles ("/Library/Frameworks/Mono.framework/External/monodoc", "*.source")); 104 105 var windowsPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "monodoc"); 106 if (Directory.Exists (windowsPath)) 107 enumerable = enumerable.Concat (Directory.EnumerateFiles (windowsPath, "*.source")); 108 109 return enumerable; 110 } 111 LoadTree(string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)112 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles) 113 { 114 if (docTree == null) { 115 docTree = new XmlDocument (); 116 using (Stream manifestResourceStream = typeof (RootTree).Assembly.GetManifestResourceStream ("monodoc.xml")) { 117 docTree.Load (manifestResourceStream); 118 } 119 } 120 121 sourceFiles = (sourceFiles ?? new string[0]); 122 RootTree rootTree = new RootTree (); 123 rootTree.basedir = indexDir; 124 XmlNodeList xml_node_list = docTree.SelectNodes ("/node/node"); 125 rootTree.nameToNode["root"] = rootTree.RootNode; 126 rootTree.nameToNode["libraries"] = rootTree.RootNode; 127 rootTree.Populate (rootTree.RootNode, xml_node_list); 128 129 if (rootTree.LookupEntryPoint ("various") == null) { 130 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!"); 131 Node rootNode = rootTree.RootNode; 132 } 133 134 foreach (string current in sourceFiles) 135 rootTree.AddSourceFile (current); 136 137 foreach (string path in uncompiledHelpSourcePaths) { 138 var hs = new Providers.EcmaUncompiledHelpSource (path); 139 hs.RootTree = rootTree; 140 rootTree.helpSources.Add (hs); 141 string epath = "extra-help-source-" + hs.Name; 142 Node hsn = rootTree.RootNode.CreateNode (hs.Name, RootNamespace + epath); 143 rootTree.nameToHelpSource [epath] = hs; 144 hsn.EnsureNodes (); 145 foreach (Node n in hs.Tree.RootNode.ChildNodes) 146 hsn.AddNode (n); 147 } 148 149 RootTree.PurgeNode (rootTree.RootNode); 150 rootTree.RootNode.Sort (); 151 return rootTree; 152 } 153 AddSource(string sourcesDir)154 public void AddSource (string sourcesDir) 155 { 156 IEnumerable<string> enumerable = Directory.EnumerateFiles (sourcesDir, "*.source"); 157 foreach (string current in enumerable) 158 if (!this.AddSourceFile (current)) 159 Console.Error.WriteLine ("Error: Could not load source file {0}", current); 160 } 161 AddSourceFile(string sourceFile)162 public bool AddSourceFile (string sourceFile) 163 { 164 if (this.loadedSourceFiles.Contains (sourceFile)) 165 return false; 166 167 Node node = this.LookupEntryPoint ("various") ?? base.RootNode; 168 XmlDocument xmlDocument = new XmlDocument (); 169 try { 170 xmlDocument.Load (sourceFile); 171 } catch { 172 bool result = false; 173 return result; 174 } 175 176 XmlNodeList extra_nodes = xmlDocument.SelectNodes ("/monodoc/node"); 177 if (extra_nodes.Count > 0) 178 this.Populate (node, extra_nodes); 179 180 XmlNodeList sources = xmlDocument.SelectNodes ("/monodoc/source"); 181 if (sources == null) { 182 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile); 183 return false; 184 } 185 186 loadedSourceFiles.Add (sourceFile); 187 foreach (XmlNode xmlNode in sources) { 188 XmlAttribute a = xmlNode.Attributes["provider"]; 189 if (a == null) { 190 Console.Error.WriteLine ("Error: no provider in <source>"); 191 continue; 192 } 193 string provider = a.InnerText; 194 a = xmlNode.Attributes["basefile"]; 195 if (a == null) { 196 Console.Error.WriteLine ("Error: no basefile in <source>"); 197 continue; 198 } 199 string basefile = a.InnerText; 200 a = xmlNode.Attributes["path"]; 201 if (a == null) { 202 Console.Error.WriteLine ("Error: no path in <source>"); 203 continue; 204 } 205 string path = a.InnerText; 206 string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile); 207 HelpSource helpSource = RootTree.GetHelpSource (provider, basefilepath); 208 if (helpSource != null) { 209 helpSource.RootTree = this; 210 this.helpSources.Add (helpSource); 211 this.nameToHelpSource[path] = helpSource; 212 Node node2 = this.LookupEntryPoint (path); 213 if (node2 == null) { 214 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path); 215 node2 = node; 216 } 217 foreach (Node current in helpSource.Tree.RootNode.ChildNodes) { 218 node2.AddNode (current); 219 } 220 node2.Sort (); 221 } 222 } 223 return true; 224 } 225 PurgeNode(Node node)226 static bool PurgeNode (Node node) 227 { 228 bool result = false; 229 if (!node.Documented) 230 { 231 List<Node> list = new List<Node> (); 232 foreach (Node current in node.ChildNodes) 233 { 234 bool flag = RootTree.PurgeNode (current); 235 if (flag) 236 { 237 list.Add (current); 238 } 239 } 240 result = (node.ChildNodes.Count == list.Count); 241 foreach (Node current2 in list) 242 { 243 node.DeleteNode (current2); 244 } 245 } 246 return result; 247 } 248 GetSupportedFormats()249 public static string[] GetSupportedFormats () 250 { 251 return new string[] 252 { 253 "ecma", 254 "ecmaspec", 255 "error", 256 "man", 257 "xhtml" 258 }; 259 } 260 GetHelpSource(string provider, string basefilepath)261 public static HelpSource GetHelpSource (string provider, string basefilepath) 262 { 263 HelpSource result; 264 try { 265 switch (provider) { 266 case "xhtml": 267 case "hb": 268 result = new XhtmlHelpSource (basefilepath, false); 269 break; 270 case "man": 271 result = new ManHelpSource (basefilepath, false); 272 break; 273 case "error": 274 result = new ErrorHelpSource (basefilepath, false); 275 break; 276 case "ecmaspec": 277 result = new EcmaSpecHelpSource (basefilepath, false); 278 break; 279 case "ecma": 280 result = new EcmaHelpSource (basefilepath, false); 281 break; 282 default: 283 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider); 284 result = null; 285 break; 286 } 287 } catch (FileNotFoundException) { 288 Console.Error.WriteLine ("Error: did not find one of the files in sources/" + basefilepath); 289 result = null; 290 } 291 return result; 292 } 293 GetProvider(string provider, params string[] basefilepaths)294 public static Provider GetProvider (string provider, params string[] basefilepaths) 295 { 296 switch (provider) { 297 case "ecma": 298 return new EcmaProvider (basefilepaths[0]); 299 case "ecmaspec": 300 return new EcmaSpecProvider (basefilepaths[0]); 301 case "error": 302 return new ErrorProvider (basefilepaths[0]); 303 case "man": 304 return new ManProvider (basefilepaths); 305 case "xhml": 306 case "hb": 307 return new XhtmlProvider (basefilepaths[0]); 308 } 309 310 throw new NotSupportedException (provider); 311 } 312 Populate(Node parent, XmlNodeList xml_node_list)313 void Populate (Node parent, XmlNodeList xml_node_list) 314 { 315 foreach (XmlNode xmlNode in xml_node_list) { 316 XmlAttribute e = xmlNode.Attributes["parent"]; 317 Node parent2 = null; 318 if (e != null && this.nameToNode.TryGetValue (e.InnerText, out parent2)) { 319 xmlNode.Attributes.Remove (e); 320 Populate (parent2, xmlNode.SelectNodes (".")); 321 continue; 322 } 323 e = xmlNode.Attributes["label"]; 324 if (e == null) { 325 Console.Error.WriteLine ("`label' attribute missing in <node>"); 326 continue; 327 } 328 string label = e.InnerText; 329 e = xmlNode.Attributes["name"]; 330 if (e == null) { 331 Console.Error.WriteLine ("`name' attribute missing in <node>"); 332 continue; 333 } 334 string name = e.InnerText; 335 Node orCreateNode = parent.GetOrCreateNode (label, RootNamespace + name); 336 orCreateNode.EnsureNodes (); 337 this.nameToNode[name] = orCreateNode; 338 XmlNodeList xmlNodeList = xmlNode.SelectNodes ("./node"); 339 if (xmlNodeList != null) { 340 this.Populate (orCreateNode, xmlNodeList); 341 } 342 } 343 } 344 LookupEntryPoint(string name)345 public Node LookupEntryPoint (string name) 346 { 347 Node result = null; 348 if (!this.nameToNode.TryGetValue (name, out result)) 349 result = null; 350 return result; 351 } 352 RenderUrl(string url, IDocGenerator<TOutput> generator, HelpSource hintSource = null)353 public TOutput RenderUrl<TOutput> (string url, IDocGenerator<TOutput> generator, HelpSource hintSource = null) 354 { 355 Node dummy; 356 return RenderUrl<TOutput> (url, generator, out dummy, hintSource); 357 } 358 RenderUrl(string url, IDocGenerator<TOutput> generator, out Node node, HelpSource hintSource = null)359 public TOutput RenderUrl<TOutput> (string url, IDocGenerator<TOutput> generator, out Node node, HelpSource hintSource = null) 360 { 361 node = null; 362 string internalId = null; 363 Dictionary<string, string> context = null; 364 HelpSource hs = GetHelpSourceAndIdForUrl (url, hintSource, out internalId, out context, out node); 365 return generator.Generate (hs, internalId, context); 366 } 367 GetHelpSourceAndIdForUrl(string url, out string internalId, out Dictionary<string, string> context)368 public HelpSource GetHelpSourceAndIdForUrl (string url, out string internalId, out Dictionary<string, string> context) 369 { 370 Node dummy; 371 return GetHelpSourceAndIdForUrl (url, out internalId, out context, out dummy); 372 } 373 GetHelpSourceAndIdForUrl(string url, out string internalId, out Dictionary<string, string> context, out Node node)374 public HelpSource GetHelpSourceAndIdForUrl (string url, out string internalId, out Dictionary<string, string> context, out Node node) 375 { 376 return GetHelpSourceAndIdForUrl (url, null, out internalId, out context, out node); 377 } 378 GetHelpSourceAndIdForUrl(string url, HelpSource hintSource, out string internalId, out Dictionary<string, string> context, out Node node)379 public HelpSource GetHelpSourceAndIdForUrl (string url, HelpSource hintSource, out string internalId, out Dictionary<string, string> context, out Node node) 380 { 381 node = null; 382 internalId = null; 383 context = null; 384 385 if (url == "root:") { 386 context = new Dictionary<string, string> { {"specialpage", "master-root"} }; 387 internalId = url; 388 node = null; 389 // We return the first help source available since the generator will simply fetch this RootTree instance through it 390 return helpSources.FirstOrDefault (); 391 } 392 if (url.StartsWith (RootNamespace, StringComparison.OrdinalIgnoreCase)) { 393 context = new Dictionary<string, string> { {"specialpage", "root"} }; 394 return GetHelpSourceAndIdFromName (url.Substring (RootNamespace.Length), out internalId, out node); 395 } 396 397 HelpSource helpSource = hintSource; 398 if (helpSource == null || string.IsNullOrEmpty (internalId = helpSource.GetInternalIdForUrl (url, out node, out context))) { 399 helpSource = null; 400 foreach (var hs in helpSources.Where (h => h.CanHandleUrl (url))) { 401 if (!string.IsNullOrEmpty (internalId = hs.GetInternalIdForUrl (url, out node, out context))) { 402 helpSource = hs; 403 break; 404 } 405 } 406 } 407 408 return helpSource; 409 } 410 GetHelpSourceAndIdFromName(string name, out string internalId, out Node node)411 public HelpSource GetHelpSourceAndIdFromName (string name, out string internalId, out Node node) 412 { 413 internalId = "root:"; 414 node = LookupEntryPoint (name); 415 416 return node == null ? null : node.ChildNodes.Select (n => n.Tree.HelpSource).FirstOrDefault (hs => hs != null); 417 } 418 GetHelpSourceFromId(int id)419 public HelpSource GetHelpSourceFromId (int id) 420 { 421 return (id < 0 || id >= this.helpSources.Count) ? null : this.helpSources[id]; 422 } 423 GetImage(string url)424 public Stream GetImage (string url) 425 { 426 if (url.StartsWith ("source-id:", StringComparison.OrdinalIgnoreCase)) { 427 string text = url.Substring (10); 428 int num = text.IndexOf (":"); 429 string text2 = text.Substring (0, num); 430 int id = 0; 431 if (!int.TryParse (text2, out id)) { 432 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, text2); 433 return null; 434 } 435 HelpSource helpSourceFromId = this.GetHelpSourceFromId (id); 436 return helpSourceFromId.GetImage (text.Substring (num + 1)); 437 } 438 Assembly assembly = Assembly.GetAssembly (typeof (RootTree)); 439 return assembly.GetManifestResourceStream (url); 440 } 441 GetIndex()442 public IndexReader GetIndex () 443 { 444 var paths = GetIndexesPathPrefixes ().Select (bp => Path.Combine (bp, "monodoc.index")); 445 var p = paths.FirstOrDefault (File.Exists); 446 return p == null ? (IndexReader)null : IndexReader.Load (p); 447 } 448 MakeIndex()449 public static void MakeIndex () 450 { 451 RootTree rootTree = RootTree.LoadTree (); 452 rootTree.GenerateIndex (); 453 } 454 GenerateIndex()455 public bool GenerateIndex () 456 { 457 IndexMaker indexMaker = new IndexMaker (); 458 foreach (HelpSource current in this.helpSources) 459 current.PopulateIndex (indexMaker); 460 461 var paths = GetIndexesPathPrefixes ().Select (bp => Path.Combine (bp, "monodoc.index")); 462 bool successful = false; 463 464 foreach (var path in paths) { 465 try { 466 indexMaker.Save (path); 467 successful = true; 468 if (RootTree.IsUnix) 469 RootTree.chmod (path, 420); 470 } catch (UnauthorizedAccessException) { 471 } 472 } 473 if (!successful) { 474 Console.WriteLine ("You don't have permissions to write on any of [" + string.Join (", ", paths) + "]"); 475 return false; 476 } 477 478 Console.WriteLine ("Documentation index updated"); 479 return true; 480 } 481 GetSearchIndex()482 public SearchableIndex GetSearchIndex () 483 { 484 var paths = GetIndexesPathPrefixes ().Select (bp => Path.Combine (bp, "search_index")); 485 var p = paths.FirstOrDefault (Directory.Exists); 486 return p == null ? (SearchableIndex)null : SearchableIndex.Load (p); 487 } 488 MakeSearchIndex()489 public static void MakeSearchIndex () 490 { 491 RootTree rootTree = RootTree.LoadTree (); 492 rootTree.GenerateSearchIndex (); 493 } 494 GenerateSearchIndex()495 public bool GenerateSearchIndex () 496 { 497 Console.WriteLine ("Loading the monodoc tree..."); 498 IndexWriter indexWriter = null; 499 var analyzer = new StandardAnalyzer (Lucene.Net.Util.Version.LUCENE_CURRENT); 500 var paths = GetIndexesPathPrefixes ().Select (bp => Path.Combine (bp, "search_index")); 501 bool successful = false; 502 503 foreach (var path in paths) { 504 try { 505 if (!Directory.Exists (path)) 506 Directory.CreateDirectory (path); 507 var directory = Lucene.Net.Store.FSDirectory.Open (path); 508 indexWriter = new IndexWriter (directory, analyzer, true, IndexWriter.MaxFieldLength.LIMITED); 509 successful = true; 510 } catch (UnauthorizedAccessException) {} 511 } 512 if (!successful) { 513 Console.WriteLine ("You don't have permissions to write on any of [" + string.Join (", ", paths) + "]"); 514 return false; 515 } 516 Console.WriteLine ("Collecting and adding documents..."); 517 foreach (HelpSource current in this.helpSources) { 518 current.PopulateSearchableIndex (indexWriter); 519 } 520 Console.WriteLine ("Closing..."); 521 indexWriter.Optimize (); 522 indexWriter.Close (); 523 return true; 524 } 525 526 [DllImport ("libc")] chmod(string filename, int mode)527 static extern int chmod (string filename, int mode); 528 GetIndexesPathPrefixes()529 IEnumerable<string> GetIndexesPathPrefixes () 530 { 531 yield return basedir; 532 yield return Config.Get ("docPath"); 533 var indexDirectory = Config.Get ("monodocIndexDirectory"); 534 if (!string.IsNullOrEmpty (indexDirectory)) 535 yield return indexDirectory; 536 yield return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData), "monodoc"); 537 } 538 539 [Obsolete] GetTitle(string url)540 public string GetTitle (string url) 541 { 542 return "Mono Documentation"; 543 } 544 } 545 } 546