You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iot/projects/IoTDameon/UpdateIoTNodeService.cs

274 lines
10 KiB

using CookComputing.XmlRpc;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace IoTDameon
{
public class UpdateIoTNodeService : BackgroundService
{
private readonly ILogger<UpdateIoTNodeService> _logger;
private readonly IWebHostEnvironment _env;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IHubContext<DefaultHub> _hub;
public bool IsUpdating { get; set; }
public UpdateIoTNodeService(ILogger<UpdateIoTNodeService> logger,
IWebHostEnvironment env,
IHttpClientFactory httpClientFactory,
IHubContext<DefaultHub> hub)
{
this._logger = logger;
this._env = env;
this._httpClientFactory = httpClientFactory;
this._hub = hub;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
this.Log($"update service start");
stoppingToken.Register(() => this.Log("update service stop"));
while (!stoppingToken.IsCancellationRequested)
{
this.Update();
await Task.Delay(10 * 60 * 1000, stoppingToken);
}
await Task.CompletedTask;
}
public void Update()
{
if (this.IsUpdating)
{
return;
}
else
{
this.IsUpdating = true;
}
try
{
UpdateInternal();
}
catch (Exception ex)
{
this.LogError(ex.ToString());
}
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 file = Path.Combine(root, name);
Version currentVersion;
try
{
currentVersion = new Version(this._httpClientFactory.CreateClient().GetAsync($"http://localhost:{port}/Home/GetVersion").Result.Content.ReadAsStringAsync().Result);
this.Log($"节点的当前版本:{currentVersion}");
}
catch (Exception ex)
{
this.LogError("获取当前版本失败");
this.LogError(ex.ToString());
throw;
}
var server = this._httpClientFactory.CreateClient().GetAsync($"http://localhost:{port}/Home/GetServer").Result.Content.ReadAsStringAsync().Result;
var serverUrl = $"{server}/{processName}.xml";
this.Log($"request url:{serverUrl}");
string info;
try
{
info = this._httpClientFactory.CreateClient().GetAsync(serverUrl).Result.Content.ReadAsStringAsync().Result;
this.Log("最新版本信息:");
this.Log(info);
}
catch (Exception ex)
{
this.LogError("获取最新版本失败");
this.LogError(ex.ToString());
throw;
}
var doc = new XmlDocument();
doc.LoadXml(info);
var lastVersion = new Version(doc.GetElementsByTagName("version")[0].InnerText.Trim());
var lastCheckSum = doc.GetElementsByTagName("checksum")[0].InnerText.Trim();
if (currentVersion.CompareTo(lastVersion) < 0)
{
this.Log($"当前版本 {currentVersion} 小于最新版本 {lastVersion}");
//查看是否已下载更新并删除旧的更新文件
if (File.Exists(file))
{
var currentCheckSum = GetCheckSum(file);
if (currentCheckSum != lastCheckSum)
{
this.Log($"已下载更新hash {currentCheckSum} 不匹配最新更新的hash {lastCheckSum}");
File.Delete(file);
this.Log($"删除过时的文件 {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);
}
var currentCheckSum = GetCheckSum(file);
if (currentCheckSum != lastCheckSum)
{
this.Log($"已下载更新hash {currentCheckSum} 不匹配最新更新的hash {lastCheckSum}");
File.Delete(file);
this.Log($"删除过时的文件 {file}");
}
}
if (File.Exists(file))
{
this.Log($"开始更新");
//关闭要更新的程序
var proxy = XmlRpcProxyGen.Create<ISupervisorService>();
proxy.Url = proxyUrl;
proxy.Credentials = new NetworkCredential("usr", "pwd");
try
{
proxy.stopProcess(processName);
this.Log($"关闭进程 {processName}");
}
catch (XmlRpcFaultException ex)
{
if (ex.FaultCode != 60)
{
throw;
}
else
{
this.Log($"关闭失败,进程 {processName} 已经关闭");
}
}
//备份要更新的程序
var backupPath = Path.Combine(root, $"{appFolder}_{currentVersion}_bk");
try
{
if (Directory.Exists(backupPath))
{
Directory.Delete(backupPath, true);
}
Directory.Move(appPath, backupPath);
this.Log($"备份 {appPath}");
}
catch (Exception ex)
{
this.Log($"备份失败:");
this.LogError(ex.ToString());
throw;
}
Directory.CreateDirectory(appPath);
this.Log($"创建目录 {appPath}");
foreach (var item in Directory.GetFiles(backupPath))
{
if (item == "iotnode.db" || item == "job.db" || item == "appsettings.json")
{
if (item == "job.db" && currentVersion.ToString() == "1.0.0.530")
{
continue;
}
else
{
File.Copy(item, Path.Combine(appPath, Path.GetFileName(item)));
}
}
}
this.Log($"还原数据库db文件 {appPath}");
//更新程序
try
{
ZipFile.ExtractToDirectory(file, root, Encoding.UTF8, true);
this.Log($"解压更新文件 {file} 到 {appPath}");
//设置权限
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var updateScript = Path.Combine(appPath, "update.sh");
var command = $"chmod 755 {updateScript}";
this.Log(command.Bash());
this.Log($"修改权限:{updateScript}");
this.Log(updateScript.Bash());
this.Log($"执行更新脚本:{updateScript}");
}
proxy.startProcess(processName);
this.Log($"启动进程:{processName}");
}
catch (Exception ex)
{
this.LogError("更新失败,开始还原");
this.LogError(ex.ToString());
Directory.Delete(appPath, true);
this.LogError("删除失败的更新目录");
Directory.Move(backupPath, appPath);
this.LogError("还原备份的程序");
proxy.startProcess(processName);
this.Log($"启动进程:{processName}");
throw;
}
}
}
}
private string GetCheckSum(string file)
{
using (var sha = SHA1.Create())
{
using (var fs = File.OpenRead(file))
{
var checksum = BitConverter.ToString(sha.ComputeHash(fs)).Replace("-", "");
return checksum;
}
}
}
private void Log(string message)
{
try
{
this._logger.LogInformation(message);
this._hub.Clients.All.SendAsync("ServerToClient", message);
}
catch (Exception ex)
{
this.LogError(ex.ToString());
}
}
private void LogError(string message)
{
try
{
this._logger.LogError(message);
this._hub.Clients.All.SendAsync("ServerToClient", message);
}
catch (Exception ex)
{
this._logger.LogError(ex, ex.Message);
}
}
}
}