C# 自动更新(基于FTP)

news2024/12/25 9:56:27

效果

启动软件后,会自动读取所有的 FTP 服务器文件,然后读取本地需要更新的目录,进行匹配,将 FTP 服务器的文件同步到本地

Winform 界面

一、前言

在去年,我写了一个 C# 版本的自动更新,这个是根据配置文件 + 网站文件等组成的框架,以实现本地文件的新增、替换和删除,虽然实现了自动更新的功能,但用起来过于复杂,代码量也比较大,改起来困难,后面我就想能不能弄一个 FTP 服务器进行版本的更新。平时客户端版本的更新,一般就两个需求,1.将服务器端最新的文件同步到本地,2.版本回退,如果当前版本有bug,可以随意的切换想要的版本号,这个功能在 FTP 服务器实现起来也比较简单,在 FTP 服务器里新建一个对应版本的文件夹,把对应版本的文件放进去就好了,想切换那个版本,就把 FTP 链接地址指向这个文件夹,然后同步到本地就好了,知道了这个原理,那么就来实现吧。

二、功能的实现

新建一个 winform 项目,界面如下

这几个控件分别是文件名,文件下载的进度,下载进度的百分比,具体信息可以在源码中查看

form1 代码

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace update
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        #region 字段

        /// <summary>
        /// 需要和FTP服务器对比的本地路径
        /// </summary>
        private string TargetPath = string.Empty;

        /// <summary>
        /// FTP文件夹列表  
        /// </summary>
        private List<string> FTPDirectoryList = new List<string>();
        /// <summary>
        /// FTP文件列表 
        /// </summary>
        private List<FileInfo> FTPFileList = new List<FileInfo>();

        /// <summary>
        /// 本地文件夹列表
        /// </summary>
        private List<string> LocalDirectorysList = new List<string>();
        /// <summary>
        /// 本地文件列表
        /// </summary>
        private List<FileInfo> LocalFilesList = new List<FileInfo>();
        /// <summary>
        /// 本地文件的黑名单(不参与到更新)
        /// </summary>
        private List<string> LocalFileBlacklist = new List<string>();

        /// <summary>
        /// ftp 和本地匹配结果,需要处理的数据
        /// </summary>
        private UpdateResultInfo UpdateResultData = null;

        //读取本地文件完成
        private bool ReadLocalEnd = false;
        //读取ftp文件完成
        private bool ReadFTPEnd = false;

        #endregion

        private void Form1_Load(object sender, EventArgs e)
        {
            TargetPath = Application.StartupPath;
            FTPManager.DownloadProgressAction = DownProgressUpdate;

            //添加黑名单
            AddBlacklist();
            //读取配置文件
            ReadConfiguration();

            Start();
        }

        private async void Start()
        {
            //刚启动就读取,会导致界面无法显示
            await Task.Delay(500);

            //读取 FTP 所有的文件
            ReadFTPFile();
            //读取本地文件
            ReadLocalFile();
        }

        /// <summary>
        /// 添加黑名单
        /// </summary>
        private void AddBlacklist()
        {
            LocalFileBlacklist.Add("update.exe");
            LocalFileBlacklist.Add("update.exe.config");
            LocalFileBlacklist.Add("update.pdb");
        }

        /// <summary>
        /// 显示下载进度
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="totalBytes"></param>
        /// <param name="totalDownloadBytes"></param>
        /// <param name="percent"></param>
        public void DownProgressUpdate(string fileName, double totalBytes, double totalDownloadBytes, int percent)
        {
            //Console.WriteLine("文件名:{0},总进度:{1},下载进度:{2},百分比:{3}", fileName, totalBytes, totalDownloadBytes, percent);

            FormControlExtensions.InvokeIfRequired(this, () =>
            {
                Label_FileName.Text = fileName;
                Label_Speed.Text = string.Format("{0} / {1}", GetSize(totalBytes), GetSize(totalDownloadBytes));
                Label_Percentage.Text = string.Format("{0}%", percent);
                ProgressBar_DownProgress.Value = percent;
            });
        }

        /// <summary>
        /// 读取 FTP 所有的文件
        /// </summary>
        private void ReadFTPFile()
        {
            FTPDirectoryList.Clear();
            FTPFileList.Clear();

            Console.WriteLine("开始读取 FTP 文件");

            Task.Run(() =>
            {
                Tuple<List<string>, List<FileInfo>> tuple = FTPManager.GetAllFileList();
                FTPDirectoryList = tuple.Item1;
                FTPFileList = tuple.Item2;
                ReadLocalEnd = true;
       
                Console.WriteLine("读取FTP所有的文件完成");

                ReadEnd();
            });
        }

        /// <summary>
        /// 读取本地文件
        /// </summary>
        private void ReadLocalFile()
        {
            LocalDirectorysList.Clear();
            LocalFilesList.Clear();

            Console.WriteLine("开始读取本地文件");

            GetDirectoryFileList(TargetPath);
            ReadFTPEnd = true;
    
            Console.WriteLine("读取本地文件完成");

            ReadEnd();
        }

        /// <summary>
        /// 获取一个文件夹下的所有文件和文件夹
        /// </summary>
        /// <param name="path"></param>
        private void GetDirectoryFileList(string path)
        {
            DirectoryInfo directory = new DirectoryInfo(path);
            FileSystemInfo[] filesArray = directory.GetFileSystemInfos();

            if (filesArray.Length == 0) return;

            foreach (var item in filesArray)
            {
                if (item.Attributes == FileAttributes.Directory)
                {
                    //添加文件夹
                    //string dir = item.FullName.Replace(path, "");
                    LocalDirectorysList.Add(item.FullName);
                    GetDirectoryFileList(item.FullName);
                }
                else
                {
                    //文件名
                    string fileName = Path.GetFileName(item.FullName);

                    //是否在黑名单中
                    if (!LocalFileBlacklist.Any(p => p == fileName))
                    {
                        FileInfo fileType = new FileInfo();
                        fileType.FileName = fileName;
                        //fileType.LastModified = File.GetLastWriteTime(item.FullName);
                        //fileType.FileSize = new System.IO.FileInfo(item.FullName).Length;
                        fileType.Path = item.FullName;
                        fileType.Hash = GetHashs(item.FullName);
                        LocalFilesList.Add(fileType);
                    }
                }
            }
        }

        /// <summary>
        /// 读取配置文件
        /// </summary>
        private void ReadConfiguration()
        {
            string ftpUrl = ConfigHelper.GetAppConfig("FtpUrl");
            string ftpUser = ConfigHelper.GetAppConfig("FtpUser");
            string ftpPassword = ConfigHelper.GetAppConfig("FtpPassword");
            
            if(string.IsNullOrEmpty(ftpUrl) )
            {
                Console.WriteLine("FTP IP地址为空");
                return;
            }
            if(string.IsNullOrEmpty(ftpUser) )
            {
                Console.WriteLine("FTP 用户名地址为空");
                return;
            }
            if(string.IsNullOrEmpty(ftpPassword) )
            {
                Console.WriteLine("FTP 用户密码地址为空");
                return;
            }
            FTPManager.ftpUrl = ftpUrl;
            FTPManager.user = ftpUser; 
            FTPManager.password = ftpPassword;
            Console.WriteLine("读取配置文件完成");
        }

        /// <summary>
        /// 获取字节大小
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        private string GetSize(double size)
        {
            String[] units = new String[] { "B", "KB", "MB", "GB", "TB", "PB" };
            double mod = 1024.0;
            int i = 0;
            while (size >= mod)
            {
                size /= mod;
                i++;
            }
            return Math.Round(size) + units[i];
        }

        /// <summary>
        /// 获取文件的哈希值
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private string GetHashs(string path)
        {
            //创建一个哈希算法对象
            using (HashAlgorithm hash = HashAlgorithm.Create())
            {
                using (FileStream file1 = new FileStream(path, FileMode.Open))
                {
                    //哈希算法根据文本得到哈希码的字节数组
                    byte[] hashByte1 = hash.ComputeHash(file1);
                    //将字节数组装换为字符串
                    return BitConverter.ToString(hashByte1);
                }
            }
        }

        /// <summary>
        /// 所有的文件读取完成后
        /// </summary>
        private void ReadEnd()
        {
            if (!ReadLocalEnd || !ReadFTPEnd)
                return;

            Console.WriteLine("所有的文件读取完成");
            FormControlExtensions.InvokeIfRequired(this, () => ProgressBar_DownProgress.Visible = true );

            Task.Run(() =>
            {
                UpdateResultData = UpdateMatching.DetectUpdates(FTPDirectoryList, FTPFileList, LocalDirectorysList, LocalFilesList, FTPManager.ftpUrl, TargetPath);
                UpdateMatching.StartUpdate(UpdateResultData);
                Console.WriteLine("所有文件更新完成");
                //FormControlExtensions.InvokeIfRequired(this, () => ProgressBar_DownProgress.Visible = true);
            });
        }
    }
}

