珂珂的个人博客 - 一个程序猿的个人网站

目录同步工具

     经常需要将本地的网站发布到服务器上,觉得太麻烦,有时经常需要整个目录拷贝,于是就诞生了这个目录同步的工具。该工具需要一个服务端配合,我这里就用asp.net 的 ashx处理,有能力的可以改成TCP 传输。原理就是对比服务器文件的SHA1,如果相同就不用上传了,所以能节省不少时间。考虑到方便,我将配置文件写成了xml 这样每次选择对应配置就行了,配置还加入了一个忽略文件列表。我自己的网站也是这样上传的。

服务端代码,主要处理列出所有文件数据、上传、删除。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Serialization;

namespace WebUpdate
{
    ///     /// DirUpdate 的摘要说明
    ///     public class DirSync : IHttpHandler
    {

        private HttpRequest Request;
        private HttpResponse Response;
        public void ProcessRequest(HttpContext context)
        {
            Request = context.Request;
            Response = context.Response;
            context.Response.ContentType = "text/plain";

            try
            {
                if (String.IsNullOrEmpty(Request["action"]))
                {
                    if (!String.IsNullOrEmpty(Request["dir"]))
                    {
                        string appDir = context.Server.UrlDecode(Request["dir"]);
                        if (!Directory.Exists(appDir))
                        {
                            throw new Exception("远程目录不存在");
                        }

                        List list = new List();
                        LoadFile(list, appDir, appDir);
                        XmlSerializer sz = new XmlSerializer(list.GetType());
                        sz.Serialize(Response.OutputStream, list);
                    }
                    else
                    {
                        Response.Write("缺少dir参数");
                    }
                }

                if (Request["action"] == "upload")
                {
                    string dir = Path.GetDirectoryName(context.Server.UrlDecode(Request["remotePath"]));
                    if (!Directory.Exists(dir))
                    {
                        Directory.CreateDirectory(dir);
                    }
                    Request.Files[0].SaveAs(context.Server.UrlDecode(Request["remotePath"]));
                    Response.Write("server:upload ok----" + context.Server.UrlDecode(Request["remotePath"]));
                }

                if (Request["action"] == "delete")
                {
                    File.Delete(context.Server.UrlDecode(Request["remotePath"]));
                    Response.Write("server:delete file ok-----" + context.Server.UrlDecode(Request["remotePath"]));
                }
            }
            catch(Exception ex)
            {
                Utility.WriteLog(ex.Message+Environment.NewLine+ex.StackTrace);
            }
        }

        public class FileMessage
        {
            public long Size { get; set; }

            public string Sha1 { get; set; }

            public string FileName { get; set; }
        }

        private void LoadFile(List list, string p, string baseDir)
        {
            foreach (string file in Directory.GetFiles(p))
            {
                if (file.Contains("update.config.xml"))
                {
                    continue;
                }

                list.Add(GetFileInfo(file, baseDir));
            }

            foreach (string folder in Directory.GetDirectories(p))
            {
                LoadFile(list, folder, baseDir);
            }
        }

        ///         /// 获取文件信息
        ///         private FileMessage GetFileInfo(String filePath, string baseDir)
        {
            FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            long lenth = fs.Length;
            System.Security.Cryptography.HashAlgorithm algorithm = System.Security.Cryptography.SHA1.Create();
            String result = BitConverter.ToString(algorithm.ComputeHash(fs)).Replace("-", "");
            fs.Close();
            return new FileMessage { Size = lenth, Sha1 = result, FileName = filePath.Replace(baseDir, "").TrimStart('\\').TrimStart('/') };
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}


下面的xml 的配置代码

      http://update.fyj.me/DirSync.ashx    D:\Git\Blogs-fyj\Blogs.UI.Main\    D:\VPS\Web\www.kecq.com\    App_Start    Areas    aspnet_client    Controllers    Filters    Models    obj    Properties    Blogs.UI.Main.csproj    Blogs.UI.Main.csproj.user    Global.asax.cs    packages.config    Web.config    Web.Debug.config    Web.Release.config

syncUrl是上面的处理url,localFolder是本地目录,remoteFolder是远程目录。


具体的处理逻辑,有点复杂

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
using System.Collections;
using System.Net;
using System.Threading;
using System.Diagnostics;
using System.Xml.Serialization;
using System.Xml;
using System.Text.RegularExpressions;


namespace DirSync
{
    public partial class SyncForm : Form
    {

        public SyncForm()
        {
            InitializeComponent();
        }

        public delegate void Action();

        public void SafeCall(Control ctrl, Action callback)
        {
            if (ctrl.InvokeRequired)
                ctrl.Invoke(callback);
            else
                callback();
        }

        void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
        {
            MessageBox.Show("同步程序发生错误,暂时无法运行" + e.Exception.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
            this.btnSync.Text = "开始同步";
        }

        public class FileMessage
        {
            public long Size { get; set; }

            public string Sha1 { get; set; }

            private string _fileName;
            public string FileName
            {
                get
                {
                    return (_fileName == null ? null : _fileName.TrimStart('\\').TrimStart('/'));
                }
                set
                {
                    _fileName = value;
                }
            }
        }

        public class UploadMessage
        {
            public long Size { get; set; }

            public string Sha1 { get; set; }

            public string LocalPath { get; set; }

            public string UploadUrl { get; set; }

            public string RemoteFileName { get; set; }

            public bool IsDelete { get; set; }
        }

        private FileMessage GetFileMessage(List list, string fileName)
        {
            foreach (FileMessage fm in list)
            {
                if (fm.FileName.Equals(fileName, StringComparison.CurrentCultureIgnoreCase))
                {
                    return fm;
                }
            }

            return null;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Control.CheckForIllegalCrossThreadCalls = false;
            XmlDocument doc = new XmlDocument();
            doc.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "sync.config.xml"));
            List list = new List();
            foreach (XmlNode node in doc.SelectNodes("/sync/app"))
            {
                list.Add(node.Attributes["name"].InnerText);
            }
            this.comboBox1.DataSource = list;
        }

        private void LoadFile(List list, string p, string baseDir)
        {
            foreach (string file in Directory.GetFiles(p))
            {
                if (file.Contains("sync.config.xml"))
                {
                    continue;
                }

                list.Add(GetFileInfo(file, baseDir));
            }

            foreach (string folder in Directory.GetDirectories(p))
            {
                LoadFile(list, folder, baseDir);
            }
        }

        ///         /// 获取文件信息
        ///         private FileMessage GetFileInfo(String filePath, string baseDir)
        {
            FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            long lenth = fs.Length;
            System.Security.Cryptography.HashAlgorithm algorithm = System.Security.Cryptography.SHA1.Create();
            String result = BitConverter.ToString(algorithm.ComputeHash(fs)).Replace("-", "");
            fs.Close();
            return new FileMessage { Size = lenth, Sha1 = result, FileName = filePath.Replace(baseDir, "") };
        }

        #region 主要逻辑
        //是否在忽略列表里
        private bool IsIngore(List ignoreList, string localfile)
        {
            foreach (string s in ignoreList)
            {
                Match m=Regex.Match(s,"\\*(\\..+)");
                if(m.Success)
                {
                    if(localfile.EndsWith(m.Groups[1].Value,StringComparison.CurrentCultureIgnoreCase))
                    {
                        return true;
                    }
                }

                if (localfile.TrimStart('\\').ToLower().StartsWith(s.TrimStart('\\').ToLower()))
                {
                    return true;
                }
            }

            return false;
        }
        private List GetSyncList(string updateUrl)
        {
            WebClient c = new WebClient();
            XmlSerializer sz = new XmlSerializer(typeof(List));
            //服务器文件信息
            List serverList = (List)sz.Deserialize(c.OpenRead(updateUrl));
            //本地文件信息
            List localList = new List();
            LoadFile(localList, txtLocal.Text.Trim(), txtLocal.Text.Trim());

            //需要同步的文件列表
            List syncList = new List();

            //忽略列表
            List ignoreList = new List();
            XmlDocument doc = new XmlDocument();
            doc.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "sync.config.xml"));
            XmlNode appnode = doc.SelectSingleNode("/sync/app[@name='" + this.comboBox1.Text + "']");
            foreach (XmlNode node in appnode.SelectNodes("ignore"))
            {
                ignoreList.Add(node.InnerText.TrimEnd('\\'));
            }

            foreach (FileMessage fm in localList)
            {
                if (IsIngore(ignoreList, fm.FileName))
                {
                    continue;
                }
                FileMessage srcFm = GetFileMessage(serverList, fm.FileName);
                //如果服务器上不存在该文件  则需同步
                if (srcFm == null)
                {
                    IDictionary dic = new Dictionary();
                    dic.Add("time", DateTime.Now.ToString("yyyyMMddHHmmss"));
                    dic.Add("ak", System.Configuration.ConfigurationManager.AppSettings["appKey"]);
                    dic.Add("remotePath", System.Web.HttpUtility.UrlEncode(Path.Combine(txtRemote.Text.Trim(), fm.FileName)));
                    dic.Add("action", "upload");
                    string par = "";
                    string sign = HttpHelper.Sign(dic, System.Configuration.ConfigurationManager.AppSettings["secretKey"], out par, "sign");
                    string url = txtURL.Text + "?" + par + "&sign=" + sign;
                    syncList.Add(new UploadMessage
                    {
                        UploadUrl = url,
                        LocalPath = Path.Combine(txtLocal.Text.Trim(), fm.FileName),
                        RemoteFileName = Path.Combine(txtRemote.Text.Trim(), fm.FileName),
                        Size = fm.Size
                    });
                }
                else
                {
                    //如果不等于服务器上的sha1  则需要同步
                    if (!srcFm.Sha1.Equals(fm.Sha1, StringComparison.CurrentCultureIgnoreCase))
                    {
                        IDictionary dic = new Dictionary();
                        dic.Add("time", DateTime.Now.ToString("yyyyMMddHHmmss"));
                        dic.Add("ak", System.Configuration.ConfigurationManager.AppSettings["appKey"]);
                        dic.Add("remotePath", Path.Combine(txtRemote.Text.Trim(), fm.FileName));
                        dic.Add("action", "upload");
                        string par = "";
                        string sign = HttpHelper.Sign(dic, System.Configuration.ConfigurationManager.AppSettings["secretKey"], out par, "sign");
                        string url = txtURL.Text + "?" + par + "&sign=" + sign;

                        syncList.Add(new UploadMessage
                        {
                            UploadUrl = url,
                            LocalPath = Path.Combine(txtLocal.Text.Trim(), fm.FileName),
                            RemoteFileName = Path.Combine(txtRemote.Text.Trim(), fm.FileName),
                            Size = fm.Size
                        });
                    }
                }
            }

            foreach (FileMessage fm in serverList)
            {
                if (IsIngore(ignoreList, fm.FileName))
                {
                    continue;
                }
                FileMessage destFm = GetFileMessage(localList, fm.FileName);
                //如果客户端不存在该文件  则需删除服务器上的     
                if (destFm == null)
                {
                    IDictionary dic = new Dictionary();
                    dic.Add("time", DateTime.Now.ToString("yyyyMMddHHmmss"));
                    dic.Add("ak", System.Configuration.ConfigurationManager.AppSettings["appKey"]);
                    dic.Add("remotePath", Path.Combine(txtRemote.Text.Trim(), fm.FileName));
                    dic.Add("action", "delete");
                    string par = "";
                    string sign = HttpHelper.Sign(dic, System.Configuration.ConfigurationManager.AppSettings["secretKey"], out par, "sign");
                    string url = txtURL.Text + "?" + par + "&sign=" + sign;

                    syncList.Add(new UploadMessage
                    {
                        UploadUrl = url,
                        RemoteFileName = Path.Combine(txtRemote.Text.Trim(), fm.FileName),
                        IsDelete = true
                    });
                }
            }

            return syncList;
        }

