[C#]取得VSS的檔案狀態

[C#]取得VSS的檔案狀態

我們家是用VSS做版控工具

之前有需求要取得程式在程式庫的狀態

先說明一下整個換版及出入庫流程

flow

由於AP主機和程式庫主機是分開的,所以採用web service方式去取得程式庫的資訊

實作如下 :

STEP 1 引用DLL(Interop.SourceSafeTypeLib.dll)

小弟是拿下列範例中的DLL檔案

An application to fetch the release sources from Visual SouceSafe based on an Excel migration plan

 

STEP 2 建立物件檔

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using SourceSafeTypeLib;
using System.IO;

namespace VssUtil
{
    public class VssReader
    {
        [Flags]
        //出入庫狀態
        public enum CheckStatusEnum
        {
            不存在 = -1,
            已簽入 = 0,
            已簽出By他人 = 1,
            已簽出By登入帳號 = 2
        };
        VSSDatabase vssdb = new VSSDatabase();
        DateTime? notAfter = null;
        public VssReader(string ssdir, string ssuser, string sspwd)
        {
            vssdb.Open(ssdir, ssuser, sspwd);
        }
        #region 顯示資料
        /// <summary>
        /// 取得專案檔案結構XML,傳入saveFolder參數可將最後版本檔案存入指定目錄
        /// 傳入notAfter則可選取某個時間之前簽入的檔案
        /// </summary>
        /// <param name="path"></param>
        /// <param name="saveFolder"></param>
        /// <param name="notAfter"></param>
        /// <returns></returns>
        public XDocument ReadProject(string path,
            string saveFolder = null, DateTime? notAfter = null)
        {
            this.notAfter = notAfter;
            XDocument xd = XDocument.Parse("<P />");
            Explore(path, xd.Root, saveFolder);
            return xd;
        }
        /// <summary>
        /// 以遞迴方式取得專案結構的所有檔案,若傳入savePath則會一併取得檔案寫入
        /// </summary>
        /// <param name="path"></param>
        /// <param name="container"></param>
        /// <param name="savePath"></param>
        private void Explore(string path, XElement container, string savePath = null)
        {
            VSSItem proj = vssdb.get_VSSItem(path, false);
            //在XML建立目錄元素
            XElement dir = new XElement("D",
                new XAttribute("P", proj.Spec), new XAttribute("N", proj.Name));
            if (!string.IsNullOrEmpty(savePath))
            {
                //若有指定儲存位置,計算路徑並確保資料夾已建立
                savePath = Path.Combine(savePath, proj.Name);
                if (!Directory.Exists(savePath))
                    Directory.CreateDirectory(savePath);
            }
            foreach (VSSItem item in proj.get_Items(false))
            {
                //若為VSSITEM_PROJECT,等同目錄,要再展開其下的子項目
                if (item.Type == (int)VSSItemType.VSSITEM_PROJECT)
                    Explore(item.Spec, dir, savePath);
                else
                {
                    //在XML建立檔案項目元素
                    XElement file = new XElement("F",
                        new XAttribute("P", item.Spec),
                        new XAttribute("N", item.Name),
                        new XAttribute("C", item.IsCheckedOut));
                    bool saved = false;
                    //取得該項目的所有版本資訊
                    foreach (VSSVersion v in item.Versions)
                    {
                        //若有指定簽入時間限制,忽略晚於限制時間的版本
                        if (notAfter != null & v.Date.CompareTo(notAfter) > 0)
                            continue;
                        //在檔案項目元素下新增版本元素
                        file.Add(new XElement("V",
                                new XAttribute("N", //若為標籤,則在前方加上l當作版號
                                    string.IsNullOrEmpty(v.Label) ?
                                    v.VersionNumber.ToString() : "l" + v.Label),
                                new XAttribute("U", v.Username),
                                new XAttribute("T",
                                    v.Date.ToString("yyyy/MM/dd HH:mm:ss"))
                            ));
                        //存入第一個吻合的版本(忽略標籤版本)
                        if (!string.IsNullOrEmpty(savePath) &&
                            string.IsNullOrEmpty(v.Label) && !saved)
                        {
                            string filePath = Path.Combine(savePath, item.Name);
                            v.VSSItem.Get(ref filePath, 0);
                            saved = true;
                        }
                        //若有簽入時間限制,則只包含吻合的第一筆
                        if (notAfter != null) break;
                    }
                    dir.Add(file);
                }
            }
            container.Add(dir);
        }
        /// <summary>
        /// 取得檔案清單
        /// </summary>
        /// <param name="path"></param>
        /// <param name="saveFolder"></param>
        /// <param name="notAfter"></param>
        /// <returns></returns>
        public string GetFileList(string path,
            string saveFolder = null, DateTime? notAfter = null)
        {
            StringBuilder sb = new StringBuilder();
            XDocument doc = ReadProject(path, saveFolder, notAfter);
            int result = -1;
            foreach (XElement element in doc.Descendants("F"))
            {
                result = int.Parse(element.Attribute("C").Value);
                sb.AppendLine(element.Attribute("P").Value + ";" + ((CheckStatusEnum)result).ToString());
            }
            return sb.ToString();
        }

        /// <summary>
        /// 檢查程式庫中檔案的狀態
        /// </summary>
        /// <param name="path">程式庫檔案</param>
        /// <returns></returns>
        public string GetFileStatus(string path)
        {
            try
            {
                VSSItem item = vssdb.get_VSSItem(path, false);
                return ((CheckStatusEnum)item.IsCheckedOut).ToString();
            }
            catch (System.Runtime.InteropServices.COMException cmo)
            {
                return CheckStatusEnum.不存在.ToString();
            }
        }
        /// <summary>
        /// 取得目錄及其子目錄名稱      
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public List<string> GetFolderName(string path)
        {
            List<string> folders = new List<string>();
            XDocument doc = ReadProject(path);
            foreach (XElement element in doc.Descendants("D"))
            {
                folders.Add(element.Attribute("P").Value);
            }
            return folders;
        }
        #endregion

        #region 程式庫物件(供web services使用)
        /// <summary>
        /// 取得程式庫物件
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public VsItem GetLibraryItem(string path)
        {
            VsItem vsitem = new VsItem();
            vsitem.fullpath = string.Copy(path);
            vsitem.status = GetFileStatus(path);
            return vsitem;
        }


        /// <summary>
        /// 取得程式庫物件陣列
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public List<VsItem> GetLibraryItemList(List<string> path)
        {
            List<VsItem> libraryList = new List<VsItem>();
            foreach (string pathitem in path)
            {
                VsItem vsitem = new VsItem();
                vsitem.fullpath = string.Copy(pathitem);
                vsitem.status = GetFileStatus(pathitem);
                libraryList.Add(vsitem);
            }
            return libraryList;
        }
        #endregion

        #region 功能操作
        /// <summary>
        /// 從程式庫取得檔案
        /// </summary>
        /// <param name="localPathfolder"></param>
        /// <param name="vssPath"></param>
        /// <returns></returns>
        public bool GetFileFromVSS(string localPathfolder, string vssPath)
        {
            try
            {
                if (Directory.Exists(localPathfolder) == false)
                {
                    Directory.CreateDirectory(localPathfolder);
                }
                VSSItem item = vssdb.get_VSSItem(vssPath, false);
                string filePath = Path.Combine(localPathfolder, item.Spec).Replace("\\$/", "\\").Replace("/", "\\");
                item.Get(ref filePath, 0);
                return true;
            }
            catch (Exception e)
            {
                throw e;
            }

        }

        /// <summary>
        /// 簽出
        /// </summary>
        /// <param name="localPath"></param>
        /// <param name="vssPath"></param>
        /// <returns></returns>
        public bool CheckOut(string comment, string localPath, string vssPath)
        {
            try
            {
                VSSItem item = vssdb.get_VSSItem(vssPath, false);
                item.Checkout(comment, localPath);
                return true;
            }
            catch (Exception e)
            {
                throw e;
            }
        }


        /// <summary>
        /// 取消簽出
        /// </summary>
        /// <param name="localPath"></param>
        /// <param name="vssPath"></param>
        /// <returns></returns>
        public bool UndoCheckout(string localPath, string vssPath)
        {
            try
            {

                VSSItem item = vssdb.get_VSSItem(vssPath, false);
                item.UndoCheckout(localPath);
                return true;
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// 簽入
        /// </summary>
        /// <param name="localPath"></param>
        /// <param name="vssPath"></param>
        /// <returns></returns>
        public bool CheckIn(string comment, string localPath, string vssPath)
        {
            try
            {

                VSSItem item = vssdb.get_VSSItem(vssPath, false);
                item.Checkin(comment, localPath);
                return true;
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// 加入新檔案
        /// </summary>
        /// <param name="comment"></param>
        /// <param name="localPath"></param>
        /// <param name="vssPath"></param>
        /// <returns></returns>
        public bool Add(string comment, string localPath, string vssParentPath)
        {
            try
            {
                CreateProject("", vssParentPath);
                VSSItem item = vssdb.get_VSSItem(vssParentPath, false);
                item.Add(localPath, comment);
                return true;
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// 建立專案目錄(從現有目錄)
        /// </summary>
        /// <param name="comment"></param>
        /// <param name="vssParentPath"></param>
        /// <param name="ProjectName"></param>
        /// <returns></returns>
        public bool CreateProjectWithParent(string comment, string vssParentPath,string ProjectName)
        {
            try
            {
                VSSItem item = vssdb.get_VSSItem(vssParentPath, false);                   
                item.NewSubproject(ProjectName, comment);
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

        /// <summary>
        /// 建立專案目錄
        /// </summary>
        /// <param name="comment"></param>
        /// <param name="vssPath"></param>
        /// <returns></returns>
        public bool CreateProject(string comment, string vssPath)
        {
            try
            {
                string[] sep = new string[] { "/" };
                string[] paths = vssPath.Substring(2, vssPath.Length - 2).Split(sep, StringSplitOptions.RemoveEmptyEntries);                

                string projectPath = "$";
                int counter = 1;
                //建立第一個目錄
                CreateProjectWithParent(comment, "$/", paths[0]);

                foreach (string pathitem in paths)
                {                   
                    projectPath += "/" + pathitem;
                    if (counter < paths.Length)
                    {                        
                        CreateProjectWithParent(comment, projectPath, paths[counter]);
                    }
                    counter++;                    
                }                
                return true;
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        #endregion

        #region 其他功能
        /// <summary>
        /// 從字串寫檔案
        /// </summary>
        /// <param name="FileName"></param>
        /// <param name="queryResult"></param>
        public void WriteFile(string FileName, string queryResult)
        {
            FileStream fs = new FileStream(FileName, FileMode.Create);
            StreamWriter sw = new StreamWriter(fs);
            string buffer = string.Empty;
            sw.Write(queryResult);
            sw.Flush();
            sw.Close();
            fs.Close();
        }
        #endregion


    }
    /// <summary>
    /// 程式庫物件
    /// </summary>
    [Serializable]
    public class VsItem
    {
        /// <summary>
        /// 程式庫完整路徑
        /// </summary>
        public string fullpath { get; set; }
        /// <summary>
        /// 程式庫狀態
        /// </summary>
        public string status { get; set; }
    }
}

引用方法如下

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using Model = VssUtil;
using System.Diagnostics;

namespace VssControl
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Model.VssReader vssReader = new Model.VssReader(@"\\127.0.0.1\VSSWeb\srcsafe.ini", "admin", string.Empty);
                //讀取目錄清單
                foreach (string folderitem in vssReader.GetFolderName("$/DotNet4"))
                {
                    Console.WriteLine(folderitem);
                }
                //Get file 從VSS
                vssReader.GetFileFromVSS(@"H:\vs2", "$/App_Code/UserInfo.cs");

                //取得檔案狀態
                Console.WriteLine(vssReader.GetFileStatus("$/App_Code/UserInfo.cs"));

                // Add File
                 vssReader.Add("", @"H:\123.txt", "$/App_Code/AB");

                //Check Out
                vssReader.CheckOut("", @"C:\vs2\App_Code\AB\123.txt", "$/App_Code/AB/123.txt");

                //Check in
                vssReader.CheckIn("這是簽入註解", @"C:\vs2\App_Code\AB\123.txt", "$/App_Code/AB/123.txt");


                vssReader.CheckOut("", @"C:\vs2\App_Code\AB\123.txt", "$/App_Code/AB/123.txt");
                //UndoCheckout                
                vssReader.UndoCheckout(@"C:\vs2\App_Code\AB\123.txt", "$/App_Code/AB/123.txt");

                //建立專案目錄(第一層目錄需要建立)
                vssReader.CreateProject("", "$/App_Code/AB/CD/EF/GH/IJK");

                //建立專案目錄第一層目錄
                vssReader.CreateProjectWithParent("", "$/", "aabbcc");
              
            }
            catch (System.Runtime.InteropServices.COMException cmo)
            {
                Console.Write(cmo.Message);
                Console.Read();
            }
        }       
    }
}

STEP3 建立Web services

只建立查詢的方法

using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using Model = VssUtil;
using System.Web.Configuration;

/// <summary>
/// VssFileManager 的摘要描述
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// 若要允許使用 ASP.NET AJAX 從指令碼呼叫此 Web 服務,請取消註解下一行。
// [System.Web.Script.Services.ScriptService]
public class VssFileManager : System.Web.Services.WebService {
    private string ssDir = WebConfigurationManager.AppSettings["ssDir"].ToString();
    private string ssAccount = WebConfigurationManager.AppSettings["ssAccount"].ToString();
    private string ssPassword = WebConfigurationManager.AppSettings["ssPassword"].ToString();
    private Model.VssReader vssReader = null;
    public VssFileManager () {

        //如果使用設計的元件,請取消註解下行程式碼 
        //InitializeComponent(); 
        if (vssReader == null)
        {
            vssReader = new Model.VssReader(ssDir, ssAccount, ssPassword);
        }
    }
    /// <summary>
    /// 取得程式庫檔案狀態(已簽入,已簽出,不存在)
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    [WebMethod]   
    public string GetFileStatus(string filePath)
    {
        return vssReader.GetFileStatus(filePath);
    }

    /// <summary>
    /// 取得單筆程式庫檔案查詢結果
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    [WebMethod]
    public VssUtil.VsItem GetLibraryItem(string filePath)
    {
        return vssReader.GetLibraryItem(filePath);
    }
    /// <summary>
    /// 取得多筆程式庫檔案查詢結果
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    [WebMethod]
    public List<VssUtil.VsItem> GetLibraryItemList(List<string> path)
    {
        return vssReader.GetLibraryItemList(path);
    }
}

web.config內容

<!--
  如需如何設定 ASP.NET 應用程式的詳細資訊,請造訪
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
	<appSettings>
		<add key="ssDir" value="\\127.0.0.1\VSSWeb\srcsafe.ini"/>
		<add key="ssAccount" value="admin"/>
		<add key="ssPassword" value=""/>
	</appSettings>
  <system.web>
	<compilation debug="false" targetFramework="4.0">
	  <assemblies>
		<add assembly="Microsoft.VisualStudio.SourceSafe.Interop, Version=5.2.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
	  </assemblies>
	</compilation>
  </system.web>
</configuration>

STEP4 在頁面中引用

    {
        VssFileManager.VssFileManager vssMgr = new VssFileManager.VssFileManager();        
        lab_QueryResult.Text = "[檔案]" + filePath + " : " + vssReader.GetFileStatus(filePath);
    }

這樣就完成了

 

備註

如果出現VSS資料庫檔案無法讀取的狀況

可以參照下列方式解決

Access to file ..\vss\data\rights.dat" denied

How to: Set Share Permissions for a Database

可以利用command line方式去改變資料夾的使用權限

DOS 使用指令變更Windows檔案【資料夾】的屬性及安全性

參考資料

An application to fetch the release sources from Visual SouceSafe based on an Excel migration plan

C# Sourcesafe Automation

How to: Create a C# Test Project

Visual SourceSafe 6.0 Automation

SourceSafe 2005 automation

VSS Automation - Interop.dll not availalbe?

VSS 轉換器疑難排解

注册VSS2005到IDE