using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;
using System.Xml;
using System.Xml.Xsl;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki.Plugins.PluginPack {
///
/// Implements a formatter that display tickets from Unfuddle.
///
public class UnfuddleTickets : IFormatterProviderV30 {
private const string ConfigHelpHtmlValue = "Config consists of three lines:
<Url> - The base url to the Unfuddle API (i.e. http://account_name.unfuddle.com/api/v1/projects/project_ID)
<Username> - The username to the unfuddle account to use for authentication
<Password> - The password to the unfuddle account to use for authentication
";
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 ComponentInformation Info = new ComponentInformation("Unfuddle Tickets", "ScrewTurn Software", "3.0.0.204", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/UnfuddleTickets.txt");
private string _config;
private IHostV30 _host;
private string _baseUrl;
private string _username;
private string _password;
#region IFormatterProviderV30 Members
///
/// Specifies whether or not to execute Phase 1.
///
public bool PerformPhase1 {
get {
return false;
}
}
///
/// Specifies whether or not to execute Phase 2.
///
public bool PerformPhase2 {
get {
return false;
}
}
///
/// Specifies whether or not to execute Phase 3.
///
public bool PerformPhase3 {
get {
return true;
}
}
///
/// Gets the execution priority of the provider (0 lowest, 100 highest).
///
public int ExecutionPriority {
get {
return 50;
}
}
///
/// Gets the Information about the Provider.
///
public ComponentInformation Information {
get {
return Info;
}
}
///
/// Performs a Formatting phase.
///
/// The raw content to Format.
/// The Context information.
/// The Phase.
/// The Formatted content.
public string Format(string raw, ContextInformation context, FormattingPhase phase) {
var buffer = new StringBuilder(raw);
var block = FindAndRemoveFirstOccurrence(buffer);
if(block.Key != -1) {
string unfuddleTickets = null;
if(HttpContext.Current != null)
unfuddleTickets = HttpContext.Current.Cache["UnfuddleTicketsStore"] as string;
if(string.IsNullOrEmpty(unfuddleTickets))
unfuddleTickets = LoadUnfuddleTicketsFromWeb();
if(string.IsNullOrEmpty(unfuddleTickets))
unfuddleTickets = LoadErrorMessage;
do {
buffer.Insert(block.Key, unfuddleTickets);
block = FindAndRemoveFirstOccurrence(buffer);
} while(block.Key != -1);
}
return buffer.ToString();
}
///
/// Prepares the title of an item for display (always during phase 3).
///
/// The input title.
/// The context information.
/// The prepared title (no markup allowed).
public string PrepareTitle(string title, ContextInformation context) {
return title;
}
///
/// Initializes the Storage Provider.
///
/// The Host of the Component.
/// The Configuration data, if any.
/// If the configuration string is not valid, the methoud should throw a .
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)
throw new InvalidConfigurationException("Configuration missing required parameters.");
_baseUrl = configEntries[0];
if(_baseUrl.EndsWith("/"))
_baseUrl = _baseUrl.Substring(0, _baseUrl.Length - 1);
_username = configEntries[1];
_password = configEntries[2];
}
///
/// Method invoked on shutdown.
///
/// This method might not be invoked in some cases.
public void Shutdown() {
// Nothing to do
}
///
/// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed.
///
public string ConfigHelpHtml {
get {
return ConfigHelpHtmlValue;
}
}
#endregion
#region Private Methods
///
/// Finds and removes the first occurrence of the custom tag.
///
/// The buffer.
/// The index->content data.
private static KeyValuePair FindAndRemoveFirstOccurrence(StringBuilder buffer) {
Match match = UnfuddleRegex.Match(buffer.ToString());
if(match.Success) {
buffer.Remove(match.Index, match.Length);
return new KeyValuePair(match.Index, match.Value);
}
return new KeyValuePair(-1, null);
}
///
/// Builds an xml document from API calls to Unfuddle.com then runs them through an Xslt to format them.
///
/// An html string that contains the tables to display the ticket information, or null
private string LoadUnfuddleTicketsFromWeb() {
var xml = BuildXmlFromApiCalls();
if(xml == null)
return null;
var settings = new XsltSettings {
EnableScript = true,
EnableDocumentFunction = true
};
var xsl = new XslCompiledTransform(true);
using(var reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("ScrewTurn.Wiki.Plugins.PluginPack.Resources.UnfuddleTickets.xsl"))) {
xsl.Load(reader, settings, new XmlUrlResolver());
}
string results;
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);
return results;
}
///
/// Builds 3 Xml Documents, the first two are lookups for Milestone, and People information, the second is the
/// ticket information.
///
///
private XmlDocument BuildXmlFromApiCalls() {
var milestones = GetXml("/milestones", _username, _password);
if(milestones == null) {
LogWarning("Exception occurred while pulling unfuddled ticket information from the API.");
return null;
}
var people = GetXml("/people", _username, _password);
if(people == null) {
LogWarning("Exception occurred while pulling unfuddled ticket information from the API.");
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);
if(tickets == null) {
LogWarning("Exception occurred while pulling unfuddled ticket information from the API.");
return null;
}
var results = new XmlDocument();
results.AppendChild(results.CreateXmlDeclaration("1.0", "UTF-8", string.Empty));
var element = results.CreateElement("root");
results.AppendChild(element);
element.AppendChild(results.ImportNode(milestones.ChildNodes[1], true));
element.AppendChild(results.ImportNode(people.ChildNodes[1], true));
element.AppendChild(results.ImportNode(tickets.ChildNodes[1], true));
return results;
}
///
/// Produces an API call, then returns the results as an Xml Document
///
/// The Url to the specific API call
/// An unfuddle account username
/// The password to above unfuddle account
///
private XmlDocument GetXml(string Url, string Username, string Password) {
try {
var results = new XmlDocument();
Url = string.Format("{0}{1}", _baseUrl, Url);
var request = WebRequest.Create(Url);
request.Credentials = new NetworkCredential(Username, Password);
var response = request.GetResponse();
using(var reader = new StreamReader(response.GetResponseStream())) {
var xmlString = reader.ReadToEnd();
try {
results.LoadXml(xmlString);
}
catch {
LogWarning("Received Unexpected Response from Unfuddle Server.");
}
}
return results;
}
catch(Exception ex) {
LogWarning(string.Format("Exception occurred: {0}", ex.Message));
return null;
}
}
///
/// Logs a warning.
///
/// The message.
private void LogWarning(string message) {
_host.LogEntry(message, LogEntryType.Warning, null, this);
}
#endregion
}
}