软件在启动后,就会自动进行文件匹配,判断那些文件是否需要更新,但在做之前,需要先做几件事

1.update.exe 这个文件是放在更新目录的,而且当前已经打开,不能自己删除自己吧,所有有关 update.exe 相关的文件都不能参与到更新中,这个就是本地更新黑名单的效果。

2.读取配置文件

ftp 的链接地址,用户名和密码,这些都是不能在代码中写死的,我一般写在配置文件中,如果你不想你的用户名和密码别人看见,也比较简单,单独写一个程序集,将用户名,密码等写到一个类中,然后用我的教程中的 C# 代码混淆加密的方式把 dll 加密就行了,在 visual studio 中也是看不到的,而且,反编译也是没用的,但是在程序运行时,是能正常的读出来的。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
    </startup>

	<appSettings>
		<add key="FtpUrl" value="ftp://127.0.0.1//"/>
		<add key="FtpUser" value="user"/>
		<add key="FtpPassword" value="123456"/>
		<add key="MainProgram" value="CNCMain"/>
	</appSettings>
</configuration>

3.读取 FTP 文件列表

在这里,我一次性将 FTP 链接中对应目录的所有文件的 文件名,文件大小,文件哈希值,文件路径,文件夹,包含子目录文件,都读出来了,这样就没必要 和之前一样,单独去搞配置文件了。

