611 lines
18 KiB
C#
611 lines
18 KiB
C#
using COSXML.Network;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Data;
|
||
using System.Drawing;
|
||
using System.Drawing.Drawing2D;
|
||
using System.IO;
|
||
using System.IO.Compression;
|
||
using System.Linq;
|
||
using System.Net;
|
||
using System.Reflection.Emit;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
using static Update.ChangelogTheme;
|
||
|
||
namespace Update
|
||
{
|
||
public partial class MainForm : Form
|
||
{
|
||
public string Title { get { return Text; } set { Text = value; } }
|
||
private Point mouseOffset; // 记录鼠标指针的坐标
|
||
private bool isMouseDown; // 记录鼠标左键是否按下
|
||
|
||
public MainForm()
|
||
{
|
||
InitializeComponent();
|
||
|
||
this.TopMost = true;
|
||
lb_title.Text = "更新日志";
|
||
|
||
var changelogItems = GetChangeInfos();
|
||
if (changelogItems != null)
|
||
{
|
||
|
||
string fromVersion = (Program.oldVersion / 100m).ToString();
|
||
string toVersion = (Program.newVersion / 100m).ToString();
|
||
ShowChangelog(fromVersion, toVersion, changelogItems);
|
||
}
|
||
|
||
//// 演示数据:v1.0.0 升级到 v1.3.0,展示 1.1.0 ~ 1.3.0(新的在上)
|
||
//ShowChangelog("1.0.0", "1.3.0", new[]
|
||
//{
|
||
// new ChangelogItem { Version = "1.3.0", PublishedAt = "2026-10-10", Notes = "新增深色模式,支持跟随系统\n新增快捷键面板(Ctrl + /)\n启动速度提升 40%" },
|
||
// new ChangelogItem { Version = "1.2.0", PublishedAt = "2026-09-05", Notes = "支持导出数据为 Excel 格式\n优化列表滚动性能\n修复偶发的保存失败问题" },
|
||
// new ChangelogItem { Version = "1.1.0", PublishedAt = "2026-08-01", Notes = "新增自动更新功能\n修复高分屏下界面模糊的问题" },
|
||
//});
|
||
}
|
||
private List<ChangelogItem> GetChangeInfos()
|
||
{
|
||
string paidanUrl = $"https://update-wx-order.e57.cn";
|
||
string shanghuoUrl = $"https://update.e57.cn";
|
||
using (WebClient c = new WebClient())
|
||
{
|
||
c.Encoding = Encoding.UTF8;
|
||
string url = $"/api/releases/since?version={(Program.oldVersion / 100m)}&channel=stable";
|
||
if (Program.softName == "29WxFindOrder")
|
||
{
|
||
url = paidanUrl + url;
|
||
}
|
||
else if (Program.softName == "29供应链.exe")
|
||
url = shanghuoUrl + url;
|
||
string json = c.DownloadString(url);
|
||
var root = MiniJson.Json.Deserialize(json) as Dictionary<string, object>;
|
||
if (root["ok"].ToString() == "True")
|
||
{
|
||
var data = (Dictionary<string, object>)root["data"];
|
||
var versionsRaw = (List<object>)data["versions"];
|
||
|
||
var list = new List<ChangelogItem>();
|
||
foreach (Dictionary<string, object> item in versionsRaw)
|
||
{
|
||
var ci = new ChangelogItem
|
||
{
|
||
Version = item["version"]?.ToString(),
|
||
Notes = item["notes"]?.ToString(),
|
||
};
|
||
|
||
if (DateTime.TryParse(item["publishedAt"]?.ToString(), out DateTime v))
|
||
{
|
||
ci.PublishedAt = v.ToString("yyyy-MM-dd");
|
||
}
|
||
list.Add(ci);
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渲染更新日志:头部展示版本区间,中间按版本分组列出更新条目
|
||
/// </summary>
|
||
private void ShowChangelog(string fromVersion, string toVersion, IEnumerable<ChangelogItem> entries)
|
||
{
|
||
int length = entries.Count();
|
||
lblHeroTitle.Text = "v" + fromVersion + " → v" + toVersion;
|
||
lblHeroMeta.Text = length > 1 ? "共包含 " + length + " 个版本的更新内容" : "本次更新内容";
|
||
|
||
panelChangelog.SuspendLayout();
|
||
panelChangelog.Controls.Clear();
|
||
|
||
var blocks = entries.Select((entry, i) => new ChangelogBlock(entry, i == 0, panelChangelog.Width)).ToList();
|
||
int contentHeight = blocks.Sum(b => b.Height);
|
||
|
||
// 内容超出可视区时预留垂直滚动条宽度,避免出现横向滚动条
|
||
int width = panelChangelog.ClientSize.Width;
|
||
if (contentHeight > panelChangelog.ClientSize.Height)
|
||
width -= SystemInformation.VerticalScrollBarWidth;
|
||
|
||
int y = 6;
|
||
foreach (var block in blocks)
|
||
{
|
||
block.Width = width;
|
||
block.Location = new Point(0, y);
|
||
y += block.Height;
|
||
panelChangelog.Controls.Add(block);
|
||
}
|
||
panelChangelog.ResumeLayout(true);
|
||
}
|
||
|
||
private void heroPanel_Paint(object sender, PaintEventArgs e)
|
||
{
|
||
var panel = (Panel)sender;
|
||
if (panel.Width <= 0 || panel.Height <= 0)
|
||
return;
|
||
|
||
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||
using (var brush = new LinearGradientBrush(panel.ClientRectangle, AccentBg, Color.White, 40F))
|
||
e.Graphics.FillRectangle(brush, panel.ClientRectangle);
|
||
|
||
using (var pen = new Pen(LineColor))
|
||
{
|
||
e.Graphics.DrawLine(pen, 0, 0, panel.Width, 0);
|
||
e.Graphics.DrawLine(pen, 0, panel.Height - 1, panel.Width, panel.Height - 1);
|
||
}
|
||
}
|
||
|
||
|
||
private void btnOk_Click(object sender, EventArgs e)
|
||
{
|
||
if (lblMessage.Text == "升级完成,点击确定重启程序")
|
||
{
|
||
string extPath = System.Environment.CurrentDirectory + "\\" + Program.softName.Replace(".exe", "") + ".exe";
|
||
System.Diagnostics.Process.Start(extPath);
|
||
Application.Exit();
|
||
return;
|
||
}
|
||
|
||
btnOk.Enabled = false;
|
||
|
||
CloseMainProgram();
|
||
|
||
if (!System.IO.Directory.Exists("upBak"))
|
||
System.IO.Directory.CreateDirectory("upBak");
|
||
var upBakPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "upBak";
|
||
if (!System.IO.Directory.Exists("upTemp"))
|
||
System.IO.Directory.CreateDirectory("upTemp");
|
||
var upTempPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "upTemp";
|
||
|
||
// 清空文件夹准备下载
|
||
foreach (string filePath in Directory.GetFiles(upTempPath))
|
||
{
|
||
File.Delete(filePath); // 删除文件
|
||
}
|
||
foreach (string dirPath in Directory.GetDirectories(upTempPath))
|
||
{
|
||
Directory.Delete(dirPath, true); // 删除子文件夹及其内容
|
||
}
|
||
|
||
this.lblMessage.Text = "升级中请稍候...";
|
||
progressBar1.Visible = true;
|
||
progressBar1.Maximum = 100;//fileNameArr.Count();
|
||
progressBar1.Value = 0;
|
||
Task.Run(async () =>
|
||
{
|
||
string tempFile = upTempPath + "\\" + Program.updateUrl.Substring(Program.updateUrl.LastIndexOf("/") + 1);
|
||
//bool result = Program.DownloadFile(Program.updateUrl, tempFile);
|
||
|
||
//while (true)
|
||
//{
|
||
// Invoke(new Action(() =>
|
||
// {
|
||
// progressBar1.Value = Program.progress;
|
||
// }));
|
||
// if (Program.progress == 100)
|
||
// break;
|
||
//}
|
||
|
||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||
using (var downloadClient = new WebClient())
|
||
{
|
||
downloadClient.Proxy = null;
|
||
|
||
downloadClient.DownloadProgressChanged += (s, args) =>
|
||
{
|
||
Invoke(new Action(() => { progressBar1.Value = args.ProgressPercentage; }));
|
||
|
||
};
|
||
|
||
await downloadClient.DownloadFileTaskAsync(new Uri(Program.updateUrl), tempFile);
|
||
}
|
||
|
||
if (1 == 1)
|
||
//if (result)
|
||
{
|
||
ZipFile.ExtractToDirectory(tempFile, upTempPath);
|
||
File.Delete(tempFile);
|
||
|
||
string mainPath = System.Environment.CurrentDirectory;
|
||
// 备份旧文件
|
||
foreach (string filePath in Directory.GetFiles(upTempPath))
|
||
{
|
||
try
|
||
{
|
||
string fileName = filePath.Substring(filePath.LastIndexOf("\\") + 1);
|
||
File.Copy(mainPath + "\\" + fileName, upBakPath + "\\" + fileName, true);
|
||
Thread.Sleep(10);
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
foreach (string dirPath in Directory.GetDirectories(upTempPath))
|
||
{
|
||
try
|
||
{
|
||
string fileName = dirPath.Substring(dirPath.LastIndexOf("\\") + 1);
|
||
CopyDirectory(mainPath + "\\" + fileName, upBakPath + "\\" + fileName);
|
||
Thread.Sleep(10);
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
|
||
string error = "";
|
||
// 替换旧文件
|
||
foreach (string filePath in Directory.GetFiles(upTempPath))
|
||
{
|
||
if (!string.IsNullOrEmpty(error))
|
||
{
|
||
error = "发生错误:\r\n" + error + "\r\n请关闭升级窗口重试!";
|
||
break;
|
||
}
|
||
try
|
||
{
|
||
string fileName = filePath.Substring(filePath.LastIndexOf("\\") + 1);
|
||
File.Copy(upTempPath + "\\" + fileName, mainPath + "\\" + fileName, true);
|
||
Thread.Sleep(10);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
error = "发生错误:\r\n" + ex.Message + "\r\n请关闭升级窗口重试!";
|
||
Invoke(new Action(() =>
|
||
{
|
||
lblMessage.Text = error;
|
||
}));
|
||
}
|
||
}
|
||
foreach (string dirPath in Directory.GetDirectories(upTempPath))
|
||
{
|
||
if (!string.IsNullOrEmpty(error))
|
||
break;
|
||
try
|
||
{
|
||
string fileName = dirPath.Substring(dirPath.LastIndexOf("\\") + 1);
|
||
CopyDirectory(upTempPath + "\\" + fileName, mainPath + "\\" + fileName);
|
||
Thread.Sleep(10);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
error = "发生错误:\r\n" + ex.Message + "\r\n请关闭升级窗口重试!";
|
||
Invoke(new Action(() =>
|
||
{
|
||
lblMessage.Text = error;
|
||
}));
|
||
}
|
||
}
|
||
|
||
Thread.Sleep(50);
|
||
Invoke(new Action(() =>
|
||
{
|
||
if (string.IsNullOrEmpty(error))
|
||
{
|
||
progressBar1.Value = 100;
|
||
lblMessage.Text = "升级完成,点击确定重启程序";
|
||
btnOk.Enabled = true;
|
||
}
|
||
}));
|
||
}
|
||
else
|
||
{
|
||
Invoke(new Action(() =>
|
||
{
|
||
progressBar1.Value = 100;
|
||
this.lblMessage.Text = "升级失败,未获取到升级文件";
|
||
btnOk.Enabled = true;
|
||
}));
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 拷贝文档目录
|
||
/// </summary>
|
||
/// <param name="srcPath"></param>
|
||
/// <param name="destPath"></param>
|
||
public static void CopyDirectory(string srcPath, string destPath)
|
||
{
|
||
try
|
||
{
|
||
DirectoryInfo dir = new DirectoryInfo(srcPath);
|
||
FileSystemInfo[] fileinfo = dir.GetFileSystemInfos(); //获取目录下(不包含子目录)的文件和子目录
|
||
foreach (FileSystemInfo i in fileinfo)
|
||
{
|
||
if (i is DirectoryInfo) //判断是否文件夹
|
||
{
|
||
if (!Directory.Exists(destPath + "\\" + i.Name))
|
||
{
|
||
Directory.CreateDirectory(destPath + "\\" + i.Name); //目标目录下不存在此文件夹即创建子文件夹
|
||
}
|
||
CopyDirectory(i.FullName, destPath + "\\" + i.Name); //递归调用复制子文件夹
|
||
}
|
||
else
|
||
{
|
||
File.Copy(i.FullName, destPath + "\\" + i.Name, true); //不是文件夹即复制文件,true表示可以覆盖同名文件
|
||
}
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
throw;
|
||
}
|
||
}
|
||
|
||
private void btnCancel_Click(object sender, EventArgs e)
|
||
{
|
||
this.Close();
|
||
}
|
||
|
||
private void panel1_MouseDown(object sender, MouseEventArgs e)
|
||
{
|
||
if (e.Button == MouseButtons.Left)
|
||
{
|
||
mouseOffset = new Point(-e.X, -e.Y);
|
||
isMouseDown = true;
|
||
}
|
||
}
|
||
|
||
private void panel1_MouseUp(object sender, MouseEventArgs e)
|
||
{
|
||
isMouseDown = false;
|
||
}
|
||
|
||
private void panel1_MouseMove(object sender, MouseEventArgs e)
|
||
{
|
||
if (isMouseDown)
|
||
{
|
||
// 获取鼠标的位置
|
||
Point mousePos = MousePosition;
|
||
mousePos.Offset(mouseOffset.X, mouseOffset.Y);
|
||
Location = mousePos;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private void btnClose_Click(object sender, EventArgs e)
|
||
{
|
||
Application.Exit();
|
||
}
|
||
|
||
private void CloseMainProgram()
|
||
{
|
||
string in_sAppName = Program.softName; //(一个word文档程序)
|
||
in_sAppName = in_sAppName.Replace(".exe", "");
|
||
|
||
try
|
||
{
|
||
System.Diagnostics.Process[] psiNewcas = System.Diagnostics.Process.GetProcessesByName(in_sAppName);
|
||
//System.Diagnostics.Process.GetProcessesByName("");
|
||
|
||
//var pro = System.Diagnostics.Process.GetProcesses();
|
||
//foreach(var p in pro)
|
||
//{
|
||
// Console.WriteLine(p.ProcessName);
|
||
//}
|
||
|
||
foreach (System.Diagnostics.Process p in psiNewcas)
|
||
{
|
||
p.Kill();
|
||
//p.WaitForExit();
|
||
//p.Close();
|
||
//Console.WriteLine(in_sAppName + "已经停止了");
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
Console.WriteLine(in_sAppName + "停止失败");
|
||
}
|
||
}
|
||
|
||
private void panel2_Paint(object sender, PaintEventArgs e)
|
||
{
|
||
|
||
}
|
||
}
|
||
public class Changelog
|
||
{
|
||
public bool Ok { get; set; }
|
||
public ChangelogData Data { get; set; }
|
||
}
|
||
public class ChangelogData
|
||
{
|
||
public List<ChangelogItem> versions { get; set; }
|
||
}
|
||
/// <summary>
|
||
/// 单个版本的更新日志
|
||
/// </summary>
|
||
public class ChangelogItem
|
||
{
|
||
public string Version { get; set; }
|
||
public string PublishedAt { get; set; }
|
||
public string Notes { get; set; }
|
||
|
||
public string[] Items
|
||
{
|
||
get
|
||
{
|
||
string[] temp = new string[0];
|
||
if (string.IsNullOrEmpty(Notes)) return temp;
|
||
return Notes.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新日志中的单个版本区块:版本标题行(色条 + 版本号 + 最新标记 + 日期)与条目列表
|
||
/// </summary>
|
||
public class ChangelogBlock : Panel
|
||
{
|
||
private const int LeftPad = 28;
|
||
private const int RightPad = 28;
|
||
private const int BarWidth = 3;
|
||
private const int BarHeight = 16;
|
||
private const int TextLeft = 55; // 条目文字左边,留出圆点位置
|
||
private const int ItemPadding = 5; // 条目上下内边距
|
||
private const int BottomGap = 20; // 区块底部留白
|
||
private const int HeaderHeight = 28;
|
||
|
||
private readonly bool isNewest;
|
||
private readonly Font versionFont = new Font("微软雅黑", 12F, FontStyle.Bold);
|
||
private readonly Font dateFont = new Font("微软雅黑", 9F);
|
||
private readonly Font badgeFont = new Font("微软雅黑", 8.5F);
|
||
private readonly Font itemFont = new Font("微软雅黑", 10.5F);
|
||
private readonly Size versionSize;
|
||
private readonly Size dateSize;
|
||
private readonly List<System.Windows.Forms.Label> lblItems = new List<System.Windows.Forms.Label>();
|
||
private readonly System.Windows.Forms.Label lblVersion = new System.Windows.Forms.Label();
|
||
private readonly System.Windows.Forms.Label lblDate = new System.Windows.Forms.Label();
|
||
private Rectangle badgeRect = Rectangle.Empty;
|
||
private Rectangle barRect = Rectangle.Empty;
|
||
private readonly List<Point> bulletPoints = new List<Point>();
|
||
private int laidOutWidth = -1;
|
||
|
||
public ChangelogBlock(ChangelogItem entry, bool isNewest, int width)
|
||
{
|
||
this.isNewest = isNewest;
|
||
BackColor = Color.White;
|
||
DoubleBuffered = true;
|
||
|
||
versionSize = TextRenderer.MeasureText("v" + entry.Version, versionFont);
|
||
dateSize = TextRenderer.MeasureText(entry.PublishedAt, dateFont);
|
||
|
||
lblVersion.Font = versionFont;
|
||
lblVersion.ForeColor = Accent;
|
||
lblVersion.BackColor = Color.White;
|
||
lblVersion.Text = "v" + entry.Version;
|
||
Controls.Add(lblVersion);
|
||
|
||
lblDate.Font = dateFont;
|
||
lblDate.ForeColor = SubColor;
|
||
lblDate.BackColor = Color.White;
|
||
lblDate.Text = entry.PublishedAt;
|
||
Controls.Add(lblDate);
|
||
|
||
foreach (string item in entry.Items)
|
||
{
|
||
var label = new System.Windows.Forms.Label
|
||
{
|
||
Font = itemFont,
|
||
ForeColor = TextColor,
|
||
BackColor = Color.White,
|
||
Padding = new Padding(0, ItemPadding, 0, ItemPadding),
|
||
Text = item
|
||
};
|
||
lblItems.Add(label);
|
||
Controls.Add(label);
|
||
}
|
||
|
||
Width = width;
|
||
}
|
||
|
||
protected override void OnResize(EventArgs eventargs)
|
||
{
|
||
base.OnResize(eventargs);
|
||
LayoutContent(Width);
|
||
}
|
||
|
||
private void LayoutContent(int width)
|
||
{
|
||
if (width <= 0 || width == laidOutWidth)
|
||
return;
|
||
laidOutWidth = width;
|
||
|
||
SuspendLayout();
|
||
|
||
barRect = new Rectangle(LeftPad, (HeaderHeight - BarHeight) / 2, BarWidth, BarHeight);
|
||
lblVersion.SetBounds(LeftPad + BarWidth + 8, (HeaderHeight - versionSize.Height) / 2, versionSize.Width, versionSize.Height);
|
||
|
||
badgeRect = Rectangle.Empty;
|
||
int headerRight = lblVersion.Right;
|
||
if (isNewest)
|
||
{
|
||
Size badgeSize = TextRenderer.MeasureText("最新", badgeFont, Size.Empty, TextFormatFlags.NoPadding);
|
||
badgeRect = new Rectangle(headerRight + 8, (HeaderHeight - 18) / 2, badgeSize.Width + 16, 18);
|
||
headerRight = badgeRect.Right;
|
||
}
|
||
|
||
lblDate.SetBounds(Math.Max(headerRight + 8, width - RightPad - dateSize.Width),
|
||
(HeaderHeight - dateSize.Height) / 2, dateSize.Width, dateSize.Height);
|
||
|
||
int lineHeight = TextRenderer.MeasureText("中", itemFont, Size.Empty, TextFormatFlags.NoPadding).Height;
|
||
int textWidth = Math.Max(60, width - TextLeft - RightPad);
|
||
bulletPoints.Clear();
|
||
int y = HeaderHeight;
|
||
foreach (var label in lblItems)
|
||
{
|
||
int measured = TextRenderer.MeasureText(label.Text, itemFont, new Size(textWidth, 0), TextFormatFlags.WordBreak).Height;
|
||
label.SetBounds(TextLeft, y, textWidth, measured + ItemPadding * 2);
|
||
bulletPoints.Add(new Point(TextLeft - 14, y + ItemPadding + lineHeight / 2));
|
||
y += label.Height;
|
||
}
|
||
|
||
Height = y + BottomGap;
|
||
ResumeLayout(false);
|
||
}
|
||
|
||
protected override void OnPaint(PaintEventArgs e)
|
||
{
|
||
base.OnPaint(e);
|
||
Graphics g = e.Graphics;
|
||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||
|
||
using (var path = RoundedRect(barRect, BarWidth / 2f))
|
||
using (var brush = new SolidBrush(Accent))
|
||
g.FillPath(brush, path);
|
||
|
||
if (isNewest && badgeRect.Width > 0)
|
||
{
|
||
using (var path = RoundedRect(badgeRect, badgeRect.Height / 2f))
|
||
using (var brush = new SolidBrush(Accent))
|
||
g.FillPath(brush, path);
|
||
TextRenderer.DrawText(g, "最新", badgeFont, badgeRect, Color.White,
|
||
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding);
|
||
}
|
||
|
||
using (var dot = new SolidBrush(Accent))
|
||
foreach (Point p in bulletPoints)
|
||
g.FillEllipse(dot, p.X - 3, p.Y - 3, 6, 6);
|
||
}
|
||
|
||
private static GraphicsPath RoundedRect(Rectangle rect, float radius)
|
||
{
|
||
var path = new GraphicsPath();
|
||
float d = radius * 2;
|
||
path.AddArc(rect.X, rect.Y, d, d, 180, 90);
|
||
path.AddArc(rect.Right - d, rect.Y, d, d, 270, 90);
|
||
path.AddArc(rect.Right - d, rect.Bottom - d, d, d, 0, 90);
|
||
path.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90);
|
||
path.CloseFigure();
|
||
return path;
|
||
}
|
||
}
|
||
|
||
public enum FormLevel
|
||
{
|
||
SMALL,
|
||
MEDIUM,
|
||
LARGE = 500
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新日志窗口的配色,与设计稿保持一致
|
||
/// </summary>
|
||
internal static class ChangelogTheme
|
||
{
|
||
public static readonly Color Accent = Color.FromArgb(152, 99, 143);
|
||
public static readonly Color AccentBg = Color.FromArgb(245, 237, 244);
|
||
public static readonly Color SubColor = Color.FromArgb(107, 114, 128);
|
||
public static readonly Color LineColor = Color.FromArgb(229, 231, 238);
|
||
public static readonly Color TextColor = Color.FromArgb(31, 36, 48);
|
||
}
|
||
|
||
|
||
}
|