对于软件来说,启用自动更新是非常必要的。

根据软件的应用场景,我们可以设计不同的更新模型。

目前,IMES框架运行在.Net framework 4.0下面,使用的Win系统版本在Win7,域内管控,平时业务调整也不是很频繁。

所以,我的更新很粗放,就是删除旧文件,拷贝新文件:

1、更新文件放置在文件服务器一个公共目录下:\\SV001\Public\Update ;

2、仅在用户登录时检测更新(或者在系统界面点击“更新”按钮手动更新);

3、根据业务变更可以指定更新某一个文件、一个文件夹、或者所有文件;

4、软件是否要更新,简单的由一个文本文件的最后修改时间来判断;

5、可以保留某些本地生成和下载的配置/文件;

完整代码:

    public partial class Fm19Update : Form
{
/// <summary>
/// 这个程序会单独运行,因此不要引用其他DLL
/// </summary>
public Fm19Update()
{
InitializeComponent();
FormBorderStyle = FormBorderStyle.None;
StartPosition = FormStartPosition.CenterScreen;
} #region 声明
[DllImport("user32.dll")]
public static extern bool ReleaseCapture();//拖动无窗体的控件
[DllImport("user32.dll")]
public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
public const int WM_SYSCOMMAND = 0x0112;
public const int SC_MOVE = 0xF010;
public const int HTCAPTION = 0x0002; /// <summary>
/// 更新程序来源路径(更新文件一般放在所有客户端可访问的网络服务器上)
/// </summary>
private string UpdatePath = string.Empty; /// <summary>
/// 更新内容(可使用\\,*.*,0;dir1\*.dll,0分割要更新的区块,所有文件全部更新使用[\\,*.*,1]
/// </summary>
private string UpdateContent = string.Empty; /// <summary>
/// 更新内容清单,由UpdateContent转换而来.
/// </summary>
private string[] UpdateList; /// <summary>
/// 要更新的文件个数,由各更新区域加总
/// </summary>
private static int UpdateFilesCount = ;
#endregion /// <summary>
/// 延时函数, 如使用Thread.Sleep()会停止响应
/// </summary>
/// <param name="delayTime"></param>
/// <returns></returns>
private static bool Delay(int delayTime)
{
DateTime now = DateTime.Now;
int s;
do
{
TimeSpan spand = DateTime.Now - now;
s = spand.Seconds;
Application.DoEvents();
}
while (s < delayTime);
return true;
} /// <summary>
/// 显示当前作业状态
/// </summary>
/// <param name="tStatus"></param>
private void SetUpdateStatus(string tStatus)
{
LabCurStatus.Text = tStatus;
Refresh();
} private void CmdQuit_Click(object sender, EventArgs e)
{
Application.Exit(); } private void Fm19Update_MouseDown(object sender, MouseEventArgs e)
{
ReleaseCapture(); //调用移动无窗体控件函数
SendMessage(Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, );
} private void Fm19Update_Shown(object sender, EventArgs e)
{
SetUpdateStatus("Checking progress status...");
Refresh();
Delay(); //这里的检测其实是一个假象,仅仅延时了2秒,以等待主程序结束运行。
SetUpdateStatus("Checking progress status...Finished");
Refresh();
DoUpdate(); //开始进行更新
}
#region 更新作业区域
/// <summary>
/// 当本地更新文件的最后修改时间与更新路径下的更新文件不同,则认为需要更新。
/// </summary>
private void DoUpdate()
{
#region 判断来源与本地文件夹状态
string localPath = AppDomain.CurrentDomain.BaseDirectory;
localPath = localPath.Substring(, localPath.Length - );
//从本地更新历史文件读取远程更新路径
string[] allLines = File.ReadAllLines(localPath + @"\UpdateLog.txt");
string remotePath = string.Empty;
foreach (string line in allLines)
{
if (line.IndexOf("UpdatePath=") >= )
{
remotePath = line.Substring(line.IndexOf("UpdatePath=") + ).Trim();
break;
}
}
if (localPath.ToUpper() == remotePath.ToUpper())
{
MessageBox.Show("更新路径不能与来源路径相同!", "警告...", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
FileInfo fiUL = new FileInfo(localPath + @"\UpdateLog.txt");
FileInfo fiUR = new FileInfo(remotePath + @"\UpdateLog.txt");
if (fiUL.LastWriteTime == fiUR.LastWriteTime)
{
SetUpdateStatus("系统没有更新,操作终止!");
return;
}
SetUpdateStatus("正在连接到目标文件夹......");
if (!Directory.Exists(remotePath))
{
SetUpdateStatus("目标文件夹:" + remotePath + " 不存在,请联络系统管理员!");
return;
}
SetUpdateStatus("正在连接到目标文件夹......成功!"); //从远程读取本次更新内容
string[] allLinesRmt = File.ReadAllLines(remotePath + @"\UpdateLog.txt");
foreach (string line in allLinesRmt)
{
if (line.IndexOf("UpdateContent=") >= )
{
UpdateContent = line.Substring(line.IndexOf("UpdateContent=") + );
break;
}
}
#endregion #region 计算文件数量并设置百分比
if (!string.IsNullOrEmpty(UpdateContent))
{
UpdateList = UpdateContent.Split(';');
}
else
{
UpdateList = (@"\\,*.*,1").Split(';'); //更新内容设置不正确,则设置为更新所有文件.
} UpdateFilesCount = ;
foreach (string ul in UpdateList)
{
string[] updContent = ul.Split(',');
string updDirPath = string.Empty;
string updFilesPattern = "*.*";
int updWithSubDir = ;
if (updContent.Length > )
{
updDirPath = updContent[];
updDirPath = remotePath + @"\" + updDirPath.Replace("\\", "");
}
if (updContent.Length > )
{
updFilesPattern = updContent[];
}
if (updContent.Length > )
{
updWithSubDir = Convert.ToInt16(updContent[]);
}
bool withSubDir = updWithSubDir > ; if (!string.IsNullOrEmpty(updContent[].Trim()))
{
UpdateFilesCount += CountAllFiles(updDirPath, updFilesPattern, withSubDir);
}
} //所有更新进度根据更新区域切割百分比
ProgMain.Value = ;
ProgMain.Step = ;
ProgMain.Maximum = UpdateFilesCount + ;
for (int i = ; i < ; i++)
{
ProgMain.PerformStep();
LabProgStep.Text = (ProgMain.Value * / ProgMain.Maximum).ToString() + "%";
}
#endregion #region 开始删除和复制文件(会删除目录下的所有文件,再重新复制,这样可以清除掉某些过期的文件) foreach (string ul in UpdateList)
{
string[] updContent = ul.Split(',');
string localUpdDirPath = string.Empty;
string remoteUpdDirPath = string.Empty; string updFilesPattern = "*.*";
int updWithSubDir = ;
if (updContent.Length > )
{
localUpdDirPath = localPath + @"\" + updContent[].Replace("\\", "");
remoteUpdDirPath = remotePath + @"\" + updContent[].Replace("\\", "");
}
if (updContent.Length > )
{
updFilesPattern = updContent[];
}
if (updContent.Length > )
{
updWithSubDir = Convert.ToInt16(updContent[]);
}
bool withSubDir = updWithSubDir > ; SetUpdateStatus("正在更新" + (updContent[] == @"\\" ? "根目录" : updContent[]) + "下的文件......");
if (!string.IsNullOrEmpty(updContent[].Trim()))
{
CopyFilesTo(remoteUpdDirPath, updFilesPattern, localUpdDirPath, withSubDir);
}
}
#endregion //更新完成后重新启动主程序
string mainEXE = AppDomain.CurrentDomain.BaseDirectory + @"B20.exe";
Process.Start(mainEXE);
Application.Exit();
} /// <summary>
/// 统计符合筛选条件的所有文件数量。(已排除A19.exe和LocalFile目录)
/// </summary>
/// <param name="fullPath">完整目录名称</param>
/// <param name="sPattern">筛选条件(如*.xls)</param>
/// <param name="withSubDir">是否包含子目录</param>
/// <returns></returns>
private int CountAllFiles(string fullPath, string sPattern, bool withSubDir = false)
{
SearchOption searchOption = withSubDir ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var files = Directory.EnumerateFiles(fullPath, sPattern, searchOption)
.Where(s => !s.Contains("A19.exe") && !s.Contains("Custom.cfg"));
return files.Count();
} /// <summary>
/// 从A文件夹拷贝文件到B文件夹下面
/// </summary>
/// <param name="remoteSourcePath">文件所在目录(@"C:\A\A")</param>
/// <param name="localSavePath">保存的目标目录(@"C:\B\B")</param>
/// <returns>返回:true-拷贝成功;false:拷贝失败</returns>
public bool CopyFilesTo(string remoteSourcePath, string sPattern, string localSavePath, bool withSubDir = false)
{
try
{
SearchOption searchOption = withSubDir ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var files = Directory.EnumerateFiles(remoteSourcePath, sPattern, searchOption)
.Where(s => !s.Contains("A19.exe")); if (withSubDir)
{
//包含子目录更新,先删除当前目录下的所有文件,再重新复制本目录,这样可以清除某些过期文件
SetUpdateStatus("正在清除旧文件......");
DelectDir(localSavePath, withSubDir); //如果包含子目录 ,则先根据远端的目录结构全部检查/创建好.
var dirs = Directory.EnumerateDirectories(remoteSourcePath, "*.*", searchOption);
foreach (string dir in dirs)
{
string locTagDir = dir.Replace(remoteSourcePath, localSavePath);
if (!Directory.Exists(locTagDir))
{
Directory.CreateDirectory(locTagDir);
}
}
}
if (files.Count() > )
{
foreach (string fileName in files)
{
//采用覆盖模式,但要保留一些本地自定义文件
if (fileName == (remoteSourcePath + @"Config\Custom.cfg"))
{
if (!File.Exists(fileName.Replace(remoteSourcePath, localSavePath)))
{
File.Copy(fileName, fileName.Replace(remoteSourcePath, localSavePath), true);
}
}
else
{
File.Copy(fileName, fileName.Replace(remoteSourcePath, localSavePath), true);
}
ProgMain.PerformStep();
LabProgStep.Text = (ProgMain.Value * / ProgMain.Maximum).ToString() + "%";
}
}
LabCurStatus.Text = "正在复制文件......完成!";
Refresh(); }
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
return true;
} /// <summary>
/// 删除本目录下除更新程序及LocalFile目录(存储本地生成的文件)以外的所有文件
/// </summary>
/// <param name="tagPath"></param>
public static void DelectDir(string tagPath, bool delSub = false)
{
try
{
DirectoryInfo dir = new DirectoryInfo(tagPath);
FileSystemInfo[] fileinfo = dir.GetFileSystemInfos(); //返回目录中所有文件和子目录
foreach (FileSystemInfo i in fileinfo)
{
if (i is DirectoryInfo) //判断是否文件夹
{
if (delSub)
{
if ((i.FullName != (tagPath + "LocalFiles")) && (i.FullName != (tagPath + "Config")))
{
DirectoryInfo subdir = new DirectoryInfo(i.FullName);
subdir.Delete(true); //删除子目录和文件
}
}
}
else
{
if ((i.Name != "A19.exe") && (i.FullName != tagPath + @"Config\Custom.cfg"))
{
File.Delete(i.FullName); //删除指定文件
}
}
}
}
catch (Exception e)
{
throw e;
}
} #endregion
}

Fm19Update.cs

界面设计:

当然,也可以扩展为从网站下载或者轮循新版本。

C# Winform下一个热插拔的MIS/MRP/ERP框架14(自动更新)的更多相关文章

  1. C# Winform下一个热插拔的MIS/MRP/ERP框架13(窗体基类)

    作为一个ERP数据处理框架,大部分的开发场景都差不多. 理想中,对于通用数据处理,我的步骤如下: 1.为窗体指定数据来源(数据表/查询等): 2.拖入编辑控件,指定绑定字段: 3.结束. 为此,我设计 ...

  2. C# Winform下一个热插拔的MIS/MRP/ERP框架11(启航)

    初学时,有了想法却完全不知道该从何下指,此序列将抛砖引玉,与大家共同学习进步. 一个程序的初始,必然是启动. 我的要求: 1.应用程序保持单例: 2.从配置文件加载一些基础数据进行初始化: 3.显示软 ...

  3. C# Winform下一个热插拔的MIS/MRP/ERP框架(简介)

    Programmer普弱哥们都喜欢玩自己的框架,我也不例外. 理想中,这个框架要易于理解.易于扩展.易于维护:最重要的,易于CODING. 系统是1主体框架+N模组的多个EXE/DLL组成的,在主体框 ...

  4. C# Winform下一个热插拔的MIS/MRP/ERP框架15(窗体基类场景1)

    最基础的窗体基类其实是通过应用场景反推的结构. 以下是场景一: 单表应用,普通的数据,比如单位/颜色/特殊字典等使用者少的,无需过多控制的可以使用一个数据表格来管理. 和Excel表格差不多,批量修改 ...

  5. C# Winform下一个热插拔的MIS/MRP/ERP框架12(数据处理基类)

    作为ERP等数据应用程序,数据库的处理是重中之重. 在框架中,我封装了一个数据库的基类,在每个模组启动或窗体启动过程中,实例化一个基类即可调用CRUD操作(create 添加read读取 update ...

  6. C# Winform下一个热插拔的MIS/MRP/ERP框架(通用控件)

    一直对商业控件不感冒, 结合日常工作, 我写了几个常用控件. 一.下拉框控件(仿Access下拉框:F4下拉,自动输入,支持单/多列显示),可在Datagridview中使用. 1.常规: 2.Dat ...

  7. C# Winform下一个热插拔的MIS/MRP/ERP框架(多语言方案)

    个别时候,我们需要一种多语种切换方案. 我的方案是这样的: 1.使用文本文本存储多语言元素,应用程序启动时加载到内存表中: 2.应用程序启动时从配置文件加载语种定义: 3.所有窗体继承自一个Base基 ...

  8. C# Winform下一个热插拔的MIS/MRP/ERP框架16(窗体基类场景2)

    如果没有特别需求,和场景1一样只变更表名,主键字段,检测字段等名称,不需要写其它代码了. * 清单列表+单笔编辑/保存,适用于大多数基础资料管理以及简单的单据资料录入(当然,排版是要改一改的): * ...

  9. winform设计一个登录界面和修改密码的界面-自动切换窗体(问题[已解] 望一起讨论)(技术改变世界-cnblog)

    http://www.cnblogs.com/IAmBetter/archive/2012/01/14/2322156.html winform设计一个登录界面和修改密码的界面-自动切换窗体(问题[已 ...

随机推荐

  1. Setup Apache2 in Debian 9 and enable two ports for two sites

    root@debian:~# apt-get install apache2 root@debian:~# cd /etc/apache2/ root@debian:/etc/apache2# ls ...

  2. Scala基础:类和构造器

    类 package com.zy.scala.cls /** * 在 Scala 中,类并不用声明为 public 类型的. * Scala 源文件中可以包含多个类,所有这些类都具有共有可见性. */ ...

  3. go_常量与枚举

    package main import ( "fmt" "math" ) //常量的数值可以作为各种类型使用 func consts(){ const file ...

  4. SourceTree 3.0.8 跳过登陆注册

    3.0.8普通用户版account.json跳过登陆注册方法已失效,请安装企业版 https://www.sourcetreeapp.com/enterprise 企业版默认安装在 %programf ...

  5. AJAX和DHTML

    DHTML: (动态的html)本身不是一门新语言,而是一门新技术,包含以下 html . css . dom . js AJAX  :  也是一门新技术包含    html . css.  dom ...

  6. osgQt支持触摸屏

    1. osgQt的构造函数添加:setAttribute(Qt::WA_AcceptTouchEvents);//wyh 2. event()修改,支持触摸时间 bool GLWidget::even ...

  7. ceph之osd

    一.删除osd ceph osd out 1ceph osd down 1systemctl stop ceph-osd@1ceph osd crush remove osd.1ceph osd rm ...

  8. Java SimpleDateFormat用法

      ? Java中怎么才能把日期转换成想要的格式呢,或把字符串转换成一定格式的日期,如把数据库中的日期或时间转换成自己想要的格式,JAVA中提供了SimpleDateFormat类可以实现,以下是Si ...

  9. 编写高质量代码改善C#程序的157个建议——建议70:避免在调用栈较低的位置记录异常

    建议70:避免在调用栈较低的位置记录异常 并不是所有的异常都要被记录到日志,一类情况是异常发生的场景需要被记录,还有一类就是未被捕获的异常.未被捕获的异常通常被视为一个Bug,所以,对于它的记录,应该 ...

  10. Java中的Type

    Type是Java 编程语言中所有类型的公共高级接口(官方解释),也就是Java中所有类型的“爹”:其中,“所有类型”的描述尤为值得关注.它并不是我们平常工作中经常使用的 int.String.Lis ...