Fixed memory leak (and refactored code).

This commit is contained in:
Dario Solera 2010-05-07 07:08:34 +00:00
parent ab80a18c18
commit 5bd3f0e0b4

View file

@ -17,151 +17,143 @@ namespace ScrewTurn.Wiki.Plugins.PluginPack {
/// <summary> /// <summary>
/// Implements a formatter that display tickets from Unfuddle. /// Implements a formatter that display tickets from Unfuddle.
/// </summary> /// </summary>
public class UnfuddleTickets : IFormatterProviderV30 { public class UnfuddleTickets : IFormatterProviderV30 {
private const string ConfigHelpHtmlValue = "Config consists of three lines:<br/><i>&lt;Url&gt;</i> - The base url to the Unfuddle API (i.e. http://account_name.unfuddle.com/api/v1/projects/project_ID)<br/><i>&lt;Username&gt;</i> - The username to the unfuddle account to use for authentication<br/><i>&lt;Password&gt;</i> - The password to the unfuddle account to use for authentication<br/>"; private const string ConfigHelpHtmlValue = "Config consists of three lines:<br/><i>&lt;Url&gt;</i> - The base url to the Unfuddle API (i.e. http://account_name.unfuddle.com/api/v1/projects/project_ID)<br/><i>&lt;Username&gt;</i> - The username to the unfuddle account to use for authentication<br/><i>&lt;Password&gt;</i> - The password to the unfuddle account to use for authentication<br/>";
private const string LoadErrorMessage = "Unable to load ticket report at this time."; private const string LoadErrorMessage = "Unable to load ticket report at this time.";
private static readonly Regex UnfuddleRegex = new Regex(@"{unfuddle}", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex UnfuddleRegex = new Regex(@"{unfuddle}", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly ComponentInformation Info = new ComponentInformation("Unfuddle Tickets Plugin", "Threeplicate Srl", "3.0.1.472", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/UnfuddleTickets2.txt"); private static readonly ComponentInformation Info = new ComponentInformation("Unfuddle Tickets Plugin", "Threeplicate Srl", "3.0.2.538", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/UnfuddleTickets2.txt");
private string _config; private string _config;
private IHostV30 _host; private IHostV30 _host;
private string _baseUrl; private string _baseUrl;
private string _username; private string _username;
private string _password; private string _password;
#region IFormatterProviderV30 Members private XslCompiledTransform _xslTransform = null;
/// <summary> /// <summary>
/// Specifies whether or not to execute Phase 1. /// Initializes the Storage Provider.
/// </summary> /// </summary>
public bool PerformPhase1 { /// <param name="host">The Host of the Component.</param>
get { /// <param name="config">The Configuration data, if any.</param>
return false; /// <remarks>If the configuration string is not valid, the methoud should throw a <see cref="InvalidConfigurationException"/>.</remarks>
} public void Init(IHostV30 host, string config) {
} _host = host;
_config = config ?? string.Empty;
var configEntries = _config.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
/// <summary> if(configEntries.Length != 3) throw new InvalidConfigurationException("Configuration is missing required parameters");
/// Specifies whether or not to execute Phase 2.
/// </summary>
public bool PerformPhase2 {
get {
return false;
}
}
/// <summary> _baseUrl = configEntries[0];
/// Specifies whether or not to execute Phase 3.
/// </summary>
public bool PerformPhase3 {
get {
return true;
}
}
/// <summary> if(_baseUrl.EndsWith("/"))
/// Gets the execution priority of the provider (0 lowest, 100 highest). _baseUrl = _baseUrl.Substring(0, _baseUrl.Length - 1);
/// </summary>
public int ExecutionPriority {
get {
return 50;
}
}
/// <summary> _username = configEntries[1];
/// Gets the Information about the Provider. _password = configEntries[2];
/// </summary>
public ComponentInformation Information {
get {
return Info;
}
}
/// <summary> var settings = new XsltSettings {
/// Performs a Formatting phase. EnableScript = true,
/// </summary> EnableDocumentFunction = true
/// <param name="raw">The raw content to Format.</param> };
/// <param name="context">The Context information.</param> _xslTransform = new XslCompiledTransform(true);
/// <param name="phase">The Phase.</param> using(var reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("ScrewTurn.Wiki.Plugins.PluginPack.Resources.UnfuddleTickets.xsl"))) {
/// <returns>The Formatted content.</returns> _xslTransform.Load(reader, settings, new XmlUrlResolver());
public string Format(string raw, ContextInformation context, FormattingPhase phase) { }
var buffer = new StringBuilder(raw); }
/// <summary>
/// Performs a Formatting phase.
/// </summary>
/// <param name="raw">The raw content to Format.</param>
/// <param name="context">The Context information.</param>
/// <param name="phase">The Phase.</param>
/// <returns>The Formatted content.</returns>
public string Format(string raw, ContextInformation context, FormattingPhase phase) {
var buffer = new StringBuilder(raw);
var block = FindAndRemoveFirstOccurrence(buffer); var block = FindAndRemoveFirstOccurrence(buffer);
if(block.Key != -1) { if(block.Key != -1) {
string unfuddleTickets = null; string unfuddleTickets = null;
if(HttpContext.Current != null) if(HttpContext.Current != null)
unfuddleTickets = HttpContext.Current.Cache["UnfuddleTicketsStore"] as string; unfuddleTickets = HttpContext.Current.Cache["UnfuddleTicketsStore"] as string;
if(string.IsNullOrEmpty(unfuddleTickets)) if(string.IsNullOrEmpty(unfuddleTickets))
unfuddleTickets = LoadUnfuddleTicketsFromWeb(); unfuddleTickets = LoadUnfuddleTicketsFromWeb();
if(string.IsNullOrEmpty(unfuddleTickets)) if(string.IsNullOrEmpty(unfuddleTickets))
unfuddleTickets = LoadErrorMessage; unfuddleTickets = LoadErrorMessage;
do { do {
buffer.Insert(block.Key, unfuddleTickets); buffer.Insert(block.Key, unfuddleTickets);
block = FindAndRemoveFirstOccurrence(buffer); block = FindAndRemoveFirstOccurrence(buffer);
} while(block.Key != -1); } while(block.Key != -1);
} }
return buffer.ToString(); return buffer.ToString();
} }
/// <summary> /// <summary>
/// Prepares the title of an item for display (always during phase 3). /// Prepares the title of an item for display (always during phase 3).
/// </summary> /// </summary>
/// <param name="title">The input title.</param> /// <param name="title">The input title.</param>
/// <param name="context">The context information.</param> /// <param name="context">The context information.</param>
/// <returns>The prepared title (no markup allowed).</returns> /// <returns>The prepared title (no markup allowed).</returns>
public string PrepareTitle(string title, ContextInformation context) { public string PrepareTitle(string title, ContextInformation context) {
return title; return title;
} }
/// <summary> /// <summary>
/// Initializes the Storage Provider. /// Method invoked on shutdown.
/// </summary> /// </summary>
/// <param name="host">The Host of the Component.</param> /// <remarks>This method might not be invoked in some cases.</remarks>
/// <param name="config">The Configuration data, if any.</param> public void Shutdown() {
/// <remarks>If the configuration string is not valid, the methoud should throw a <see cref="InvalidConfigurationException"/>.</remarks> // Nothing to do
public void Init(IHostV30 host, string config) { }
_host = host;
_config = config ?? string.Empty;
var configEntries = _config.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
if(configEntries.Length != 3) /// <summary>
throw new InvalidConfigurationException("Configuration missing required parameters."); /// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return ConfigHelpHtmlValue; }
}
_baseUrl = configEntries[0]; /// <summary>
/// Specifies whether or not to execute Phase 1.
/// </summary>
public bool PerformPhase1 {
get { return false; }
}
if(_baseUrl.EndsWith("/")) /// <summary>
_baseUrl = _baseUrl.Substring(0, _baseUrl.Length - 1); /// Specifies whether or not to execute Phase 2.
/// </summary>
public bool PerformPhase2 {
get { return false; }
}
_username = configEntries[1]; /// <summary>
_password = configEntries[2]; /// Specifies whether or not to execute Phase 3.
} /// </summary>
public bool PerformPhase3 {
get { return true; }
}
/// <summary> /// <summary>
/// Method invoked on shutdown. /// Gets the execution priority of the provider (0 lowest, 100 highest).
/// </summary> /// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks> public int ExecutionPriority {
public void Shutdown() { get { return 50; }
// Nothing to do }
}
/// <summary> /// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed. /// Gets the Information about the Provider.
/// </summary> /// </summary>
public string ConfigHelpHtml { public ComponentInformation Information {
get { get { return Info; }
return ConfigHelpHtmlValue; }
}
}
#endregion
#region Private Methods
/// <summary> /// <summary>
/// Finds and removes the first occurrence of the custom tag. /// Finds and removes the first occurrence of the custom tag.
@ -180,113 +172,101 @@ namespace ScrewTurn.Wiki.Plugins.PluginPack {
return new KeyValuePair<int, string>(-1, null); return new KeyValuePair<int, string>(-1, null);
} }
/// <summary> /// <summary>
/// Builds an xml document from API calls to Unfuddle.com then runs them through an Xslt to format them. /// Builds an xml document from API calls to Unfuddle.com then runs them through an Xslt to format them.
/// </summary> /// </summary>
/// <returns>An html string that contains the tables to display the ticket information, or null</returns> /// <returns>An html string that contains the tables to display the ticket information, or null</returns>
private string LoadUnfuddleTicketsFromWeb() { private string LoadUnfuddleTicketsFromWeb() {
var xml = BuildXmlFromApiCalls(); var xml = BuildXmlFromApiCalls();
if(xml == null) if(xml == null) return null;
return null;
var settings = new XsltSettings { string results;
EnableScript = true, using(var sw = new StringWriter()) {
EnableDocumentFunction = true using(var xnr = new XmlNodeReader(xml)) {
}; _xslTransform.Transform(xnr, null, sw);
var xsl = new XslCompiledTransform(true); }
using(var reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("ScrewTurn.Wiki.Plugins.PluginPack.Resources.UnfuddleTickets.xsl"))) { results = sw.ToString();
xsl.Load(reader, settings, new XmlUrlResolver()); }
}
string results; HttpContext.Current.Cache.Add("UnfuddleTicketsStore", results, null, DateTime.Now.AddMinutes(10),
using(var sw = new StringWriter()) {
using(var xnr = new XmlNodeReader(xml)) {
xsl.Transform(xnr, null, sw);
}
results = sw.ToString();
}
HttpContext.Current.Cache.Add("UnfuddleTicketsStore", results, null, DateTime.Now.AddMinutes(10),
Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
return results; return results;
} }
/// <summary> /// <summary>
/// Builds 3 Xml Documents, the first two are lookups for Milestone, and People information, the second is the /// Builds 3 Xml Documents, the first two are lookups for Milestone, and People information, the second is the
/// ticket information. /// ticket information.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private XmlDocument BuildXmlFromApiCalls() { private XmlDocument BuildXmlFromApiCalls() {
var milestones = GetXml("/milestones", _username, _password); var milestones = GetXml("/milestones", _username, _password);
if(milestones == null) { if(milestones == null) {
LogWarning("Exception occurred while pulling unfuddled ticket information from the API."); LogWarning("Exception occurred while pulling unfuddled ticket information from the API.");
return null; return null;
} }
var people = GetXml("/people", _username, _password); var people = GetXml("/people", _username, _password);
if(people == null) { if(people == null) {
LogWarning("Exception occurred while pulling unfuddled ticket information from the API."); LogWarning("Exception occurred while pulling unfuddled ticket information from the API.");
return null; return null;
} }
var tickets = GetXml("/ticket_reports/dynamic?sort_by=priority&sort_direction=DESC&conditions_string=status-neq-closed&group_by=priority&fields_string=number,priority,summary,milestone,status,version", _username, _password); var tickets = GetXml("/ticket_reports/dynamic?sort_by=priority&sort_direction=DESC&conditions_string=status-neq-closed&group_by=priority&fields_string=number,priority,summary,milestone,status,version", _username, _password);
if(tickets == null) { if(tickets == null) {
LogWarning("Exception occurred while pulling unfuddled ticket information from the API."); LogWarning("Exception occurred while pulling unfuddled ticket information from the API.");
return null; return null;
} }
var results = new XmlDocument(); var results = new XmlDocument();
results.AppendChild(results.CreateXmlDeclaration("1.0", "UTF-8", string.Empty)); results.AppendChild(results.CreateXmlDeclaration("1.0", "UTF-8", string.Empty));
var element = results.CreateElement("root"); var element = results.CreateElement("root");
results.AppendChild(element); results.AppendChild(element);
element.AppendChild(results.ImportNode(milestones.ChildNodes[1], true)); element.AppendChild(results.ImportNode(milestones.ChildNodes[1], true));
element.AppendChild(results.ImportNode(people.ChildNodes[1], true)); element.AppendChild(results.ImportNode(people.ChildNodes[1], true));
element.AppendChild(results.ImportNode(tickets.ChildNodes[1], true)); element.AppendChild(results.ImportNode(tickets.ChildNodes[1], true));
return results; return results;
} }
/// <summary> /// <summary>
/// Produces an API call, then returns the results as an Xml Document /// Produces an API call, then returns the results as an Xml Document
/// </summary> /// </summary>
/// <param name="Url">The Url to the specific API call</param> /// <param name="Url">The Url to the specific API call</param>
/// <param name="Username">An unfuddle account username</param> /// <param name="Username">An unfuddle account username</param>
/// <param name="Password">The password to above unfuddle account</param> /// <param name="Password">The password to above unfuddle account</param>
/// <returns></returns> /// <returns></returns>
private XmlDocument GetXml(string Url, string Username, string Password) { private XmlDocument GetXml(string Url, string Username, string Password) {
try { try {
var results = new XmlDocument(); var results = new XmlDocument();
Url = string.Format("{0}{1}", _baseUrl, Url); Url = string.Format("{0}{1}", _baseUrl, Url);
var request = WebRequest.Create(Url); var request = WebRequest.Create(Url);
request.Credentials = new NetworkCredential(Username, Password); request.Credentials = new NetworkCredential(Username, Password);
var response = request.GetResponse(); var response = request.GetResponse();
using(var reader = new StreamReader(response.GetResponseStream())) { using(var reader = new StreamReader(response.GetResponseStream())) {
var xmlString = reader.ReadToEnd(); var xmlString = reader.ReadToEnd();
try { try {
results.LoadXml(xmlString); results.LoadXml(xmlString);
} }
catch { catch {
LogWarning("Received Unexpected Response from Unfuddle Server."); LogWarning("Received Unexpected Response from Unfuddle Server.");
} }
} }
return results; return results;
} }
catch(Exception ex) { catch(Exception ex) {
LogWarning(string.Format("Exception occurred: {0}", ex.Message)); LogWarning(string.Format("Exception occurred: {0}", ex.Message));
return null; return null;
} }
} }
/// <summary> /// <summary>
/// Logs a warning. /// Logs a warning.
/// </summary> /// </summary>
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
private void LogWarning(string message) { private void LogWarning(string message) {
_host.LogEntry(message, LogEntryType.Warning, null, this); _host.LogEntry(message, LogEntryType.Warning, null, this);
} }
#endregion }
}
} }