4.读取本地文件

读取本地文件是为了判断那些文件需要替换,删除,那些文件夹需要创建,删除,总之就是让客户端这边和服务器一样,没有多余的文件,也能够保持文件的一致性。

5.匹配更新

有了 FTP 服务器对应目录的文件数据,也有了本地目录的所有文件数据,接下来就是进行匹配了,找出那些 需要创建的文件夹,需要删除的文件夹,需要更新的文件,需要删除的文件,这里匹配文件的用法依然使用哈希值匹配。

using System.Collections.Generic;

internal class UpdateResultInfo
{
    /// <summary>
    /// 需要创建的文件夹列表
    /// </summary>
    public List<string> CreateFolderList { get; set; } = new List<string>();
    /// <summary>
    /// 需要删除的文件夹列表
    /// </summary>
    public List<string> DeleteFolderList { get; set; } = new List<string>();

    /// <summary>
    /// 本地需要更新的文件列表
    /// </summary>
    public List<DownloadFileInfo> LocalUpdateFileList { get;set; } = new List<DownloadFileInfo>();
    /// <summary>
    /// 本地需要删除的文件列表
    /// </summary>
    public List<FileInfo> LocalDeleteFileList { get; set; } = new List<FileInfo>();
}

这里会单独写一个方法来得出想要的结果,然后由单独的方法去处理这些结果。

下面是控制台效果,不喜欢也可以去掉,下面是本地没有需要更新的文件,这会把 ftp 服务器对应目录的所有文件下载下来

界面效果

6.版本的切换

版本的切换也比较简单,在 ftp 链接中改对应的目录就行了

比如:

