using System; using System.Collections; using System.Configuration; using System.Diagnostics; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Serialization; using ByteFX.Data.MySqlClient; using SourceSafeTypeLib; namespace VSSMantisService { /// <summary> /// Integrates Visual Source Safe with Mantis Bugtracker. /// </summary> public class Integrator { /// <summary> /// Reads Visual Source Safe information from the application /// configuration and constructs an Integrator object. /// </summary> public Integrator() { user_ = ConfigurationSettings.AppSettings["username"]; password_ = ConfigurationSettings.AppSettings["password"]; dir_ = ConfigurationSettings.AppSettings["srcsafeini"]; if (!EventLog.Exists(@"VSSMantis")) EventLog.CreateEventSource(@"VSSMantis", @"VSSMantis", @"."); eventlog_ = new EventLog(); eventlog_.Source = @"VSSMantis"; } /// <summary> /// Manages timely destruction of the event log, "VSSMantis". /// </summary> ~Integrator() { } /// <summary> /// Extracts bug-related checkin comments from Visual Source Safe /// and adds it as bugnotes to Mantis. /// </summary> public void Run() { start_ = DateTime.Now; Unpersist(); eventlog_.WriteEntry(@"Commencing run.", EventLogEntryType.Information); database_.Open(dir_, user_, password_); IVSSItem item = database_.get_VSSItem(ConfigurationSettings.AppSettings["rootdir"], false); if (item.Type != 0) { eventlog_.WriteEntry(@"rootdir not valid directory", EventLogEntryType.Error); return; } ArrayList bugitems = ProcessDir(item); foreach (VersionInfo vinfo in bugitems) { try { UpdateMantisDB(vinfo.bugitem, vinfo.version); } catch (Exception ex) { string error = string.Format(@"Error occurred updating mantis tables - this entry has been lost: {0} version: {1}\r\nThe error was: {2}\r\nResuming with remainding items.", vinfo.version.VSSItem.Spec, vinfo.version.VersionNumber.ToString(), ex.Message); eventlog_.WriteEntry(error, EventLogEntryType.Error); } } eventlog_.WriteEntry(@"Terminating run.", EventLogEntryType.Information); Persist(); } private void Persist() { try { using (StreamWriter sw = new StreamWriter(@"lastrun.xml", false, Encoding.UTF8)) { XmlSerializer xs = new XmlSerializer(typeof(DateTime)); xs.Serialize(sw, start_); } } catch (Exception) { } finally { since_ = start_; } } private void Unpersist() { try { using (StreamReader sr = new StreamReader(@"lastrun.xml", Encoding.UTF8)) { XmlSerializer xs = new XmlSerializer(typeof(DateTime)); since_ = (DateTime)xs.Deserialize(sr.BaseStream); } } catch (Exception) { since_ = new DateTime(0); } } private ArrayList ProcessDir(IVSSItem item) { ArrayList al = new ArrayList(); foreach (IVSSItem child in item.get_Items(false)) { if (child.Type == 0) { ArrayList tmp = ProcessDir(child); al.AddRange(tmp); } else { ArrayList tmp = ProcessFile(child); al.AddRange(tmp); } } return al; } private ArrayList ProcessFile(IVSSItem item) { Stack versions = new Stack(); foreach (IVSSVersion version in item.get_Versions(0)) { if (version.Date.CompareTo(since_) >= 0) { if (version.Date.CompareTo(start_) < 0) versions.Push(version); } } ArrayList al = new ArrayList(); while (versions.Count > 0) { IVSSVersion version = (IVSSVersion)versions.Pop(); BugItem[] bugitems = GetBugItems(version); foreach (BugItem bugitem in bugitems) al.Add(new VersionInfo(bugitem, version)); } return al; } private struct VersionInfo { public BugItem bugitem; public IVSSVersion version; public VersionInfo(BugItem bugitem, IVSSVersion version) { this.bugitem = bugitem; this.version = version; } } private BugItem[] GetBugItems(IVSSVersion version) { return GetBugItems(version, new DateTime(0)); } private BugItem[] GetBugItems(IVSSVersion version, DateTime since) { if (version == null) throw new ArgumentException("Parameter must not be null.", "version"); if (!since.Equals(null) && version.Date.CompareTo(since) < 0) return null; string[] lines = version.Comment.Split(new char[]{'\n'}); ArrayList items = new ArrayList(); for (int i = 0; i < lines.Length; ++i) { Match match = Regex.Match(lines[i], @"^\[#?(\d+)\]?(.*)", RegexOptions.Compiled); if (match.Success) { string id = match.Groups[1].Value; string note = match.Groups[2].Value; items.Add(new BugItem(id, note)); } } BugItem[] bugitems = new BugItem[items.Count]; items.CopyTo(bugitems); return bugitems; } private struct BugItem { public string Id; public string Comment; public BugItem(string Id, string Comment) { this.Id = Id; this.Comment = Comment; } } private void UpdateMantisDB(BugItem item, IVSSVersion version) { using (MySqlConnection conn = new MySqlConnection(ConfigurationSettings.AppSettings["mantis"])) { conn.Open(); MySqlTransaction trans = conn.BeginTransaction(); // find user id MySqlCommand cmd_user = conn.CreateCommand(); cmd_user.CommandText = @"SELECT id FROM mantis_user_table WHERE username = @username"; cmd_user.Parameters.Add("@username", MySqlDbType.VarChar, 32).Value = version.Username; object oid = cmd_user.ExecuteScalar(); if (oid == null) { eventlog_.WriteEntry(@"Unable to find user with username: " + version.Username + " for item: " + version.VSSItem.Spec, EventLogEntryType.FailureAudit); trans.Rollback(); return; } uint id = (uint)oid; // ensure bug exists MySqlCommand cmd_bug_cnt = conn.CreateCommand(); cmd_bug_cnt.CommandText = @"SELECT COUNT(id) FROM mantis_bug_table WHERE id = @id"; cmd_bug_cnt.Parameters.Add(@"id", MySqlDbType.Int, 7).Value = item.Id; int ct = (int)cmd_bug_cnt.ExecuteScalar(); if (ct == 0) { eventlog_.WriteEntry(@"Unable to find bug item with id: " + item.Id + " for item: " + version.VSSItem.Spec, EventLogEntryType.FailureAudit); trans.Rollback(); return; } // add bugnote text string comment_data = string.Format("Tracking: {0}\r\n" + "Version: {3}\r\n" + "Checked in: {1}\r\n" + "Comment: {2}", version.VSSItem.Spec, version.Date.ToString(), item.Comment, version.VersionNumber); MySqlCommand cmd_bugnote_text = conn.CreateCommand(); cmd_bugnote_text.CommandText = @"INSERT INTO mantis_bugnote_text_table (note) VALUES (@note)"; cmd_bugnote_text.Parameters.Add(@"note", MySqlDbType.Blob).Value = comment_data; int aff = cmd_bugnote_text.ExecuteNonQuery(); if (aff != 1) { eventlog_.WriteEntry(@"Error inserting bug comment, no rows inserted for item " + version.VSSItem.Spec + ".", EventLogEntryType.Error); trans.Rollback(); return; } // pick out bugnote text id MySqlCommand cmd_bugnote_text_id = conn.CreateCommand(); cmd_bugnote_text_id.CommandText = @"SELECT MAX(id) FROM mantis_bugnote_text_table"; uint btid = (uint)cmd_bugnote_text_id.ExecuteScalar(); // update bugnote table MySqlCommand cmd_bugnote = conn.CreateCommand(); cmd_bugnote.CommandText = @"INSERT INTO mantis_bugnote_table (bug_id, reporter_id, bugnote_text_id, view_state, date_submitted, last_modified) VALUES (@bug_id, @reporter_id, @bugnote_text_id, @view_state, @date_submitted, @last_modified)"; cmd_bugnote.Parameters.Add(@"bug_id", MySqlDbType.Int, 7).Value = item.Id; cmd_bugnote.Parameters.Add(@"reporter_id", MySqlDbType.Int, 7).Value = id; cmd_bugnote.Parameters.Add(@"bugnote_text_id", MySqlDbType.Int, 7).Value = btid; cmd_bugnote.Parameters.Add(@"view_state", MySqlDbType.Int, 2).Value = 10; cmd_bugnote.Parameters.Add(@"date_submitted", MySqlDbType.Datetime).Value = DateTime.Now; cmd_bugnote.Parameters.Add(@"last_modified", MySqlDbType.Datetime).Value = DateTime.Now; aff = cmd_bugnote.ExecuteNonQuery(); if (aff != 1) { eventlog_.WriteEntry(@"Error adding bugnote info for item: " + version.VSSItem.Spec, EventLogEntryType.Error); trans.Rollback(); return; } // get bugnote table id MySqlCommand cmd_bugnote_id = conn.CreateCommand(); cmd_bugnote_id.CommandText = @"SELECT MAX(id) FROM mantis_bugnote_table"; uint bugnoteid = (uint)cmd_bugnote_id.ExecuteScalar(); // update history table MySqlCommand cmd_hist = conn.CreateCommand(); cmd_hist.CommandText = @"INSERT INTO mantis_bug_history_table (user_id, bug_id, date_modified, field_name, old_value, new_value, type) VALUES (@user_id, @bug_id, @date_modified, '', @old_value, '', 2)"; cmd_hist.Parameters.Add("@user_id", MySqlDbType.Int, 7).Value = id; cmd_hist.Parameters.Add("@bug_id", MySqlDbType.Int, 7).Value = item.Id; cmd_hist.Parameters.Add("@date_modified", MySqlDbType.Datetime).Value = DateTime.Now; cmd_hist.Parameters.Add("@old_value", MySqlDbType.VarChar, 128).Value = bugnoteid.ToString().PadLeft(7, '0'); aff = cmd_hist.ExecuteNonQuery(); if (aff != 1) { eventlog_.WriteEntry(@"Error writing history information for bug for item: " + version.VSSItem.Spec, EventLogEntryType.Error); trans.Rollback(); return; } trans.Commit(); } } private IVSSDatabase database_ = new VSSDatabase(); private string user_; private string dir_; private string password_; private DateTime start_; private DateTime since_; private System.Diagnostics.EventLog eventlog_; } }