using CookComputing.XmlRpc; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Net; using System.Net.Http; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using System.Xml; namespace IoTDameon { public class UpdateIoTNodeService : BackgroundService { private readonly ILogger _logger; private readonly IWebHostEnvironment _env; private readonly IConfiguration _cfg; private readonly IHttpClientFactory _httpClientFactory; public bool IsUpdating { get; set; } public UpdateIoTNodeService(ILogger logger, IWebHostEnvironment env, IConfiguration cfg, IHttpClientFactory httpClientFactory) { this._logger = logger; this._env = env; this._cfg = cfg; this._httpClientFactory = httpClientFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogDebug($"update service start"); stoppingToken.Register(() => _logger.LogDebug("update service stop")); while (!stoppingToken.IsCancellationRequested) { this.Update(); await Task.Delay(1000 * 60, stoppingToken); } await Task.CompletedTask; } private void Update() { if (this.IsUpdating) { return; } else { this.IsUpdating = true; } try { UpdateInternal(); } catch (Exception ex) { this._logger.LogError(ex, "update error"); } this.IsUpdating = false; } private void UpdateInternal() { var processName = "iotnode"; var appFolder = "IoTNode"; var port = 8002; var proxyUrl = "http://localhost:9001/RPC2"; var root = Directory.GetParent(_env.ContentRootPath).FullName; var appPath = Path.Combine(root, appFolder); var name = $"{appFolder}.zip"; var backupPath = Path.Combine(root, $"{appFolder}_bk"); var file = Path.Combine(root, name); var currentCheckSum = string.Empty; //检查是否有更新 this._logger.LogInformation("check last version"); var currentVersion = this._httpClientFactory.CreateClient().GetAsync($"http://localhost:{port}/Home/GetVersion").Result.Content.ReadAsStringAsync().Result; var server = this._httpClientFactory.CreateClient().GetAsync($"http://localhost:{port}/Home/GetServer").Result.Content.ReadAsStringAsync().Result; var serverUrl = $"{server}/{processName}.xml"; this._logger.LogInformation($"request url:{serverUrl}"); var info = this._httpClientFactory.CreateClient().GetAsync(serverUrl).Result.Content.ReadAsStringAsync().Result; var doc = new XmlDocument(); doc.LoadXml(info); var lastVersion = doc.GetElementsByTagName("version")[0].InnerText.Trim(); var lastCheckSum = doc.GetElementsByTagName("checksum")[0].InnerText.Trim(); if (currentVersion != lastVersion) { this._logger.LogInformation($"current version {currentVersion} does not equals {lastVersion}"); //查看是否已下载更新并删除旧的更新文件 if (File.Exists(file)) { currentCheckSum = getCheckSum(file); if (currentCheckSum != lastCheckSum) { this._logger.LogWarning($"current file hash {currentCheckSum} does not equals last file hash {lastCheckSum}"); File.Delete(file); this._logger.LogWarning($"delete old file {file}"); } } //下载更新并校验 if (!File.Exists(file)) { var bytes = this._httpClientFactory.CreateClient().GetAsync($"{server}/{name}").Result.Content.ReadAsByteArrayAsync().Result; using var fs = File.Create(file); fs.Write(bytes); currentCheckSum = getCheckSum(file); if (currentCheckSum != lastCheckSum) { this._logger.LogWarning($"download file hash {currentCheckSum} does not math hash in xml {lastCheckSum}"); File.Delete(file); this._logger.LogWarning($"delete old file {file}"); } } if (File.Exists(file)) { this._logger.LogWarning($"begin update"); //关闭要更新的程序 var proxy = XmlRpcProxyGen.Create(); proxy.Url = proxyUrl; proxy.Credentials = new NetworkCredential("usr", "pwd"); try { proxy.stopProcess(processName); this._logger.LogWarning($"close process {processName}"); } catch (XmlRpcFaultException ex) { if (ex.FaultCode != 60) { throw ex; } else { this._logger.LogWarning($"close error,{processName} is alreday stoped"); } } //备份要更新的程序 try { Directory.Move(appPath, backupPath); this._logger.LogInformation($"back up {appPath}"); } catch (Exception ex) { this._logger.LogError(ex, ex.Message); throw new Exception("backup error", ex); } Directory.CreateDirectory(appPath); this._logger.LogInformation($"mkdir {appPath}"); foreach (var item in Directory.GetFiles(backupPath,"*.db")) { File.Copy(item, Path.Combine(appPath, Path.GetFileName(item))); } this._logger.LogInformation($"copy db files to {appPath}"); //更新程序 try { ZipFile.ExtractToDirectory(file, root, true); this._logger.LogInformation($"unzip {file} to {appPath}"); } catch (Exception ex) { this._logger.LogError(ex, ex.Message); Directory.Delete(appPath, true); Directory.Move(backupPath, appPath); throw new Exception("upzip error,restore old files", ex); } //设置权限 if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { var updateScript = Path.Combine(appPath, "update.sh"); var command = $"-c \"chmod 755 {updateScript}\""; Console.WriteLine(command); Console.WriteLine(command.Bash()); Console.WriteLine(updateScript.Bash()); this._logger.LogInformation($"chmod and run {updateScript}"); } //启动更新程序 proxy.startProcess(processName); this._logger.LogInformation($"{appPath} start"); } } } private string getCheckSum(string file) { using var sha = SHA512.Create(); using var fs = File.OpenRead(file); var checksum = BitConverter.ToString(sha.ComputeHash(fs)).Replace("-", "").ToLower(); return checksum; } } }