共享WinCE6.0 下的一個軟件升級程序


需求場境
廠里搞了幾把PDA掃描槍,用來對下料工序進行掃描確認,本來以為就幾把,直接連到電腦上用ActiveSync復制程序上去就好了,但是剛開發的程序一天要改個好幾次,把槍收回來,裝好再拿回去,一天做個4,5次簡直叫人抓狂了。於是就決定自己整個簡單的更新程序.

基本設計
1.要發布到WinCE上的程序比較簡單,每個文件也比較小(都在500KB以下)所以決定直接讓WebService 返回byte[]類型的文件內容然后在WinCE里寫下文件,
  另外也提供GetFile.ashx頁面,里面使用Response.TransmitFile(path)以供客戶端下載比較大的文件,當然在局域網環境下網速不是問題.


2.版本方面,不打算搞成每個文件都有一個版本,而是一個總版本,只要本地版本號小於服務器端版本號那么就下載服務器上的文件覆蓋本地全部文件,
  服務端與本地升級程序都有個配置文件來記錄當前版本。


3.服務端使用IIS,不使用數據庫,在站點下建立一個目錄,把要發布的軟件copy到目錄下,客戶端升級程序會在其工作目錄創建一個跟服務器目錄結構相同的文件夾結構,並下載各文件夾下的文件。

擴展與加強
當然一個完整的更新程序還有很多地方需要做,比方多版本管理、客戶端有嵌入數據庫時不應該覆蓋,自動生成桌面快捷方式,注冊啟動項目等,但是對我來說,只要能下載就減少我80%工作量了,剩下的直接人肉搞定,作為上了年紀的程序員,盡量避免做折磨前列腺、頸椎、腰椎這些折壽的事情。

代碼部分
升級站點跟掃描的WebService放在一起,分離打包上來太麻煩了,所以就把主要文件copy上來,相信你化個10幾分鍾就能把項目建立會來。

1.服務端部分

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;

namespace PDAJob.PDAService.Service
{
    /// <summary>
    /// ServiceAppSync 的摘要說明
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // 若要允許使用 ASP.NET AJAX 從腳本中調用此 Web 服務,請取消對下行的注釋。
    // [System.Web.Script.Services.ScriptService]
    public class ServiceAppSync : System.Web.Services.WebService
    {

        [WebMethod]
        public FileReadResponse GetFile(string path)
        {
            return new AppSyncMgr().GetFile(path);
        }
        [WebMethod]
        public FileReadData GetFileData(string path)
        {
            var r = new AppSyncMgr().GetFile(path);
            var response = new FileReadData() { Code = r.Code, Msg = r.Msg };
            if (r.Code == 0)
            {
               response.DataB64= Convert.ToBase64String(r.Data);
            }
            return response;
        }
        [WebMethod]
        public List<AppSyncItem> GetList()
        {
            return new AppSyncMgr().GetList();
        }
      
        [WebMethod]
        public string GetVersion()
        {
            return new AppSyncMgr().GetVersion();
        }
    }
}

注意:上面說直接返回byte[]給客戶端,但是在wince + vs2005下發現不行,於是直接將byte[] basic64成string后在WinCE上還原。

2.主要業務邏輯文件

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.Configuration;
using System.Collections.Specialized;
using System.Collections;
namespace PDAJob.PDAService.Service
{

    using System.Xml;
    using System.Xml.Serialization;
    using System.IO;

    public class AppSyncMgr
    {
        private List<FileSystemInfo> ExcludeItems = new List<FileSystemInfo>();

        