ftp://127.0.0.1//v1.0.1
ftp://127.0.0.1//v1.0.2
ftp://127.0.0.1//v1.0.3

代码我并没有全部贴出来,有需要的可以去支持一下我,在此谢谢了,有源码有疑问的可以私信我,我看到后会回复的。

源码地址:点击下载

结束

end

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/652524.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

黑盒、白盒、灰盒,如何选择合适的模糊测试工具?

在软件开发和安全领域&#xff0c;模糊测试是一种常用技术&#xff0c;用于发现应用程序或系统中的潜在漏洞和安全弱点。选择不同的模糊测试方法将极大地影响测试的有效性和效率。本文将比较对比黑盒、白盒和灰盒模糊测试的特点和优势并提供选型指导。 模糊测试的分类 黑盒模糊…

人工智能带来的利好将继续推动Palantir股价反弹

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 Palantir的人工智能产品已经获得行业认可 Palantir&#xff08;PLTR&#xff09;成立于2003年&#xff0c;是一家专注于数据分析和人工智能的知名软件公司。其尖端产品已应用到了政府机构、企业和非营利组织等各个行业&…

华为OD机试 Java 实现【输出单向链表中倒数第k个结点】【LeetCode练习题】,附详细解题思路

一、题目描述 输入一个单向链表,输出该链表中倒数第k个结点,链表的倒数第1个结点为链表的尾指针。 链表结点定义如下: class ListNode{int value;ListNode next;public ListNode(){}public ListNode(

解决H5在native中键盘弹起影响页面交互

您好&#xff0c;如果喜欢我的文章&#xff0c;可以关注我的公众号「量子前端」&#xff0c;将不定期关注推送前端好文~ 问题描述 在native中拉起键盘再收回&#xff0c;滚动列表实际距离发生变化&#xff0c;被键盘一起弹上去了&#xff08;我这里大约是400px的样子&#xf…

使用yolox训练自己的数据集并测试

1.首先给出yolox原模型的下载地址: ​​​​​​https://github.com/bubbliiiing/yolox-pytorch 百度网盘链接给出自己完整的模型&#xff08;包括数据集以及权重文件&#xff09;&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1JNjB42u9eGNhRjr1SfD_Tw 提取码&am…

Redis和Redis可视化管理工具的下载和安装

文章目录 Redis 简介一&#xff0c;Redis 下载二&#xff0c;Redis 安装三&#xff0c;Redis 配置四&#xff0c;Redis 启动 Redis-Desktop-Manager 简介一&#xff0c;Redis-Desktop-Manager 下载二&#xff0c;Redis-Desktop-Manager 安装三&#xff0c;Redis-Desktop-Manage…

深度学习笔记之Transformer(三)自注意力机制

深度学习笔记之Transformer——自注意力机制 引言回顾&#xff1a;缩放点积注意力机制自注意力机制自注意力机制与 RNN,CNN \text{RNN,CNN} RNN,CNN的对比简单介绍&#xff1a;卷积神经网络处理序列信息的原理从计算复杂度的角度观察 位置编码 引言 上一节对注意力分数 ( Atte…

关于前端Vue脚手架的完整搭建

创建脚手架 在VSC中打开命令行&#xff0c;输入如下命令可以用于创建脚手架 Vue create <项目名称>会出现如下选项&#xff1a; 前面是选项的名称&#xff0c;括号中的是选项包含有&#xff1a; 1、Vue的版本 2、babel是用于将高版本的js转化成为低版本的js&#xff0…

SSM 整合案例

Ssm整合 注意事项 Spring mvcSpringMybatisMySQL项目结构&#xff0c;以web项目结构整合&#xff08;还有maven、gradle&#xff09;整合日志、事务一个项目中Idea 2019、JDK12、Tomcat9、MySQL 5.5 项目结构 D:\java\Idea\Idea_spacework\SSMhzy&#xff1a;不会就去找项目…

chatgpt赋能python:Python怎么筛选奇数

Python怎么筛选奇数 Python是一种高级编程语言&#xff0c;既具有面向对象编程的特点&#xff0c;又可以进行函数式编程。Python的语法简洁、清晰&#xff0c;非常适合初学者学习。在Python中&#xff0c;筛选奇数的方法非常简单&#xff0c;本文将介绍Python中筛选奇数的方法…

人机交互学习-5 交互式系统的需求

交互式系统的需求 需求是什么需求需求活动 产品特性用户特性体验水平差异新手用户专家用户中间用户 年龄差异老年人儿童 文化差异健康差异 用户建模人物角色人物角色的作用人物角色的构造错误观点人物角色基于问题举例注意事项 建模过程 需求获取、分析和验证观察场景人物角色场…

爬虫数据是如何收集和整理的?

爬虫数据的收集和整理通常包括以下步骤&#xff1a; 确定数据需求&#xff1a;确定要收集的信息类型、来源和范围。 网络爬取&#xff1a;使用编程工具&#xff08;如Python的Scrapy、BeautifulSoup等&#xff09;编写爬虫程序&#xff0c;通过HTTP请求获取网页内容&#xff…

网卡命名规则和网卡变动结论

net.ifnames0 biosdevname0 插卡前状态&#xff1a; 插卡后状态&#xff1a; 结论&#xff1a;明显eth0 MAC地址从00:0d:48:94:10:fc 变更为 c0:33:da:10:31:ff。该方法eth0实际对应的网口发生了变动。 net.ifname1 插卡前状态&#xff1a; 插卡后状态&#xff1a; 查看…

MEC | 条款2 最好使用C++转型操作符

条款2 最好使用C转型操作符 文章目录 条款2 最好使用C转型操作符c4个转型操作符static_castconst_castdynamic_castreinterpret_cast 宏模仿新转换语法欢迎关注公众号【三戒纪元】 c4个转型操作符 static_castconst_castdynamic_castreinterpret_cast 原因是 新式转型方法容…

chatgpt赋能python:Python在SEO中如何确定主语?

Python在SEO中如何确定主语&#xff1f; Python是一种高级编程语言&#xff0c;广泛应用于Web开发、数据分析和机器学习等领域。但是&#xff0c;Python编写的网页是否符合SEO标准&#xff0c;是一个需要重视的问题。在SEO中&#xff0c;主语是一个非常重要的因素。那么&#…

CLickhouse 物化视图--干货记录(亲验证)

1、普通视图VS物化视图 普通视图不保存数据&#xff0c;保存的仅仅是查询语句&#xff0c;查询的时候还是从原表读取数据&#xff0c;可以将普通视图理解为是个子查询。--中看不中用 物化视图则是把查询的结果根据相应的引擎存入到了磁盘或内存中&#xff0c;对数据重新进行了…

Duilib中禁止一个窗口双击最大化

1、Duilib中禁止一个窗口双击最大化 用duilib开发了一个窗口&#xff0c;比如是登录窗口&#xff0c;那么这个窗口的窗口的双击最大化就毫无意义&#xff0c;甚至带来灾难&#xff0c;我们就要明确禁止这样的行为。 我们应该明确&#xff0c;一个窗口创建的时候就赋予了它一些…

内核链表、JSON的序列化与反序列化

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除&#xff1b; 2) 结构体每个成员相对结构体首地址的偏移量(offset)都是成员大小的整数倍&#xff0c;如有需要编译器会在成员之间加上填充字节(internal adding)&#xff1b; 3) 结构体的总大小为结构体最宽基本类型…

计算机组成原理(考研408)练习题#3

用于复习408或计算机组成原理期末考试。如有错误请在评论区指出。 So lets start studying with questions! それでは、問題の勉強を始めましょう&#xff01; 1. 定点整数原码编码[x]原1110100B 的真值为_________。 首先&#xff0c;1110100B是一个8位二进制数&#xff0c…

Spring Cloud Sleuth使用简介

Spring-Cloud Spring Cloud为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性Token、全局锁、决策竞选、分布式会话和集群状态)操作的开发工具。使用SpringCloud开发者可以快速实现上述这些模式。 SpringCloud Sleuth Distribu…