經常看一些的程序,有些一個解決方案帶有多個項目,由於代碼比較多,多人開發,所以好多vs.net下的工程是用source safe進行版本控制的。而用source safe進行版本控制需要局域網路徑共享,因此好多項目換一台機器打開會出現一些問題,比如“解決方案看起來是受源代碼管理,但無法找到它的綁定信息……”之類的提示信息很多。有時候修改了代碼還保存不了,所以想把他去掉,下面是對項目管理前後的一些對比。
一、工程項目比較
同沒有受Source Safe代碼管理的工程相比:
1. 多出了.scc、.vssscc和.vspscc文件;
2. C#項目文件(.csproj)裡面添加了幾行標簽:
SccProjectName = "SAK"
SccLocalPath = "SAK"
SccAuxPath = "SAK"
SccProvider = "SAK"
3.在解決方案文件(.sln)中,中增加了如下節點原素:
GlobalSection(SourceCodeControl) = preSolution
SccNumberOfProjects = 4
SccLocalPath0 = .
……
SccLocalPath3 = SUBSCRIBE_TOOLS
CanCheckoutShared = false
EndGlobalSection
二、編寫實現的類
既然文件增多了,還有有些文件被修改,所以想通過編程把他修改回原樣,這樣可能可以去掉那些提示信息,所以就寫了下面的代碼。
using System;
using System.IO;
using System.Text;
using System.Threading;
namespace ZZ
{
/// <summary>
/// 操作信息事件代理
/// </summary>
public delegate void OperateNotifyHandler(object sender,VssEventArgs e);
/// <summary>
/// VssConverter 處理解決方案或項目的SourceSafe關聯。
/// </summary>
public class VssConverter
{
//操作根目錄
private string operatePath;
/// <summary>
/// 操作信息事件
/// </summary>
public event OperateNotifyHandler OperateNotify;
/// <summary>
/// 線程結束通知事件
/// </summary>
public event EventHandler ThreadCompleted;
/// <summary>
/// 構造函數
/// </summary>
/// <param name="operatePath">項目路徑</param>
public VssConverter(string operatePath)
{
this.operatePath = operatePath;
}
OperatePath屬性,用來設置或獲取當前需要處理的工程路徑,不過在運行時最好不要設置他,
/// <summary>
/// 設置解決方案工程路徑
/// </summary>
public string OperatePath
{
get{return this.operatePath;}
set{this.operatePath = value;}
}
下面是一個public 修飾符的函數,也是類實例的惟一對外公開的方法,裡面用了兩個線程來分別刪除文件和修改文件。
/// <summary>
/// 去除Source Safe代碼管理
/// </summary>
public void RemoveVss()
{
Thread deleteThread = new Thread(new ThreadStart(DeleteVssFile));
Thread RemoveVssIdentifyThread = new Thread(new ThreadStart(RemoveVssIdentify));
deleteThread.Start();
RemoveVssIdentifyThread.Start();
}
後來測試了一下deleteThread的完成要比RemoveVssIdentifyThread快一些,當然也可以再開一個線程來分擔文件的修改,不過這裡需要注意的是好多文件是帶只讀屬性的,所以還要把文件屬性設置成Normal才能順利完成操作,否則會拋出異常。
這裡使用了遞歸來刪除相關文件,由三個函數構成:
/// <summary>
/// 線程委托函數,完成刪除"*.scc","*.vssscc"以及*.vspscc文件功能。
/// </summary>
private void DeleteVssFile()
{
DeleteVssFile(this.operatePath);
//通知刪除文件結束
OnThreadCompleted(this,new EventArgs());
}
/// <summary>
/// 遞歸函數,刪除"*.scc","*.vssscc"以及*.vspscc文件。
/// </summary>
/// <param name="path">當前處理路徑</param>
private void DeleteVssFile(string path)
{
DeleteFile(Directory.GetFiles(path,"*.scc"));
DeleteFile(Directory.GetFiles(path,"*.vssscc"));
DeleteFile(Directory.GetFiles(path,"*.vspscc"));
foreach(string dir in Directory.GetDirectories(path))
DeleteVssFile(dir);
}
/// <summary>
/// 刪除文件,真正刪除文件
/// </summary>
/// <param name="files"></param>
private void DeleteFile(string [] files)
{
foreach(string file in files)
{
FileInfo fi = new FileInfo(file);
fi.Attributes = FileAttributes.Normal;
File.Delete(file);
OnOperateNotify(this,new VssEventArgs(file+"刪除完成"));
}
}
對於".sln"解決方案文件和".csproj’"C#項目文件的修改也采用了遞歸實現:
/// <summary>
/// 線程委托函數,去除"*.sln"解決方案文件和"*.csproj"C#項目文件的.Vss關聯標簽。
/// </summary>
private void RemoveVssIdentify()
{
RemoveVssTag(this.operatePath);
//通知去除標簽結束
OnThreadCompleted(this,new EventArgs());
}
/// <summary>
/// 去除"*.sln"解決方案文件和"*.csproj"C#項目文件的.Vss關聯標簽。
/// </summary>
/// <param name="path">當前處理路徑</param>
private void RemoveVssTag(string path)
{
RemoveTagContent(Directory.GetFiles(path,"*.sln"));
RemoveTagContent(Directory.GetFiles(path,"*.csproj"));
foreach(string dir in Directory.GetDirectories(path))
RemoveVssTag(dir);
}
下面的函數用來分析處理文件的修改,因為都是做刪除部分文件內容的工作,所以把處理函數寫成了一個,
/// <summary>
/// 去除"*.sln"解決方案文件和"*.csproj"C#項目文件的.Vss關聯標簽。
/// </summary>
/// <param name="file">當前處理文件</param>
private void RemoveTagContent(string [] files)
{
foreach(string file in files)
{
string strStart; //Vss標簽文本開始內容
string strEnd; //標簽文本結束內容
int offSet;//結束標簽文本的偏移量
FileInfo fi = new FileInfo(file);
fi.Attributes =FileAttributes.Normal;
if(fi.Extension == ".sln")//如果是解決方案文件
{
strStart = "GlobalSection(SourceCodeControl)";
strEnd = "EndGlobalSection";
offSet = 19;//包含/r/n和空格
}
else//如果是項目文件
{
strStart = "SccProjectName";
strEnd = ">";
offSet = 0;
}
try
{
int start;//Vss標簽文本開始索引
int end;//Vss標簽文本結束索引
string content;//文件內容
using(FileStream fs = new FileStream(file,FileMode.Open,FileAccess.ReadWrite,FileShare.ReadWrite))
{
StreamReader sr = new StreamReader(fs);
content = sr.ReadToEnd();
sr.Close();
start = content.IndexOf(strStart);
}
if(start!=-1)//文件需要去除標簽
{
using(FileStream fs = new FileStream(file,FileMode.Truncate,FileAccess.Write,FileShare.Read))
{
end = start+content.Substring(start).IndexOf(strEnd)+offSet;
content = content.Substring(0,start)+content.Substring(end);
StreamWriter sw = new StreamWriter(fs);
sw.Write(content);
sw.Close();
}
OnOperateNotify(this,new VssEventArgs(file+"去除標簽完成"));
}
}
catch(Exception ex)
{
OnOperateNotify(this,new VssEventArgs(file+"操作錯誤:"+ex.ToString()));
}
}
}
當此為止,上面的程序實現了主要的功能,不過上面定義的事件,下面就是關於事件的函數,
/// <summary>
/// 操作信息事件通知
/// </summary>
/// <param name="sender">VssConverter</param>
/// <param name="e">參數,</param>
protected virtual void OnOperateNotify(object sender,VssEventArgs e)
{
if(OperateNotify!=null)
OperateNotify(sender,e);
}
/// <summary>
/// 線程結束事件通知
/// </summary>
/// <param name="sender">VssConverter</param>
/// <param name="e">參數</param>
protected virtual void OnThreadCompleted(object sender,EventArgs e)
{
if(ThreadCompleted!=null)
ThreadCompleted(sender,e);
}
}
相對於事件中的參數,這裡定義了一個類從EventArgs繼承,裡面只包含一個字段用來保存信息,
/// <summary>
/// 消息通知事件參數類
/// </summary>
public class VssEventArgs : EventArgs
{
private string message;
/// <summary>
/// 構造函數
/// </summary>
/// <param name="message"></param>
public VssEventArgs(string message)
{
this.message = message;
}
/// <summary>
/// 消息內容
/// </summary>
public string Message
{
get{return this.message;}
}
}
}//命名空間
三、測試使用
程序測試運行界面,
界面部分代碼大多數由設計器生成,下面列出了主要添加代碼,
//委托,更新文本框
private delegate void AppendTextHandler(string content);
//標記轉換操作是否完成
private int convertOK =0;
private System.Windows.Forms.TextBox textBoxFolder;//路徑文本框
private System.Windows.Forms.Button buttonFolder;//浏覽按鈕
private System.Windows.Forms.TextBox textBoxInfo;//信息顯示框
private System.Windows.Forms.Button buttonOK;//運行按鈕
private System.Windows.Forms.Button buttonCancel;//退出按鈕
按鈕處理函數用來打開一個路徑選擇框,
private void buttonFolder_Click(object sender, System.EventArgs e)
{
FolderBrowserDialog myDialog = new FolderBrowserDialog();
myDialog.ShowNewFolderButton = false;
myDialog.Description = "選擇需要處理的解決方案或項目目錄";
if(myDialog.ShowDialog()==DialogResult.OK)
this.textBoxFolder.Text = myDialog.SelectedPath;
myDialog.Dispose();
}
運行函數,在這裡面實例化VssConverter類,並調用了RemoveVss方法,運行時把幾個按鈕禁了,裡面注冊了兩個事件,起信息傳遞作用,不過對於直接在地址欄中輸入非法路徑沒有做具體判斷,
private void buttonOK_Click(object sender, System.EventArgs e)
{
if(this.textBoxFolder.Text.Length>1)
{
this.textBoxInfo.Clear();
this.convertOK = 0;
this.buttonOK.Enabled = false;
this.buttonFolder.Enabled = false;
this.buttonCancel.Enabled = false;
this.textBoxFolder.Enabled = false;
VssConverter vssConverter = new VssConverter(this.textBoxFolder.Text);
vssConverter.OperateNotify += new OperateNotifyHandler(vssConverter_OperateNotify);
vssConverter.ThreadCompleted += new EventHandler(vssConverter_ThreadCompleted);
vssConverter.RemoveVss();
}
else
MessageBox.Show("請輸入解決方案或項目路徑!");
}
下面是兩個事件處理函數,第一個是用來在前台即時顯示當前處理的文件信息,第二個函數是用來通知線程的執行結果。函數如下,
// 信息通知
private void vssConverter_OperateNotify(object sender, VssEventArgs e)
{
AppendTextHandler ath = new AppendTextHandler(this.textBoxInfo.AppendText);
this.textBoxInfo.BeginInvoke(ath,new object[]{e.Message+Environment.NewLine});
}
// 線程結束通知
private void vssConverter_ThreadCompleted(object sender, EventArgs e)
{
if(this.convertOK==0)
this.convertOK++;
else
{
this.buttonOK.Enabled = true;
this.buttonFolder.Enabled = true;
this.buttonCancel.Enabled = true;
this.textBoxFolder.Enabled = true;
this.textBoxInfo.AppendText("#### 轉換完成 ####");
}
}
總結,程序通過搜索指定目錄下的文件,根據擴展名進行相應的操作來完成處理,其中為了加快運行速度增加了線程來處理。