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
36 IDocCache cache = new MonkeyDoc.Caches.FileCache (Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData), "monkeydoc", "cache"));
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");
46 tree = create ? new Tree (this, string.Empty, string.Empty) : new Tree (this, treeFilePath);
53 tree = new Tree (this, "Blah", "Blah");
69 /* This gives the full path of the source/ directory */
70 public string BaseFilePath {
76 public TraceLevel TraceLevel {
81 public string BaseDir {
93 public RootTree RootTree {
102 public IDocCache Cache {
108 public IDocStorage Storage {
114 // A HelpSource may have a common prefix to its URL, give it here
115 protected virtual string UriPrefix {
122 /// Returns a stream from the packaged help source archive
124 public virtual Stream GetHelpStream (string id)
126 return storage.Retrieve (id);
129 public virtual Stream GetCachedHelpStream (string id)
131 if (string.IsNullOrEmpty (id))
132 throw new ArgumentNullException ("id");
133 if (!cache.CanCache (DocEntity.Text))
134 return GetHelpStream (id);
135 if (!cache.IsCached (id))
136 cache.CacheText (id, GetHelpStream (id));
137 return cache.GetCachedStream (id);
140 public XmlReader GetHelpXml (string id)
142 var url = "monodoc:///" + SourceID + "@" + Uri.EscapeDataString (id) + "@";
143 var stream = cache.IsCached (id) ? cache.GetCachedStream (id) : storage.Retrieve (id);
145 return stream == null ? null : new XmlTextReader (url, stream);
148 public virtual XmlDocument GetHelpXmlWithChanges (string id)
150 XmlDocument doc = new XmlDocument ();
151 if (!storage.SupportRevision) {
152 doc.Load (GetHelpXml (id));
154 var revManager = storage.RevisionManager;
155 doc.Load (revManager.RetrieveLatestRevision (id));
160 public virtual string GetCachedText (string id)
162 if (!cache.CanCache (DocEntity.Text))
164 if (!cache.IsCached (id))
165 cache.CacheText (id, GetText (id));
166 return cache.GetCachedString (id);
169 public virtual string GetText (string id)
171 return new StreamReader (GetHelpStream (id)).ReadToEnd ();
174 // Tells if the result for the provided id is generated dynamically
175 // by the help source
176 public virtual bool IsGeneratedContent (string id)
181 // Tells if the content of the provided id is meant to be returned raw
182 public virtual bool IsRawContent (string id)
187 // Tells if provided id refers to a multi-content-type document if it's case
188 // tells the ids it's formed of
189 public virtual bool IsMultiPart (string id, out IEnumerable<string> parts)
196 /// Saves the tree and the archive
200 tree.Save (treeFilePath);
204 public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
206 throw new NotImplementedException ();
209 public virtual string GetPublicUrl (Node node)
211 return node.GetInternalUrl ();
214 public virtual bool CanHandleUrl (string url)
216 return url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase);
219 public virtual string GetInternalIdForUrl (string url, out Node node)
221 node = MatchNode (url);
222 return node == null ? null : url.Substring (UriPrefix.Length);
225 public virtual Node MatchNode (string url)
229 var matchCache = LRUCache<string, Node>.Default;
230 if ((current = matchCache.Get (url)) != null)
233 current = Tree.RootNode;
234 var strippedUrl = url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase) ? url.Substring (UriPrefix.Length) : url;
235 var searchNode = new Node () { Element = strippedUrl };
238 int index = current.Nodes.BinarySearch (searchNode, NodeElementComparer.Instance);
240 Node n = current.Nodes[index];
241 //Console.WriteLine ("Binarysearch success for {0} which fell on {1}", strippedUrl, n.Element);
242 matchCache.Put (url, n);
246 if (index == current.Nodes.Count) {
247 //Console.WriteLine ("Match fail for {0}", strippedUrl);
248 //Console.WriteLine (current.Nodes.Select (n => n.Element).Aggregate ((e1, e2) => e1 + ", " + e2));
249 return SlowMatchNode (Tree.RootNode, matchCache, strippedUrl);
251 current = current.Nodes [index - 1];
252 //Console.WriteLine ("Binarysearch failed for {0}, next node check is {1}", strippedUrl, current.Element);
258 /* That slow path is mainly here to handle ecmaspec type of url which are composed of hard to sort numbers
259 * because they don't have the same amount of digit. We could use a regex to harmonise the various number
260 * parts but then it would be quite specific. Since in the case of ecmaspec the tree is well-formed enough
261 * the "Slow" match should still be fast enough
263 Node SlowMatchNode (Node current, LRUCache<string, Node> matchCache, string url)
265 //Console.WriteLine ("Entering slow path for {0} starting from {1}", url, current.Element);
266 while (current != null) {
268 foreach (Node n in current.Nodes) {
269 var element = n.Element.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase) ? n.Element.Substring (UriPrefix.Length) : n.Element;
270 if (url == element) {
271 matchCache.Put (url, n);
273 } else if (url.StartsWith (element + ".", StringComparison.OrdinalIgnoreCase) && !n.IsLeaf) {
286 class NodeElementComparer : IComparer<Node>
288 public static NodeElementComparer Instance = new NodeElementComparer ();
290 public int Compare (Node n1, Node n2)
292 return string.Compare (Cleanup (n1), Cleanup (n2), StringComparison.Ordinal);
295 string Cleanup (Node n)
297 var prefix = n.Tree != null && n.Tree.HelpSource != null ? n.Tree.HelpSource.UriPrefix : string.Empty;
298 var element = n.Element.StartsWith (prefix, StringComparison.OrdinalIgnoreCase) ? n.Element.Substring (prefix.Length) : n.Element;
299 if (char.IsDigit (element, 0)) {
300 var count = element.TakeWhile (char.IsDigit).Count ();
301 element = element.PadLeft (Math.Max (0, 3 - count) + element.Length, '0');
303 //Console.WriteLine ("Cleaned up {0} to {1}", n.Element, element);
308 public virtual DocumentType GetDocumentTypeForId (string id, out Dictionary<string, string> extraParams)
311 return DocumentType.PlainText;
314 public virtual Stream GetImage (string url)
320 // Populates the index.
322 public virtual void PopulateIndex (IndexMaker index_maker)
327 // Create different Documents for adding to Lucene search index
328 // The default action is do nothing. Subclasses should add the docs
330 public virtual void PopulateSearchableIndex (IndexWriter writer)