        private AppSyncConfig _Config;
        public AppSyncConfig Config
        {
            get
            {
                if (_Config == null)
                {
                    _Config = new AppSyncConfig( new FileConfigStore()) ;
                }
                return _Config;
            }
        }
        /// <summary>
        /// 獲取版本號
        /// 客戶端版本只有被服務端版本號小時才同步
        /// </summary>
        /// <returns></returns>
        public string GetVersion()
        {
            if (!Config.Enabled) return "0.0.0";
            return Config.Version;
        }
        public FileReadResponse GetFile(string path)
        {
            var root=Config.Path;
            var filename = Path.Combine(root, path);
            var response = new FileReadResponse();
            try
            {
                var bytes = File.ReadAllBytes(filename);
                response.Data = bytes;
            }
            catch (Exception ex)
            {
                response.Code = 1;
                response.Msg = ex.Message;
            }
            return response;
        }
        /// <summary>
        /// 獲取同步列表
        /// </summary>
        /// <returns></returns>
        public List<AppSyncItem> GetList()
        {
            var root = new DirectoryInfo(Config.Path);
            #region 構建排除目錄列表
            Config.Bill.Where(ent => ent.IsExclude).ToList().ForEach(ent =>
            {

                string path = Path.Combine(root.FullName, ent.FileName.Trim(@"/\".ToArray()));
                if (ent.IsDir)
                {
                    ExcludeItems.Add(new DirectoryInfo(path));
                }
                else
                {
                    ExcludeItems.Add(new FileInfo(path));
                }


            });
            #endregion

            
            #region 加載目錄下文件與目錄
            var list = new List<AppSyncItem>();
            ReadList(list, root);
            //去掉前綴目錄
            list.ForEach(ent => {
               ent.FileName= ent.FileName.Remove(0, root.FullName.Length);
            });
            #endregion


            return list;
        }
        /// <summary>
        /// 項目是否在排除列表中
        /// </summary>
        /// <param name="fs"></param>
        /// <returns></returns>
        private bool IsExcludeItem(FileSystemInfo fs)
        {
            foreach (var item in ExcludeItems)
            {
                if (string.Compare(item.FullName, fs.FullName, true) == 0
                    &&
                    fs.GetType() == item.GetType()
                    )
                {

                    return true;

                }
            }
            return false;
        }

        private void ReadList(List<AppSyncItem> list, DirectoryInfo dir)
        {
            
            foreach (var  fs in dir.GetFileSystemInfos())
            {
                if (IsExcludeItem(fs)) continue;

                var item = new AppSyncItem();
                item.FileName = fs.FullName.Replace(@"/",@"\");
                if(fs is FileInfo)
                {
                    item.FileSize = (fs as FileInfo).Length;
                   item.IsDir=false;
                }else{
                   item.IsDir=true;
                   ReadList(list, (fs as DirectoryInfo));

                }
                
                list.Add(item);
                    
            }

        }
    }

    /// <summary>
    /// 讀取一個文件
    /// Code=0表示成功
    /// </summary>
    [Serializable]
    public class FileReadResponse
    {
        public int Code { get; set; }
        public string Msg { get; set; }
        public byte[] Data { get; set; }
    }
    [Serializable]
    public class FileReadData
    {
        public int Code { get; set; }
        public string Msg { get; set; }
        public string DataB64 { get; set; }
    }

    /// <summary>
    /// 同步目錄下的全部文件,
    /// 排除清單文件中的排除項目
    /// </summary>
    public class AppSyncConfig
    {
        public const string AppSync_Path = "AppSync_Path";
        public const string AppSync_Version = "AppSync_Version";
        public const string AppSync_Enabled = "AppSync_Enabled";
        public const string AppSync_BillPath = "AppSync_BillPath";
        public const string AppSync_Mode = "AppSync_Model";

        public ICfgStore Store { get; set; }
        /// <summary>
        /// 需要發布文件存放目錄
        /// </summary>
        public string Path { get;  set; }
        public string Version { get; set; }
        /// <summary>
        /// 是否開啟同步系統
        /// </summary>
        public bool Enabled { get; set; }
        /// <summary>
        /// 同步清單文件路徑
        /// </summary>
        public string BillPath { get; set; }

        public string Mode { get; set; }

        private List<AppSyncItem> _Bill = null;
        public List<AppSyncItem> Bill
        {
            get
            {
                if (_Bill == null)
                {
                    _Bill = ReadBill();
                }
                return _Bill;
            }


        }
       
        /// <summary>
        /// 從配置文件讀取相關設置
        /// </summary>
        public AppSyncConfig(ICfgStore store)
        {
            Store = store;
            Store.LoadConfig(this);
        }

        /// <summary>
        /// 可能返回null
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public  List<AppSyncItem> ReadBill()
        {
            string path = BillPath;

           
            List<AppSyncItem> list = null;
            if (File.Exists(path))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<AppSyncItem>));
                using (var sr = new StreamReader(path))
                {
                    list = serializer.Deserialize(sr) as List<AppSyncItem>;
                    sr.Close();
                }
            }
            if (list == null) list = new List<AppSyncItem>();
            return list;

        }
        public  void SaveBill()
        {
            string path = BillPath;

            List<AppSyncItem> list = this.Bill;

            XmlSerializer serializer = new XmlSerializer(typeof(List<AppSyncItem>));
            using (var sr = new StreamWriter(path))
            {
                serializer.Serialize(sr, list);
                sr.Close();
               
            }
        }
    }

