Building a database script using NAnt – supporting source code

I had a couple of requests for the code I used to build my sql script on the fly in conjunction with NAntContrib (blogged about it here).  Before I show the code, my next refactoring would include (1) creating a ProcessState interface and generating seperate ProcessState classes for NAnt and MultiThreaded environments and (2) do the same for vssManager to handle multiple source control engines.


Before I list the code, I suppose I should add a CMA disclaimer:


The code listed below is offered without any warranty expressed or implied.  This is despite anything stated before, after or during this posting.  To understand the scope of this lack of a warranty, understand that no guarantee is made that code will compile and if it does compile and actually accomplishes anything it is merely a coincidence on par with a monkey randomly banging on a typewriter and producing a word-for-word copy of Hamlet.  If you use the code and it causes you any problems, you assume full responsibility for the problems and obsolve the author of any form of responsibility.


I should have been a lawyer… now for the code…


 


 


using System;
using System.Collections;
using System.IO;
using SourceSafeTypeLib;
using System.Xml;



namespace hssScriptBuilder
{
/// <summary>
/// Summary description for hssSourceControl.
/// </summary>
public class hssSourceControl
{
     public class ProcessState
     {
         /// <summary>
         /// Stateful information used for current status displays and caching
         /// of progress data between worker and ui threads. This data must be
         /// protected for multi-threaded access.
         /// </summary>

         private ArrayList m_alProcessNotes = new ArrayList();
        
         private String m_sCurrentSection;
         private String m_sCurrentItem;

         private int m_iSectionCount = 0;
         private int m_iCurrentSection = 0;
         private int m_iItemCount = 0;

         private bool m_bStop = false;

         /// <summary>
         /// Project Property: set/get of NantProject for displaying run
         /// status over the Nant channel.
         /// </summary>
         private NAnt.Core.Project _nantProject = null;

         public NAnt.Core.Project NAntProject
         {
            get { return _nantProject; }
            set { _nantProject = value; }
         }
        

         /// <summary>
         /// Stop: Allows for notifying the worker thread to quit processing
         /// the Source Control files.
         /// </summary>
         /// <param name=”bStop”></param>
         public void Stop( bool bStop )
         {
            m_bStop = bStop;

         }

         /// <summary>
         /// IsStopped: Used to determine if the worker thread has been requested
         /// to stop processing.
         /// </summary>
         /// <returns></returns>
         public bool IsStopped()
         {
            return m_bStop;
         }

         /// <summary>
         /// GetItemCount(): returns the number of processed files during the course
         /// of a run.
         /// </summary>
         /// <returns></returns>
         public int GetItemCount()
         {
            lock(this)
             return m_iItemCount;
         }

         /// <summary>
         /// GetCurrentSectionIndex(): returns the current section number being process.
         /// A section is defined as a single line item in the SCC processing XML document.
         /// </summary>
         /// <returns></returns>
         public int GetCurrentSectionIndex()
         {
            lock(this)
             return m_iCurrentSection;
         }

         /// <summary>
         /// GetSectionCount(): Returns the total number of sections declared
         /// for a given run.
         /// </summary>
         /// <returns></returns>
         public int GetSectionCount()
         {
            lock(this)
             return m_iSectionCount;
         }

         /// <summary>
         /// GetCurrentSection(): Returns the name of the current section being processed.
         /// </summary>
         /// <returns>String</returns>
         public String GetCurrentSection()
         {
            lock(this)
             return m_sCurrentSection ;
         }

         /// <summary>
         /// GetProcessNotes(): Returns an ArrayList of messages created by the
         /// worker thread since the last time GetProcessNotes was called.
         /// </summary>
         /// <returns>ArrayList of strings</returns>
         public ArrayList GetProcessNotes()
         {
            ArrayList aList = new ArrayList();
            lock(this)
            {
             IEnumerator oEnum = m_alProcessNotes.GetEnumerator();
                    
             while( oEnum.MoveNext() )
                 aList.Add( oEnum.Current );
                    
             m_alProcessNotes.Clear();
            }

            return aList;
         }

         /// <summary>
         /// GetCurrentItem(): Returns the name of the current item being processed.
         /// </summary>
         /// <returns></returns>
         public String GetCurrentItem()
         {
            lock(this)
            {
             return m_sCurrentItem;
            }
         }

         /// <summary>
         /// AddItem(): Adds one to the item count – used by the worker thread.
         /// </summary>
         public void AddItem()
         {
            lock(this)
             m_iItemCount += 1;
         }

         /// <summary>
         /// SetSectionCount(): used to set the number of sections to be processed.
         /// </summary>
         /// <param name=”iCount”></param>
         public void SetSectionCount( int iCount )
         {
            lock(this)
             m_iSectionCount = iCount;
         }

         /// <summary>
         /// SetCurrentItem(): Used by the worker thread to set the current item being processed.
         /// </summary>
         /// <param name=”sItem”></param>
         public void SetCurrentItem( String sItem )
         {
            lock(this)
             m_sCurrentItem = sItem;
         }

