/// <summary> /// This is overridden to allow use of an SQL backed MSDN content ID cache /// </summary> /// <param name="configuration">The component configuration</param> /// <returns>An MSDN resolver instance</returns> protected override MsdnResolver CreateMsdnResolver(XPathNavigator configuration) { MsdnResolver resolver; IDictionary<string, string> cache = null; int localCacheSize; if(BuildComponentCore.Data.ContainsKey(SharedMsdnContentIdCacheId)) cache = BuildComponentCore.Data[SharedMsdnContentIdCacheId] as IDictionary<string, string>; // If the shared cache already exists, return an instance that uses it. It is assumed that all // subsequent instances will use the same cache. if(cache != null) return new MsdnResolver(cache, true); XPathNavigator node = configuration.SelectSingleNode("msdnContentIdCache"); // If a <cache> element is not specified, use the default resolver if(node == null) resolver = base.CreateMsdnResolver(configuration); else { node = configuration.SelectSingleNode("sqlCache"); string connectionString = node.GetAttribute("connectionString", String.Empty); // If a connection string is not defined, use the default resolver if(String.IsNullOrWhiteSpace(connectionString)) resolver = base.CreateMsdnResolver(configuration); else { string cacheSize = node.GetAttribute("msdnLocalCacheSize", String.Empty); if(String.IsNullOrWhiteSpace(cacheSize) || !Int32.TryParse(cacheSize, out localCacheSize)) localCacheSize = 2500; // Load or create the cache database and the resolver. The resolver will dispose of the // dictionary when it is disposed of since it implements IDisposable. resolver = new MsdnResolver(new SqlDictionary<string>(connectionString, "ContentIds", "TargetKey", "ContentId") { LocalCacheSize = localCacheSize }, false); int cacheCount = resolver.MsdnContentIdCache.Count; if(cacheCount == 0) { // Log a diagnostic message since looking up all IDs can significantly slow the build base.WriteMessage(MessageLevel.Diagnostic, "The SQL MSDN content ID cache in '" + connectionString + "' does not exist yet. All IDs will be looked up in this " + "build which will slow it down."); } else base.WriteMessage(MessageLevel.Info, "{0} cached MSDN content ID entries exist", cacheCount); BuildComponentCore.Data[SharedMsdnContentIdCacheId] = resolver.MsdnContentIdCache; } } return resolver; }
//===================================================================== /// <summary> /// This is used to create an MSDN resolver for the reference link to use in looking up MSDN content iDs /// </summary> /// <param name="configuration">The component configuration</param> /// <returns>An MSDN resolver instance</returns> /// <remarks>This can be overridden in derived classes to provide persistent caches with backing stores /// other than the default dictionary serialized to a binary file. It also allows sharing the cache /// across instances by placing it in the <see cref="BuildComponentCore.Data"/> dictionary using the key /// name <c>SharedMsdnContentIdCacheID</c>. /// /// <para>If overridden, the <see cref="UpdateMsdnContentIdCache"/> method should also be overridden to /// persist changes to the cache if needed.</para></remarks> protected virtual MsdnResolver CreateMsdnResolver(XPathNavigator configuration) { MsdnResolver newResolver; IDictionary <string, string> cache = null; if (BuildComponentCore.Data.ContainsKey(SharedMsdnContentIdCacheId)) { cache = BuildComponentCore.Data[SharedMsdnContentIdCacheId] as IDictionary <string, string>; } // If the shared cache already exists, return an instance that uses it. It is assumed that all // subsequent instances will use the same cache. if (cache != null) { return(new MsdnResolver(cache, true)); } // If a <cache> element is not specified, we'll use the standard resolver without a persistent cache. // We will share it across all instances though. XPathNavigator node = configuration.SelectSingleNode("msdnContentIdCache"); if (node == null) { newResolver = new MsdnResolver(); } else { // Keep the filename. If we own it, we'll update the cache file when disposed. msdnIdCacheFile = node.GetAttribute("path", String.Empty); if (String.IsNullOrWhiteSpace(msdnIdCacheFile)) { this.WriteMessage(MessageLevel.Error, "You must specify a path attribute value on the " + "msdnContentIdCache element."); } // Create the folder if it doesn't exist msdnIdCacheFile = Path.GetFullPath(Environment.ExpandEnvironmentVariables(msdnIdCacheFile)); string path = Path.GetDirectoryName(msdnIdCacheFile); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } // Load the cache if it exists if (!File.Exists(msdnIdCacheFile)) { // Logged as a diagnostic message since looking up all IDs can significantly slow the build this.WriteMessage(MessageLevel.Diagnostic, "The MSDN content ID cache '" + msdnIdCacheFile + "' does not exist yet. All IDs will be looked up in this build which will slow it down."); newResolver = new MsdnResolver(); } else { using (FileStream fs = new FileStream(msdnIdCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read)) { BinaryFormatter bf = new BinaryFormatter(); newResolver = new MsdnResolver((IDictionary <string, string>)bf.Deserialize(fs), false); this.WriteMessage(MessageLevel.Info, "Loaded {0} cached MSDN content ID entries", newResolver.MsdnContentIdCache.Count); } } } BuildComponentCore.Data[SharedMsdnContentIdCacheId] = newResolver.MsdnContentIdCache; return(newResolver); }
//===================================================================== /// <inheritdoc /> public override void Initialize(XPathNavigator configuration) { TargetDictionary newTargets; ReferenceLinkType type; string attrValue, id; targets = new TargetTypeDictionary(); resolver = new LinkTextResolver(targets); // Get the shared instances dictionary. Create it if it doesn't exist. if (BuildComponentCore.Data.ContainsKey(SharedReferenceTargetsId)) { sharedTargets = BuildComponentCore.Data[SharedReferenceTargetsId] as Dictionary <string, TargetDictionary>; } if (sharedTargets == null) { BuildComponentCore.Data[SharedReferenceTargetsId] = sharedTargets = new Dictionary <string, TargetDictionary>(); } // base-url is an xpath expression applied against the current document to pick up the save location of the // document. If specified, local links will be made relative to the base-url. string baseUrlValue = configuration.GetAttribute("base-url", String.Empty); if (!String.IsNullOrEmpty(baseUrlValue)) { baseUrl = XPathExpression.Compile(baseUrlValue); } // hrefFormat is a string format that is used to format the value of local href attributes. The // default is "{0}.htm" if not specified. hrefFormat = (string)configuration.Evaluate("string(hrefFormat/@value)"); if (String.IsNullOrWhiteSpace(hrefFormat)) { hrefFormat = "{0}.htm"; } // The container XPath can be replaced; this is useful string containerValue = configuration.GetAttribute("container", String.Empty); if (!String.IsNullOrEmpty(containerValue)) { XmlTargetDictionaryUtilities.ContainerExpression = containerValue; } XPathNodeIterator targetsNodes = configuration.Select("targets"); #if DEBUG this.WriteMessage(MessageLevel.Diagnostic, "Loading reference link target info"); DateTime startLoad = DateTime.Now; #endif foreach (XPathNavigator targetsNode in targetsNodes) { // Get target type attrValue = targetsNode.GetAttribute("type", String.Empty); if (String.IsNullOrEmpty(attrValue)) { this.WriteMessage(MessageLevel.Error, "Each targets element must have a type attribute " + "that specifies which type of links to create"); } if (!Enum.TryParse <ReferenceLinkType>(attrValue, true, out type)) { this.WriteMessage(MessageLevel.Error, "'{0}' is not a supported reference link type", attrValue); } // Check for shared instance by ID. If not there, create it and add it. id = targetsNode.GetAttribute("id", String.Empty); if (!sharedTargets.TryGetValue(id, out newTargets)) { this.WriteMessage(MessageLevel.Info, "Loading {0} reference link type targets", type); newTargets = this.CreateTargetDictionary(targetsNode); sharedTargets[newTargets.DictionaryId] = newTargets; } targets.Add(type, newTargets); } #if DEBUG TimeSpan loadTime = (DateTime.Now - startLoad); this.WriteMessage(MessageLevel.Diagnostic, "Load time: {0} seconds", loadTime.TotalSeconds); // Dump targets for comparison to other versions // targets.DumpTargetDictionary(Path.GetFullPath("TargetDictionary.xml")); // Serialization test // targets.SerializeDictionary(Directory.GetCurrentDirectory()); #endif // Getting the count from a database cache can be expensive so only report it if it will be seen if (this.BuildAssembler.VerbosityLevel == MessageLevel.Info) { this.WriteMessage(MessageLevel.Info, "{0} total reference link targets", targets.Count); } if (targets.NeedsMsdnResolver) { this.WriteMessage(MessageLevel.Info, "Creating MSDN URL resolver"); msdnResolver = this.CreateMsdnResolver(configuration); string localeValue = (string)configuration.Evaluate("string(locale/@value)"); if (msdnResolver != null && !String.IsNullOrWhiteSpace(localeValue)) { msdnResolver.Locale = localeValue; } } linkTarget = (string)configuration.Evaluate("string(linkTarget/@value)"); if (String.IsNullOrWhiteSpace(linkTarget)) { linkTarget = "_blank"; } }
/// <summary> /// This is overridden to allow use of an ESENT backed MSDN content ID cache /// </summary> /// <param name="configuration">The component configuration</param> /// <returns>An MSDN resolver instance</returns> protected override MsdnResolver CreateMsdnResolver(XPathNavigator configuration) { MsdnResolver resolver; IDictionary<string, string> cache = null; int localCacheSize; if(BuildComponentCore.Data.ContainsKey(SharedMsdnContentIdCacheId)) cache = BuildComponentCore.Data[SharedMsdnContentIdCacheId] as IDictionary<string, string>; // If the shared cache already exists, return an instance that uses it. It is assumed that all // subsequent instances will use the same cache. if(cache != null) return new MsdnResolver(cache, true); XPathNavigator node = configuration.SelectSingleNode("msdnContentIdCache"); // If an <msdnContentIdCache> element is not specified, use the default resolver if(node == null) resolver = base.CreateMsdnResolver(configuration); else { string msdnIdCachePath = node.GetAttribute("cachePath", String.Empty); // If a cache path is not defined, use the default resolver if(String.IsNullOrWhiteSpace(msdnIdCachePath)) resolver = base.CreateMsdnResolver(configuration); else { msdnIdCachePath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(msdnIdCachePath)); string cacheSize = node.GetAttribute("localCacheSize", String.Empty); if(String.IsNullOrWhiteSpace(cacheSize) || !Int32.TryParse(cacheSize, out localCacheSize)) localCacheSize = 2500; // Load or create the cache database and the resolver. The resolver will dispose of the // dictionary when it is disposed of since it implements IDisposable. We won't compress the // columns as they're typically not that big and there aren't usually that many entries. // This gives a slight performance increase. resolver = new MsdnResolver(new PersistentDictionary<string, string>(msdnIdCachePath, false) { LocalCacheSize = localCacheSize }, false); // We own the cache and will report statistics when done ownsResolverCache = true; int cacheCount = resolver.MsdnContentIdCache.Count; if(cacheCount == 0) { // Log a diagnostic message since looking up all IDs can significantly slow the build base.WriteMessage(MessageLevel.Diagnostic, "The ESENT MSDN content ID cache in '" + msdnIdCachePath + "' does not exist yet. All IDs will be looked up in this build " + "which will slow it down."); } else base.WriteMessage(MessageLevel.Info, "{0} cached MSDN content ID entries exist", cacheCount); BuildComponentCore.Data[SharedMsdnContentIdCacheId] = resolver.MsdnContentIdCache; } } return resolver; }
//===================================================================== /// <summary> /// This is used to create an MSDN resolver for the reference link to use in looking up MSDN content iDs /// </summary> /// <param name="configuration">The component configuration</param> /// <returns>An MSDN resolver instance</returns> /// <remarks>This can be overridden in derived classes to provide persistent caches with backing stores /// other than the default dictionary serialized to a binary file. It also allows sharing the cache /// across instances by placing it in the <see cref="BuildComponentCore.Data"/> dictionary using the key /// name <c>SharedMsdnContentIdCacheID</c>. /// /// <para>If overridden, the <see cref="UpdateMsdnContentIdCache"/> method should also be overridden to /// persist changes to the cache if needed.</para></remarks> protected virtual MsdnResolver CreateMsdnResolver(XPathNavigator configuration) { MsdnResolver newResolver; IDictionary<string, string> cache = null; if(BuildComponentCore.Data.ContainsKey(SharedMsdnContentIdCacheId)) cache = BuildComponentCore.Data[SharedMsdnContentIdCacheId] as IDictionary<string, string>; // If the shared cache already exists, return an instance that uses it. It is assumed that all // subsequent instances will use the same cache. if(cache != null) return new MsdnResolver(cache, true); // If a <cache> element is not specified, we'll use the standard resolver without a persistent cache. // We will share it across all instances though. XPathNavigator node = configuration.SelectSingleNode("msdnContentIdCache"); if(node == null) newResolver = new MsdnResolver(); else { // Keep the filename. If we own it, we'll update the cache file when disposed. msdnIdCacheFile = node.GetAttribute("path", String.Empty); if(String.IsNullOrWhiteSpace(msdnIdCacheFile)) this.WriteMessage(MessageLevel.Error, "You must specify a path attribute value on the " + "msdnContentIdCache element."); // Create the folder if it doesn't exist msdnIdCacheFile = Path.GetFullPath(Environment.ExpandEnvironmentVariables(msdnIdCacheFile)); string path = Path.GetDirectoryName(msdnIdCacheFile); if(!Directory.Exists(path)) Directory.CreateDirectory(path); // Load the cache if it exists if(!File.Exists(msdnIdCacheFile)) { // Logged as a diagnostic message since looking up all IDs can significantly slow the build this.WriteMessage(MessageLevel.Diagnostic, "The MSDN content ID cache '" + msdnIdCacheFile + "' does not exist yet. All IDs will be looked up in this build which will slow it down."); newResolver = new MsdnResolver(); } else using(FileStream fs = new FileStream(msdnIdCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read)) { BinaryFormatter bf = new BinaryFormatter(); newResolver = new MsdnResolver((IDictionary<string, string>)bf.Deserialize(fs), false); this.WriteMessage(MessageLevel.Info, "Loaded {0} cached MSDN content ID entries", newResolver.MsdnContentIdCache.Count); } } BuildComponentCore.Data[SharedMsdnContentIdCacheId] = newResolver.MsdnContentIdCache; return newResolver; }
//===================================================================== /// <inheritdoc /> public override void Initialize(XPathNavigator configuration) { TargetDictionary newTargets; ReferenceLinkType type; string attrValue, id; targets = new TargetTypeDictionary(); resolver = new LinkTextResolver(targets); // Get the shared instances dictionary. Create it if it doesn't exist. if(BuildComponentCore.Data.ContainsKey(SharedReferenceTargetsId)) sharedTargets = BuildComponentCore.Data[SharedReferenceTargetsId] as Dictionary<string, TargetDictionary>; if(sharedTargets == null) BuildComponentCore.Data[SharedReferenceTargetsId] = sharedTargets = new Dictionary<string, TargetDictionary>(); // base-url is an xpath expression applied against the current document to pick up the save location of the // document. If specified, local links will be made relative to the base-url. string baseUrlValue = configuration.GetAttribute("base-url", String.Empty); if(!String.IsNullOrEmpty(baseUrlValue)) baseUrl = XPathExpression.Compile(baseUrlValue); // hrefFormat is a string format that is used to format the value of local href attributes. The // default is "{0}.htm" if not specified. hrefFormat = (string)configuration.Evaluate("string(hrefFormat/@value)"); if(String.IsNullOrWhiteSpace(hrefFormat)) hrefFormat = "{0}.htm"; // The container XPath can be replaced; this is useful string containerValue = configuration.GetAttribute("container", String.Empty); if(!String.IsNullOrEmpty(containerValue)) XmlTargetDictionaryUtilities.ContainerExpression = containerValue; XPathNodeIterator targetsNodes = configuration.Select("targets"); #if DEBUG this.WriteMessage(MessageLevel.Diagnostic, "Loading reference link target info"); DateTime startLoad = DateTime.Now; #endif foreach(XPathNavigator targetsNode in targetsNodes) { // Get target type attrValue = targetsNode.GetAttribute("type", String.Empty); if(String.IsNullOrEmpty(attrValue)) this.WriteMessage(MessageLevel.Error, "Each targets element must have a type attribute " + "that specifies which type of links to create"); if(!Enum.TryParse<ReferenceLinkType>(attrValue, true, out type)) this.WriteMessage(MessageLevel.Error, "'{0}' is not a supported reference link type", attrValue); // Check for shared instance by ID. If not there, create it and add it. id = targetsNode.GetAttribute("id", String.Empty); if(!sharedTargets.TryGetValue(id, out newTargets)) { this.WriteMessage(MessageLevel.Info, "Loading {0} reference link type targets", type); newTargets = this.CreateTargetDictionary(targetsNode); sharedTargets[newTargets.DictionaryId] = newTargets; } targets.Add(type, newTargets); } #if DEBUG TimeSpan loadTime = (DateTime.Now - startLoad); this.WriteMessage(MessageLevel.Diagnostic, "Load time: {0} seconds", loadTime.TotalSeconds); // Dump targets for comparison to other versions // targets.DumpTargetDictionary(Path.GetFullPath("TargetDictionary.xml")); // Serialization test // targets.SerializeDictionary(Directory.GetCurrentDirectory()); #endif // Getting the count from a database cache can be expensive so only report it if it will be seen if(this.BuildAssembler.VerbosityLevel == MessageLevel.Info) this.WriteMessage(MessageLevel.Info, "{0} total reference link targets", targets.Count); if(targets.NeedsMsdnResolver) { this.WriteMessage(MessageLevel.Info, "Creating MSDN URL resolver"); msdnResolver = this.CreateMsdnResolver(configuration); string localeValue = (string)configuration.Evaluate("string(locale/@value)"); if(msdnResolver != null && !String.IsNullOrWhiteSpace(localeValue)) msdnResolver.Locale = localeValue; } linkTarget = (string)configuration.Evaluate("string(linkTarget/@value)"); if(String.IsNullOrWhiteSpace(linkTarget)) linkTarget = "_blank"; }