    #region 主配置文件存儲
    public interface ICfgStore
    {
        void LoadConfig(AppSyncConfig cfg);
    }
    /// <summary>
    /// 配置文件加在實現
    /// </summary>
    public class WebConfigStore : ICfgStore
    {


        public void LoadConfig(AppSyncConfig cfg)
        {
            cfg.Path = ConfigurationManager.AppSettings[AppSyncConfig.AppSync_Path];
            cfg.Path = HostingEnvironment.MapPath(cfg.Path);

            cfg.Version = ConfigurationManager.AppSettings[AppSyncConfig.AppSync_Version];
            cfg.Enabled = bool.Parse(ConfigurationManager.AppSettings[AppSyncConfig.AppSync_Enabled]);

            cfg.BillPath = ConfigurationManager.AppSettings[AppSyncConfig.AppSync_BillPath];
            cfg.BillPath = HostingEnvironment.MapPath(cfg.BillPath);
        }
    }
    /// <summary>
    /// 文本文件實現
    /// 一行保存一個配置
    /// 如:AppSync_Path=/SyncDir/Files/
    /// 
    /// </summary>
    public class FileConfigStore : ICfgStore
    {
        public string _FileName = "/AppSyncCfg.txt";
        public string FileName
        {
            get
            {
                return HostingEnvironment.MapPath(_FileName);
            }
        }
        public void LoadConfig(AppSyncConfig cfg)
        {
            var list = ReadFile();

            cfg.Path = GetV(AppSyncConfig.AppSync_Path,list);
            cfg.Path = HostingEnvironment.MapPath(cfg.Path);

            cfg.Version =GetV(AppSyncConfig.AppSync_Version,list);
            cfg.Enabled = bool.Parse(GetV(AppSyncConfig.AppSync_Enabled,list));

            cfg.BillPath = GetV(AppSyncConfig.AppSync_BillPath,list);
            cfg.BillPath = HostingEnvironment.MapPath(cfg.BillPath);
        }
        private string GetV(string key,List<Tuple<string,string>> list)
        {
            foreach (var item in list)
            {
                if (string.Compare(item.Item1, key,true) == 0)
                {
                    return item.Item2;
                }
            }
            throw new Exception(string.Format("未找到Key:{0},對應的配置!",key));
        }
        private List<Tuple<string,string>> ReadFile()
        {
           
            if (!File.Exists(FileName))
            {
                throw new Exception(string.Format("未找到同步配置文件{0}!", _FileName));
            }
            string[] lines = File.ReadAllLines(FileName);

            List<Tuple<string, string>> list = new List<Tuple<string, string>>();
            foreach (string line in lines)
            {
               var paire= line.Split("=".ToArray(), StringSplitOptions.RemoveEmptyEntries);
               if (paire.Length != 2)
               {
                   throw new Exception("配置文件的格式錯誤,需要是每行Name=Value的形式");
               }
               list.Add(new Tuple<string,string>(paire[0].Trim(),paire[1].Trim()));
            }
            return list;
        }

    }
   #endregion
}

