5 using System.Diagnostics;
6 using System.Collections.Generic;
9 using Lucene.Net.Index;
14 // The HelpSource class keeps track of the archived data, and its
17 public class HelpSource
22 // The unique ID for this HelpSource.
26 // The name of the HelpSource, used by all the file (.tree, .zip, ...) used by it
28 // The full directory path where the HelpSource files are located
31 // The tree of this help source
39 public HelpSource (string base_filename, bool create)
41 this.name = Path.GetFileName (base_filename);
42 this.basePath = Path.GetDirectoryName (base_filename);
43 this.treeFilePath = base_filename + ".tree";
44 this.storage = new MonkeyDoc.Storage.ZipStorage (base_filename + ".zip");
45 this.cache = DocCacheHelper.GetDefaultCache (Name);
47 tree = create ? new Tree (this, string.Empty, string.Empty) : new Tree (this, treeFilePath);
54 tree = new Tree (this, "Blah", "Blah");
70 /* This gives the full path of the source/ directory */
71 public string BaseFilePath {
77 public TraceLevel TraceLevel {
82 public string BaseDir {
94 public RootTree RootTree {
103 public IDocCache Cache {
109 public IDocStorage Storage {
118 // A HelpSource may have a common prefix to its URL, give it here
119 protected virtual string UriPrefix {
126 /// Returns a stream from the packaged help source archive
128 public virtual Stream GetHelpStream (string id)
130 return storage.Retrieve (id);
133 public virtual Stream GetCachedHelpStream (string id)
135 if (string.IsNullOrEmpty (id))
136 throw new ArgumentNullException ("id");
137 if (!cache.CanCache (DocEntity.Text))
138 return GetHelpStream (id);
139 if (!cache.IsCached (id))
140 cache.CacheText (id, GetHelpStream (id));
141 return cache.GetCachedStream (id);
144 public XmlReader GetHelpXml (string id)
146 var url = "monodoc:///" + SourceID + "@" + Uri.EscapeDataString (id) + "@";
147 var stream = cache.IsCached (id) ? cache.GetCachedStream (id) : storage.Retrieve (id);
149 return stream == null ? null : new XmlTextReader (url, stream);
152 public virtual XmlDocument GetHelpXmlWithChanges (string id)
154 XmlDocument doc = new XmlDocument ();
155 if (!storage.SupportRevision) {
156 doc.Load (GetHelpXml (id));
158 var revManager = storage.RevisionManager;
159 doc.Load (revManager.RetrieveLatestRevision (id));
164 public virtual string GetCachedText (string id)
166 if (!cache.CanCache (DocEntity.Text))
168 if (!cache.IsCached (id))
169 cache.CacheText (id, GetText (id));
170 return cache.GetCachedString (id);
173 public virtual string GetText (string id)
175 return new StreamReader (GetHelpStream (id)).ReadToEnd ();
178 // Tells if the result for the provided id is generated dynamically
179 // by the help source
180 public virtual bool IsGeneratedContent (string id)
185 // Tells if the content of the provided id is meant to be returned raw
186 public virtual bool IsRawContent (string id)
191 // Tells if provided id refers to a multi-content-type document if it's case
192 // tells the ids it's formed of
193 public virtual bool IsMultiPart (string id, out IEnumerable<string> parts)
200 /// Saves the tree and the archive
204 tree.Save (treeFilePath);
208 public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
210 throw new NotImplementedException ();
213 public virtual string GetPublicUrl (Node node)
215 return node.GetInternalUrl ();
218 public virtual bool CanHandleUrl (string url)
220 return url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase);
223 public virtual string GetInternalIdForUrl (string url, out Node node)
225 node = MatchNode (url);
226 return node == null ? null : url.Substring (UriPrefix.Length);
229 public virtual Node MatchNode (string url)
233 var matchCache = LRUCache<string, Node>.Default;
234 if ((current = matchCache.Get (url)) != null)
237 current = Tree.RootNode;
238 var strippedUrl = url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase) ? url.Substring (UriPrefix.Length) : url;
239 var searchNode = new Node () { Element = strippedUrl };
242 int index = current.Nodes.BinarySearch (searchNode, NodeElementComparer.Instance);
244 Node n = current.Nodes[index];
245 //Console.WriteLine ("Binarysearch success for {0} which fell on {1}", strippedUrl, n.Element);
246 matchCache.Put (url, n);
250 if (index == current.Nodes.Count) {
251 //Console.WriteLine ("Match fail for {0}", strippedUrl);
252 //Console.WriteLine (current.Nodes.Select (n => n.Element).Aggregate ((e1, e2) => e1 + ", " + e2));
253 return SlowMatchNode (Tree.RootNode, matchCache, strippedUrl);
255 current = current.Nodes [index - 1];
256 //Console.WriteLine ("Binarysearch failed for {0}, next node check is {1}", strippedUrl, current.Element);
262 /* That slow path is mainly here to handle ecmaspec type of url which are composed of hard to sort numbers
263 * because they don't have the same amount of digit. We could use a regex to harmonise the various number
264 * parts but then it would be quite specific. Since in the case of ecmaspec the tree is well-formed enough
265 * the "Slow" match should still be fast enough
267 Node SlowMatchNode (Node current, LRUCache<string, Node> matchCache, string url)
269 //Console.WriteLine ("Entering slow path for {0} starting from {1}", url, current.Element);
270 while (current != null) {
272 foreach (Node n in current.Nodes) {
273 var element = n.Element.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase) ? n.Element.Substring (UriPrefix.Length) : n.Element;
274 if (url == element) {
275 matchCache.Put (url, n);
277 } else if (url.StartsWith (element + ".", StringComparison.OrdinalIgnoreCase) && !n.IsLeaf) {
290 class NodeElementComparer : IComparer<Node>
292 public static NodeElementComparer Instance = new NodeElementComparer ();
294 public int Compare (Node n1, Node n2)
296 return string.Compare (Cleanup (n1), Cleanup (n2), StringComparison.Ordinal);
299 string Cleanup (Node n)
301 var prefix = n.Tree != null && n.Tree.HelpSource != null ? n.Tree.HelpSource.UriPrefix : string.Empty;
302 var element = n.Element.StartsWith (prefix, StringComparison.OrdinalIgnoreCase) ? n.Element.Substring (prefix.Length) : n.Element;
303 if (char.IsDigit (element, 0)) {
304 var count = element.TakeWhile (char.IsDigit).Count ();
305 element = element.PadLeft (Math.Max (0, 3 - count) + element.Length, '0');
307 //Console.WriteLine ("Cleaned up {0} to {1}", n.Element, element);
312 public virtual DocumentType GetDocumentTypeForId (string id, out Dictionary<string, string> extraParams)
315 return DocumentType.PlainText;
318 public virtual Stream GetImage (string url)
324 // Populates the index.
326 public virtual void PopulateIndex (IndexMaker index_maker)
331 // Create different Documents for adding to Lucene search index
332 // The default action is do nothing. Subclasses should add the docs
334 public virtual void PopulateSearchableIndex (IndexWriter writer)