        private void Start()
        {
            try
            {
                this.txtStatus.Text = "正在获取同步信息...";

                IDictionary dic = new Dictionary();

                dic.Add("time", DateTime.Now.ToString("yyyyMMddHHmmss"));
                dic.Add("ak", System.Configuration.ConfigurationManager.AppSettings["appKey"]);
                dic.Add("dir", txtRemote.Text.Trim());
                string par = "";
                string sign = HttpHelper.Sign(dic, System.Configuration.ConfigurationManager.AppSettings["secretKey"], out par, "sign");
                string updateUrl = txtURL.Text + "?" + par + "&sign=" + sign;
                //获取需要同步的文件
                List syncList = GetSyncList(updateUrl);
                if (syncList.Count == 0)
                {
                    MessageBox.Show("已经同步好了。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    this.btnSync.Text = "开始同步";
                    this.txtStatus.Text = "就绪";
                    return;
                }
                //计算总同步大小
                long total = 0;
                foreach (UploadMessage d in syncList)
                {
                    total += d.Size;
                }

                this.SafeCall(this.progressBar1, () =>
                {
                    this.progressBar1.Maximum = (int)total;
                });

                foreach (UploadMessage mess in syncList)
                {
                    try
                    {
                        if (mess.IsDelete)
                        {
                            if (checkBox1.Checked)
                            {
                                this.txtStatus.Text = "正在删除服务器文件" + mess.RemoteFileName;
                                WebClient wc = new WebClient();
                                string d = wc.DownloadString(mess.UploadUrl);
                                this.SafeCall(this.listBox1, () =>
                                {
                                    this.listBox1.Items.Add(d);
                                });
                                continue;
                            }
                        }
                        else
                        {
                            string localFile = mess.LocalPath;
                            this.txtStatus.Text = "正在上传" + localFile;
                            string url = mess.UploadUrl;

                            //WebClient wb = new WebClient();
                            //byte[] b = wb.UploadFile(url, localFile);
                            //string result = System.Text.Encoding.UTF8.GetString(b);
                            //this.SafeCall(this.listBox1, () =>
                            //{
                            //    this.listBox1.Items.Add(result);
                            //});

                            HttpUploadHelper up = new HttpUploadHelper();
                            up.UploadStenEvent += up_UploadStenEvent;
                            up.CompleteEvent += up_CompleteEvent;
                            up.Upload_Request(url, localFile, mess.RemoteFileName);
                        }
                    }
                    catch(Exception ex)
                    {
                        this.SafeCall(this.listBox1, () =>
                        {
                            this.listBox1.Items.Add(ex.Message);
                        });

                        this.SafeCall(this.txtErrorCount, () =>
                        {
                            this.txtErrorCount.Text = (Convert.ToInt32(txtErrorCount.Text)+1).ToString();
                        });
                    }
                }

                this.txtStatus.Text = "同步完成";
                this.btnSync.Text = "开始同步";
            }
            catch (Exception ex)
            {
                this.txtStatus.Text = "同步出错:" + ex.Message;
                this.btnSync.Text = "开始同步";
            }
        }
        #endregion

        void up_CompleteEvent(string result)
        {
            this.SafeCall(this.listBox1, () =>
            {
                this.listBox1.Items.Add(result);
            });
        }

        void up_UploadStenEvent(UploadEvent e)
        {
            this.SafeCall(this.progressBar1, () =>
            {
                this.progressBar1.Value = this.progressBar1.Value += e.CurrentSize;
                if (this.progressBar1.Value == this.progressBar1.Maximum)
                {
                    this.statusStrip1.Text = "同步完成";
                    this.btnSync.Text = "开始同步";
                }
            });

            this.SafeCall(this.txtTotal, () =>
            {
                this.txtTotal.Text = this.progressBar1.Value + "/" + this.progressBar1.Maximum;
            });
        }


        private void btnSyc_Click(object sender, EventArgs e)
        {
            Thread th = null;
            this.txtErrorCount.Text = "0";
            this.progressBar1.Value = 0;
            this.listBox1.Items.Clear();
            if (this.btnSync.Text == "开始同步")
            {
                if (txtURL.Text.Trim() == "")
                {
                    MessageBox.Show("请输入远程URL", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                if (txtLocal.Text.Trim() == "")
                {
                    MessageBox.Show("请输入本地目录", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                if (txtRemote.Text.Trim() == "")
                {
                    MessageBox.Show("请输入远程目录", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }
                this.progressBar1.Value = 0;
                this.btnSync.Text = "取消";
                th = new Thread(new ThreadStart(Start));
                th.Start();
            }
            else
            {
                if (this.btnSync.Text == "取消")
                {
                    th.Abort();
                    this.btnSync.Text = "开始同步";
                }
            }
        }

        protected override void WndProc(ref Message SystemMessage)
        {
            switch (SystemMessage.Msg)
            {
                case 0x0112:
                    if (((int)SystemMessage.WParam) == 61536)
                    {
                        // this.Owner.Close();
                        Application.ExitThread();
                    }
                    else
                    {
                        base.WndProc(ref SystemMessage);
                    }
                    break;
                default:
                    base.WndProc(ref SystemMessage);
                    break;
            }
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "sync.config.xml"));
            XmlNode appnode = doc.SelectSingleNode("/sync/app[@name='" + this.comboBox1.Text + "']");
            this.txtURL.Text = appnode.SelectSingleNode("syncUrl").InnerText;
            this.txtLocal.Text = appnode.SelectSingleNode("localFolder").InnerText;
            this.txtRemote.Text = appnode.SelectSingleNode("remoteFolder").InnerText;
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            this.listBox1.Items.Clear();
        }
    }
}




上一篇:多线程域名查询工具

下一篇:个人代码全部开源


0 评论

查看所有评论

给个评论吧