注意:
1.本來版本配置文件放在Web.config中的,但是每次改完Web.config后網站就重新加載,搞的第一次都等老久,所以就做了文本配置文件放在根目錄下。
2.服務器端提供一個bill.xml,可以用來配置同步時需要排除的文件或目錄,當然以后可以考慮來控制數據庫文件只在客戶端不存在時下載。

數據結構

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Serialization;
using System.Xml;
namespace PDAJob.PDAService.Service
{
    [Serializable]
    public class AppSyncItem
    {
        /// <summary>
        /// 文件名,包括擴展名
        /// 相對同步文件存放目錄的路徑
        /// 前門不要加"/"
        /// </summary>
        [XmlAttribute]
        public string FileName { get; set; }
        [XmlAttribute]
        public bool IsExclude { get; set; }
        [XmlAttribute]
        public string Version { get; set; }
        /// <summary>
        /// 是否目錄
        /// </summary>
        [XmlAttribute]
        public bool IsDir{get;set;}
        [XmlAttribute]
        public long FileSize { get; set; }

    }
}

文件下載頁面

View Code
namespace PDAJob.PDAService.Service
{
    using System.IO;
    /// <summary>
    /// GetFile 的摘要說明
    /// </summary>
    public class GetFile : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            string file=context.Request["f"];

            if (string.IsNullOrWhiteSpace(file)) return;

            var mgr = new AppSyncMgr();
            var path = Path.Combine(mgr.Config.Path, file);

            context.Response.ContentType = "application/octet-stream";
            context.Response.Expires=-1;
            context.Response.TransmitFile(path);
        }

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

 

客戶端部分
wince上的配置文件管理,貌似上面用不了app.config,所以只能自己寫個了

View Code
using System;
using System.Collections.Generic;
using System.Text;



namespace AppSyncCEClient
{
    using System.Xml;
    using System.IO;
    using System.Xml.Serialization;
    using System.Reflection;
    public class ConfigMgr
    {
        public static readonly string _Path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
        private static string _FileName = "AppSynCfg.xml";

        private static string FileName
        {
            get
            {
                return string.Format(@"{0}\{1}", _Path, _FileName);
            }
        }

        private static List<Item> _Items = null;
        private static List<Item> Items
        {
            get
            {
                if (_Items == null)
                {
                    lock (typeof(string))
                    {
                        if (_Items == null)
                        {
                            ReadConfig();

                        }
                    }
                }
                return _Items;

            }
        }
        private static void ReadConfig()
        {
            if (!File.Exists(FileName))
            {
                _Items = new List<Item>();
                return;
            }

            using (StreamReader sr = new StreamReader(FileName))
            {

                XmlSerializer serializer = new XmlSerializer(typeof(List<Item>));
                List<Item> list = serializer.Deserialize(sr) as List<Item>;
                if (list == null)
                {
                    list = new List<Item>();
                }
                _Items = list;
                sr.Close();

            }
        }
        public static void SaveConfig()
        {
            string path = FileName;

            List<Item> list = Items;

            XmlSerializer serializer = new XmlSerializer(typeof(List<Item>));
            using (StreamWriter sr = new StreamWriter(path))
            {
                serializer.Serialize(sr, list);
                sr.Close();

            }
        }
        public static string GetSetting(string key, string defValue)
        {

            foreach (Item item in Items)
            {
                if (string.Compare(item.Key, key, true) == 0)
                {
                    return item.Value;
                }
            }
            return defValue;
        }
        public static void SetSetting(string key, string value)
        {
            bool isFind = false;
            foreach (Item item in Items)
            {
                if (string.Compare(item.Key, key, true) == 0)
                {
                   item.Value=value;
                   isFind = true;
                   break;
                  
                }
            }
            if (!isFind)
            {
                Items.Add(new Item(key, value));
            }
            
        }
        