         /// <summary>
         /// AddProcessNote(String): Used by the worker thread to
         /// </summary>
         /// <param name=”sNote”></param>
         public void AddProcessNote( String sNote )
         {
            lock(this)
             m_alProcessNotes.Add( sNote );

            if( _nantProject != null )
             _nantProject.Log(NAnt.Core.Level.Verbose, ” Processing… {0}”, sNote );
         }

         /// <summary>
         /// Notifies the UI thread of a fatal error that has occurred.
         /// </summary>
         /// <param name=”sNote”></param>
         public void SetFatalError( String sNote )
         {
            lock(this)
             m_alProcessNotes.Add( sNote );

            if( _nantProject != null )
             _nantProject.Log(NAnt.Core.Level.Error, sNote );
            
         }

         /// <summary>
         /// SetCurrentSection(): Sets the current section name being processed
         /// </summary>
         /// <param name=”sSection”></param>
         public void SetCurrentSection( String sSection )
         {
            lock(this)
            {
             if (_nantProject != null )
             {
                 _nantProject.Log(NAnt.Core.Level.Info, “Processing Segment ({0} of {1}): {2}”, m_iCurrentSection+1, m_iSectionCount ,sSection );
             }

             m_sCurrentSection = sSection;
             m_iCurrentSection += 1;
            }
         }
     }


     /// <summary>
     /// class vssManager: This class is reponsible for managing the interation
     /// with Source Control. Eventually moving this to a base/derived class pair
     /// could allow for supporting other source control systems.
     /// </summary>
     private class vssManager
     {
         private const int VSSITEM_PROJECT = 0;
         private const int VSSITEM_ITEM = 1;

         private StreamWriter m_sw;
         private ProcessState m_oProcessState;

         public vssManager( StreamWriter sw, ProcessState oProcessState )
         {
            m_sw = sw;
            m_oProcessState = oProcessState;
         }


         /// <summary>
         /// ProcessItem(): Processes a given source safe item and adds it to
         /// the output stream. The method deletes the file on the way out to
         /// ensure that subsequent reads of the same file will result in a physical
         /// code get.
         /// </summary>
         /// <param name=”item”>The sourcesafe item object</param>
         /// <param name=”sLocalPath”>The local path where the physical file should be stored while processing.</param>
         private void ProcessItem( VSSItem item, String sLocalPath )
         {
            
             // Check to see if we’ve been requested to stop.
            if ( m_oProcessState.IsStopped() )
             return;

            // Add a process warning if the file is checked out
            if( item.IsCheckedOut != 0 )
            {
             IVSSCheckouts oCO = item.Checkouts;
             String sName = “”;

             if( oCO.Count > 0 )
             {
                 IEnumerator oEnum = oCO.GetEnumerator();
                 oEnum.MoveNext();
                 IVSSCheckout oCheck = (IVSSCheckout)oEnum.Current;
                        

                 sName = ” to “ + oCheck.Username;
             }
                                        
             m_oProcessState.AddProcessNote( “WARNING: “ + item.Name + ” is checked out” + sName + “.”);
            }
                

            // perform a get of the file to the local path
            String sSpec = System.IO.Path.Combine(sLocalPath, item.Name);

            int iFlags = Convert.ToInt32(VSSFlags.VSSFLAG_REPREPLACE) |
                         Convert.ToInt32(VSSFlags.VSSFLAG_USERRONO);

            item.Get(ref sSpec, iFlags);

            m_oProcessState.SetCurrentItem(item.Name);
            m_oProcessState.AddItem();


            // add the contents of the file to the output stream
            using (StreamReader sr = new StreamReader(sSpec))
            {
             String line;

             while(( line = sr.ReadLine()) != null)
                 m_sw.WriteLine(line);
            }

            // delete the processed file.
            System.IO.File.Delete(sSpec);

         }

         /// <summary>
         /// ProcessProject(): Recurses into a folder/project hierarchy when given a parent project.
         /// The function dispatches the ProcessItem call when it reaches a file object.
         /// </summary>
         /// <param name=”item”></param>
         /// <param name=”sLocalPath”></param>
         private void ProcessProject( VSSItem item, String sLocalPath )
         {
                
            foreach( VSSItem subItem in item.get_Items(false))
            {
             if( m_oProcessState.IsStopped() )
                 return;

             if( subItem.Type == VSSITEM_PROJECT )
                 ProcessProject( subItem, sLocalPath );
             else
                 ProcessItem( subItem, sLocalPath );
            }
         }

         /// <summary>
         /// ProcessLineItem(): Public interface that accepts a path from the client and begins processing the structure.
         /// </summary>
         /// <param name=”oVssDb”></param>
         /// <param name=”sPath”></param>
         /// <param name=”sLocalPath”></param>
         public void ProcessLineItem( VSSDatabase oVssDb, String sPath, String sLocalPath )
         {
            VSSItem item = oVssDb.get_VSSItem(sPath, false);

            if( item.Type == VSSITEM_PROJECT )
             ProcessProject( item, sLocalPath);
            else
             ProcessItem( item, sLocalPath );

         }

         /// <summary>
         /// GetSourceSafeDatabase(): Takes the parent XMLDocument and locates the sourcesafe login credentials for processing purposes.
         /// This function returns an active sourcesafe database object used for subsequent processing.
         /// </summary>
         /// <param name=”oDoc”></param>
         /// <returns></returns>
         public static VSSDatabase GetSourceSafeDatabase(XmlDocument oDoc)
         {
            String sIniPath, sUserName, sPassword;
            try
            {
             XmlNodeList nodeIni = oDoc.SelectNodes(“/hssSourceManagerSection/vssConfig”);

             sIniPath = nodeIni.Item(0).Attributes.GetNamedItem(“IniFile”).Value;
             sUserName = nodeIni.Item(0).Attributes.GetNamedItem(“UserName”).Value;
             sPassword = nodeIni.Item(0).Attributes.GetNamedItem(“Password”).Value;
            }
            catch (Exception ex)
            {
             throw new Exception(“Unable to retrieve SourceSafe configuration information from config file.”, ex);
            }


            VSSDatabase oVssDb = new VSSDatabaseClass();

            try
            {
             oVssDb.Open(sIniPath, sUserName, sPassword);
            }
            catch( Exception ex)
            {
             throw new Exception(“Unable to open SourceSafe Database”, ex);
            }


            return oVssDb;
                            
         }

     }


     private ProcessState m_oProcessState;
     private XmlDocument m_oBuildConfig;
     private StreamWriter m_sw;
     private NAnt.Core.Project m_nantProject;
     private String m_sLocalPath;


     /// <summary>
     /// GetProcessState(): Returns the live process state – used by the ui thread to gain access to status info.
     /// </summary>
     /// <returns></returns>
     public ProcessState GetProcessState()
     {
         return m_oProcessState;
     }

     /// <summary>
     /// hssSourceControl(): default constructor
     /// </summary>
     public hssSourceControl()
     {
         m_oProcessState = new ProcessState();
     }

     /// <summary>
     /// ProcessSourceProject(): Responsible for taking the source XmlDocument and processing all the items within it
     /// into the stream output.
     /// </summary>
     /// <param name=”oDoc”>XmlDocument with the agreed upon format to identify processing of the database project.</param>
     /// <param name=”sw”>StreamWriter object that contains the aggregated text.</param>
     /// <param name=”sLocalPath”>Directory path to processing area used to hold file temporarily during build.</param>
     /// <param name=”nantProject”>Nant project object used to direct status output when called from the Nant environment</param>
     public void ProcessSourceProject( XmlDocument oDoc, StreamWriter sw, string sLocalPath, NAnt.Core.Project nantProject )
     {
         try
         {
            // here, we’ll connect to SourceSafe and get ready for processing
            VSSDatabase oVss = vssManager.GetSourceSafeDatabase(oDoc);

            // next, we’ll iterate through all the configured lines
            XmlNodeList nodeSubs = oDoc.SelectNodes(“/hssSourceManagerSection/hssSourceItem”);

            vssManager oMgr = new vssManager(sw, m_oProcessState);
            m_oProcessState.NAntProject = nantProject;
        
            m_oProcessState.SetSectionCount( nodeSubs.Count );

            for( int i = 0; i < nodeSubs.Count; i++)
            {
             if( m_oProcessState.IsStopped() )
                 return;
            
             XmlNode xNode = nodeSubs.Item(i);
             String sTitle = xNode.Attributes.GetNamedItem(“Value”, “”).Value;
             String sPath = xNode.Attributes.GetNamedItem(“Path”, “”).Value;
             m_oProcessState.SetCurrentSection(sTitle);
             oMgr.ProcessLineItem(oVss, sPath, sLocalPath);
            }
         }
         catch( Exception ex)
         {
            string sMsg = string.Format(“FATAL ERROR: {0}”, ex.Message );
            m_oProcessState.SetFatalError( sMsg );
         }
     }

     public void SetThreadData( XmlDocument oBuildConfig,
         StreamWriter sw,
         NAnt.Core.Project nantProject,
         String sLocalPath)
     {
         m_oBuildConfig = oBuildConfig;
         m_sw = sw;
         m_nantProject = nantProject;
         m_sLocalPath = sLocalPath;
     }

     public void ThreadProc()
     {
         ProcessSourceProject( m_oBuildConfig, m_sw, m_sLocalPath, m_nantProject );

     }


}
}

This entry was posted in Build Automation. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

2 Responses to Building a database script using NAnt – supporting source code

  1. Steve Hebert's Development Blog says:

    Changing course on NAnt

  2. Darrell says:

    Nice. I’ll have to look at this more in-depth shortly.

Leave a Reply