        public static void CreateConfig()
        {

            List<Item> list = new List<Item>();
            list.Add(new Item("ScanSrvUrl", "http://192.168.1.95:6666/"));
            using (StreamWriter sw = new StreamWriter(FileName))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<Item>));
                serializer.Serialize(sw, list);
                sw.Close();
            };
        }
    }


    
    public class Item
    {
        private string _key;
        private string _value;
        [XmlAttribute]
        public string Key
        {
            get
            {
                return _key;
            }

            set
            {
                _key = value;
            }
        }
        [XmlAttribute]
        public string Value
        {
            get
            {
                return _value;

            }
            set
            {
                _value = value;
            }
        }
        public Item()
        {

        }
        public Item(string key, string value)
        {
            this.Key = key;
            this.Value = value;
        }
    }
}

UI&同步任務類
都放在一個文件里,vs2005,CE下沒BackgroundWorker,線程類還沒狀態屬性,只能肉搏了。

View Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace AppSyncCEClient
{
    using AppSync;
    using System.Reflection;
    using System.Collections;
    using System.IO;
    using System.Net;
    using System.Threading;
    using System.Diagnostics;

    public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();
        }
        private AppSyncTask TaskMgr = null;
        private void frmMain_Load(object sender, EventArgs e)
        {

           

            
            timer1.Enabled = false;
            btnClose.Enabled = false;
            lblTip.Text = "程序啟動中,請稍等...";
            this.Show();
            Application.DoEvents();

       

            ServiceAppSync service = new AppSync.ServiceAppSync();
            service.Url = ConfigMgr.GetSetting("SyncUrl", "");

            string version = ConfigMgr.GetSetting("Version", "0.0");
            TaskMgr = new AppSyncTask(service, version,this);
            TaskMgr.Do();

            timer1.Enabled = true;


            

        }

        private void btnClose_Click(object sender, EventArgs e)
        {
            try
            {
                this.Enabled = false;
                ProcessStartInfo psInfo = new ProcessStartInfo();
                psInfo.FileName = Path.Combine(ConfigMgr._Path, ConfigMgr.GetSetting("StartPath", ""));
                psInfo.UseShellExecute = true;
                
                Process.Start(psInfo);
                Thread.Sleep(1000);
            }
            catch { }
            finally { this.Close(); }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (TaskMgr.IsComplete)
            {
                btnClose.Enabled = true;
                timer1.Enabled = false;
            }
        }

        private void frmMain_Closing(object sender, CancelEventArgs e)
        {
            if (!TaskMgr.IsComplete)
            {
                MessageBox.Show("任務執行期間不允許關閉窗體!");
                e.Cancel = true;
            }
        }
    }


    public delegate void ShowProcessMsgHandle(int progress,string msg);
    /// <summary>
    /// 同步任務
    /// </summary>
    public class AppSyncTask
    {
        Thread WorkerThread = null;
     
        private AppSync.ServiceAppSync Service = null;
        private string LocalVersion = string.Empty;
        public string Version = string.Empty;
        private frmMain UI = null;
        private bool IsRun = false;
        public AppSyncTask( ServiceAppSync service, string curVersion,frmMain form)
        {
         
            Service = service;
            LocalVersion = curVersion;
            UI = form;

        }

        public void Report(int progress, string msg)
        {
            if (UI.InvokeRequired)
            {
                UI.Invoke(new ShowProcessMsgHandle(Report), progress, msg);

            }
            else
            {
                UI.progressBar.Value = progress;
                UI.lblTip.Text = msg;
            }
        }

        public void Do()
        {
           try
            {
               IsRun = true;
               WorkerThread = new Thread(InternalDo);
               WorkerThread.Start();
               Thread.Sleep(100);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

        }
        public bool IsComplete
        {
            get
            {
                return !IsRun;
            }
        }
        private void InternalDo()
        {
            //計數器
            int success = 0;
            int error = 0;
            int b = 0;
            int t = 0;

            try
            {
                #region
                Report(0, "檢測程序是否有可用更新....");
                string version = Service.GetVersion();
                Version = version;
                if (string.Compare(LocalVersion, version, true) >= 0)
                {
                    Report(100, "當前版本已經是最新版本");
                    IsRun = false;
                    return;
                }
                Report(5, "獲取更新列表");
                List<AppSyncItem> list = new List<AppSyncItem>(Service.GetList());

                string msg = string.Format("有{0}個文件需要更新", list.Count);
                Report(10, msg);

                //創建本地目錄
                string root = ConfigMgr._Path;

                List<AppSyncItem> dirList = list.FindAll(FilterDir);
                dirList.Sort(CompareDir);
                foreach (AppSyncItem dir in dirList)
                {
                    string path = Path.Combine(root, dir.FileName);
                    if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                }



                List<AppSyncItem> downList = list.FindAll(FilterFile);
                //更新文件
                 success = 0;
                 error = 0;
                 b = 90;
                 t = downList.Count;


                #region  遍歷下載文件
                foreach (AppSyncItem file in downList)
                {
                    try
                    {
                        string filename = Path.Combine(root, file.FileName);
                        //根據文件大小使用不同的下載方式
                        FileReadData response = Service.GetFileData(file.FileName);


                        if (response.Code == 0)
                        {
                            byte[] data = Convert.FromBase64String(response.DataB64);
                            using (FileStream fs = File.OpenWrite(filename))
                            {
                                fs.Position = 0;
                                fs.Write(data, 0, data.Length);
                                fs.Flush();
                                fs.Close();
                            }
                            success++;

                            msg = string.Format("{0}", file.FileName.Trim());
                            Report(funcPercent(success, error, t, b), msg);
                        }
                        else
                        {
                            error++;
                            msg = string.Format("下載{0}錯誤,{1}", file.FileName, response.Msg);
                            Report(funcPercent(success, error, t, b), msg);
                        }

                    }
                    catch (System.Net.WebException)
                    {
                        error++;
                        throw;
                    }
                    catch (Exception ex)
                    {
                        error++;
                        msg = string.Format(ex.Message, file.FileName);
                        Report(funcPercent(success, error, t, b), msg);
                        continue;
                    }



                }
                #endregion

                msg = string.Format("更新結束,成功{0},失敗{1}.", success, error);
                Report(100, msg);
                if (error == 0)
                {
                    ConfigMgr.SetSetting("Version", Version);
                    ConfigMgr.SaveConfig();
                }
                Thread.Sleep(100);
                IsRun = false;
                #endregion
            }
            catch (Exception ex)
            {
                Report(funcPercent(success, error, t, b), ex.Message);
                IsRun = false;
                
            }
          

        }
        #region 委托
        /// <summary>
        /// 計算完成百分比
        /// </summary>
        /// <param name="s">成功數</param>
        /// <param name="e">失敗數</param>
        /// <param name="t">總數</param>
        /// <param name="b">基准百分比</param>
        /// <returns></returns>
        private int funcPercent(int s, int e, int t, int b)
        {
            if (t == 0) return 0;
            return (int)((decimal)(e + s) * b / (decimal)t);
        }
        /// <summary>
        /// 過濾目錄
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private bool FilterDir(AppSyncItem item)
        {
            return item.IsDir;
        }
        private bool FilterFile(AppSyncItem item)
        {
            return !item.IsDir;
        }
        private int CompareDir(AppSyncItem x, AppSyncItem y)
        {
            return x.FileName.Length - y.FileName.Length;
        }
        #endregion
    }
    public class ReportInfo
    {
        public string Msg = string.Empty;
    }


}

另外關於vs2005 + wince 的一些注意,可以看下這里http://www.cnblogs.com/wdfrog/archive/2012/12/11/2812749.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM