| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Drawing;
- using System.Drawing.Drawing2D;
- using System.IO;
- using System.Reflection;
- using System.Text;
- using System.Text.RegularExpressions;
- using System.Threading;
- using System.Windows.Forms;
- namespace MesDbBackup
- {
- /// <summary>
- /// MES 数据库备份桌面客户端:窗口内完成连接、选目录和备份,不打开浏览器。
- /// 实际导出仍调用 backup-db.ps1,不改原备份逻辑。
- /// </summary>
- internal static class Program
- {
- internal const string MutexName = "Global\\MES_Cloud_DB_Backup_UI";
- internal const string MarkerName = ".packed-by-mes-backup";
- [STAThread]
- static void Main()
- {
- Application.EnableVisualStyles();
- Application.SetCompatibleTextRenderingDefault(false);
- bool createdNew;
- using (var mutex = new Mutex(true, MutexName, out createdNew))
- {
- if (!createdNew)
- {
- MessageBox.Show(
- "备份客户端已经在运行,请查看任务栏。",
- "MES 数据库备份",
- MessageBoxButtons.OK,
- MessageBoxIcon.Information);
- return;
- }
- Application.Run(new MainForm());
- }
- }
- }
- /// <summary>
- /// 深空 AI 主题:暗底玻璃卡片 + 青绿/紫罗兰高光。
- /// </summary>
- internal static class Theme
- {
- public static readonly Color Bg = Color.FromArgb(7, 8, 13);
- public static readonly Color Header = Color.FromArgb(9, 12, 20);
- public static readonly Color HeaderText = Color.FromArgb(238, 244, 255);
- public static readonly Color HeaderMuted = Color.FromArgb(139, 149, 171);
- public static readonly Color Cyan = Color.FromArgb(94, 234, 212);
- public static readonly Color CyanDeep = Color.FromArgb(34, 211, 238);
- public static readonly Color Violet = Color.FromArgb(129, 140, 248);
- public static readonly Color Ink = Color.FromArgb(6, 16, 24);
- public static readonly Color Gold = Color.FromArgb(94, 234, 212);
- public static readonly Color Card = Color.FromArgb(18, 22, 32);
- public static readonly Color Line = Color.FromArgb(48, 80, 92, 108);
- public static readonly Color GhostBorder = Color.FromArgb(70, 255, 255, 255);
- public static readonly Color Text = Color.FromArgb(238, 244, 255);
- public static readonly Color Muted = Color.FromArgb(139, 149, 171);
- public static readonly Color Accent = Color.FromArgb(34, 211, 238);
- public static readonly Color AccentHover = Color.FromArgb(94, 234, 212);
- public static readonly Color Ok = Color.FromArgb(52, 211, 153);
- public static readonly Color Warn = Color.FromArgb(251, 191, 36);
- public static readonly Color Bad = Color.FromArgb(251, 113, 133);
- public static readonly Color Input = Color.FromArgb(8, 10, 16);
- public static readonly Color InputMuted = Color.FromArgb(12, 16, 24);
- public static readonly Color Go = Color.FromArgb(34, 211, 238);
- public static readonly Color LogBg = Color.FromArgb(5, 7, 12);
- public static readonly Color LogText = Color.FromArgb(159, 232, 220);
- public static readonly Color Shadow = Color.FromArgb(90, 0, 0, 0);
- public static readonly Color Mesh = Color.FromArgb(18, 148, 163, 184);
- public static readonly Font Ui = new Font("Microsoft YaHei UI", 10F);
- public static readonly Font Title = new Font("Microsoft YaHei UI", 16F, FontStyle.Bold);
- public static readonly Font Caption = new Font("Microsoft YaHei UI", 8.5F);
- public static readonly Font Section = new Font("Microsoft YaHei UI", 11F, FontStyle.Bold);
- public static readonly Font Mono = new Font("Consolas", 9F);
- public static readonly Font Mark = new Font("Segoe UI", 16F, FontStyle.Bold);
- public static GraphicsPath RoundRect(Rectangle bounds, int radius)
- {
- var path = new GraphicsPath();
- int d = Math.Max(2, radius * 2);
- path.AddArc(bounds.X, bounds.Y, d, d, 180, 90);
- path.AddArc(bounds.Right - d, bounds.Y, d, d, 270, 90);
- path.AddArc(bounds.Right - d, bounds.Bottom - d, d, d, 0, 90);
- path.AddArc(bounds.X, bounds.Bottom - d, d, d, 90, 90);
- path.CloseFigure();
- return path;
- }
- }
- /// <summary>
- /// 暗色玻璃卡片,顶部一道青绿高光。
- /// </summary>
- internal sealed class CardPanel : Panel
- {
- public CardPanel()
- {
- SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
- BackColor = Color.Transparent;
- Padding = new Padding(8);
- Margin = new Padding(0, 0, 0, 12);
- }
- protected override void OnPaint(PaintEventArgs e)
- {
- e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
- var rect = new Rectangle(8, 4, Math.Max(8, Width - 18), Math.Max(8, Height - 12));
- using (GraphicsPath shadow = Theme.RoundRect(new Rectangle(rect.X + 3, rect.Y + 6, rect.Width, rect.Height), 16))
- using (var brush = new SolidBrush(Theme.Shadow))
- {
- e.Graphics.FillPath(brush, shadow);
- }
- using (GraphicsPath path = Theme.RoundRect(rect, 16))
- using (var fill = new SolidBrush(Theme.Card))
- using (var pen = new Pen(Color.FromArgb(55, 94, 234, 212)))
- {
- e.Graphics.FillPath(fill, path);
- e.Graphics.DrawPath(pen, path);
- }
- using (var glow = new Pen(Color.FromArgb(140, 94, 234, 212), 1F))
- {
- e.Graphics.DrawLine(glow, rect.X + 18, rect.Y + 1, rect.Right - 18, rect.Y + 1);
- }
- }
- }
- /// <summary>
- /// 青绿渐变进度条,替代系统默认绿条。
- /// </summary>
- internal sealed class AiProgressBar : Control
- {
- int _value;
- int _phase;
- ProgressBarStyle _style = ProgressBarStyle.Continuous;
- readonly System.Windows.Forms.Timer _tick;
- public int Minimum;
- public int Maximum = 100;
- public AiProgressBar()
- {
- SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
- Height = 8;
- _tick = new System.Windows.Forms.Timer();
- _tick.Interval = 28;
- _tick.Tick += delegate
- {
- _phase = (_phase + 5) % 400;
- Invalidate();
- };
- }
- public int Value
- {
- get { return _value; }
- set
- {
- _value = Math.Max(Minimum, Math.Min(Maximum, value));
- Invalidate();
- }
- }
- public ProgressBarStyle Style
- {
- get { return _style; }
- set
- {
- _style = value;
- if (_style == ProgressBarStyle.Marquee)
- {
- _tick.Start();
- }
- else
- {
- _tick.Stop();
- }
- Invalidate();
- }
- }
- protected override void OnPaint(PaintEventArgs e)
- {
- if (Width < 4 || Height < 4)
- {
- return;
- }
- e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
- var bg = new Rectangle(0, 0, Math.Max(1, Width - 1), Math.Max(1, Height - 1));
- using (GraphicsPath path = Theme.RoundRect(bg, Math.Max(2, Height / 2)))
- using (var fill = new SolidBrush(Color.FromArgb(255, 12, 16, 24)))
- {
- e.Graphics.FillPath(fill, path);
- }
- if (_style == ProgressBarStyle.Marquee)
- {
- int w = Math.Max(48, Width / 3);
- int x = (_phase * (Width + w) / 400) - w;
- using (var lg = new LinearGradientBrush(new Rectangle(x, 0, w, Height), Theme.CyanDeep, Theme.Violet, 0f))
- {
- e.Graphics.FillRectangle(lg, new Rectangle(x, 1, w, Math.Max(1, Height - 2)));
- }
- }
- else if (_value > 0 && Maximum > 0)
- {
- int w = Math.Max(4, (int)((Width - 2) * (_value / (double)Maximum)));
- using (var lg = new LinearGradientBrush(new Rectangle(0, 0, w, Height), Theme.CyanDeep, Theme.Violet, 0f))
- using (GraphicsPath path = Theme.RoundRect(new Rectangle(0, 0, w, Math.Max(1, Height - 1)), Math.Max(2, Height / 2)))
- {
- e.Graphics.FillPath(lg, path);
- }
- }
- }
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- _tick.Dispose();
- }
- base.Dispose(disposing);
- }
- }
- internal sealed class DbConfig
- {
- public string Engine = "mysql";
- public string Host = "127.0.0.1";
- public int Port = 3306;
- public string Database = "mes_cloud";
- public string User = "mes_cloud";
- public string Password = "s2ENj7KaEySTC7dW";
- }
- internal static class DbConfigStore
- {
- public static string ConfigPath(string workDir)
- {
- return Path.Combine(workDir, "db-config.json");
- }
- public static DbConfig Load(string workDir)
- {
- string path = ConfigPath(workDir);
- if (!File.Exists(path))
- {
- return new DbConfig();
- }
- try
- {
- string raw = File.ReadAllText(path, Encoding.UTF8);
- var lines = new List<string>();
- foreach (string line in raw.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None))
- {
- if (!line.TrimStart().StartsWith("#"))
- {
- lines.Add(line);
- }
- }
- string json = string.Join("\n", lines.ToArray()).Trim();
- int start = json.IndexOf('{');
- int end = json.LastIndexOf('}');
- if (start >= 0 && end > start)
- {
- json = json.Substring(start, end - start + 1);
- }
- var cfg = new DbConfig();
- cfg.Host = ReadString(json, "host", cfg.Host);
- cfg.Database = ReadString(json, "database", cfg.Database);
- cfg.User = ReadString(json, "user", cfg.User);
- cfg.Password = ReadString(json, "password", cfg.Password);
- int port = ReadInt(json, "port", cfg.Port);
- if (port < 1 || port > 65535)
- {
- throw new Exception("端口必须在 1-65535");
- }
- cfg.Port = port;
- cfg.Host = NormalizeHost(cfg.Host);
- if (string.IsNullOrWhiteSpace(cfg.User))
- {
- throw new Exception("请填写账号");
- }
- return cfg;
- }
- catch
- {
- return new DbConfig();
- }
- }
- public static void Save(string workDir, DbConfig cfg)
- {
- cfg.Host = NormalizeHost(cfg.Host);
- if (string.IsNullOrWhiteSpace(cfg.User))
- {
- throw new Exception("请填写账号");
- }
- if (cfg.Port < 1 || cfg.Port > 65535)
- {
- throw new Exception("端口必须在 1-65535");
- }
- string json =
- "{\r\n" +
- " \"engine\": \"mysql\",\r\n" +
- " \"host\": \"" + Escape(cfg.Host) + "\",\r\n" +
- " \"port\": " + cfg.Port + ",\r\n" +
- " \"database\": \"" + Escape(cfg.Database) + "\",\r\n" +
- " \"user\": \"" + Escape(cfg.User) + "\",\r\n" +
- " \"password\": \"" + Escape(cfg.Password) + "\"\r\n" +
- "}";
- File.WriteAllText(ConfigPath(workDir), json, new UTF8Encoding(false));
- }
- static string NormalizeHost(string host)
- {
- if (string.IsNullOrWhiteSpace(host))
- {
- return "127.0.0.1";
- }
- host = host.Trim();
- Match jdbc = Regex.Match(host, @"jdbc:mysql://([^:/]+)");
- if (jdbc.Success)
- {
- host = jdbc.Groups[1].Value;
- }
- if (host.StartsWith("mysql://", StringComparison.OrdinalIgnoreCase))
- {
- host = host.Substring(8);
- }
- if (Regex.IsMatch(host, @"\s|/"))
- {
- throw new Exception("IP/主机名不合法");
- }
- return host;
- }
- static string ReadString(string json, string key, string fallback)
- {
- Match m = Regex.Match(json, "\"" + key + "\"\\s*:\\s*\"((?:\\\\.|[^\"\\\\])*)\"");
- if (!m.Success)
- {
- return fallback;
- }
- return Unescape(m.Groups[1].Value);
- }
- static int ReadInt(string json, string key, int fallback)
- {
- Match m = Regex.Match(json, "\"" + key + "\"\\s*:\\s*(-?\\d+)");
- int n;
- if (m.Success && int.TryParse(m.Groups[1].Value, out n))
- {
- return n;
- }
- return fallback;
- }
- static string Escape(string s)
- {
- if (s == null)
- {
- return "";
- }
- return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "\\r").Replace("\n", "\\n");
- }
- static string Unescape(string s)
- {
- var sb = new StringBuilder();
- for (int i = 0; i < s.Length; i++)
- {
- if (s[i] == '\\' && i + 1 < s.Length)
- {
- char n = s[++i];
- if (n == '"') { sb.Append('"'); }
- else if (n == '\\') { sb.Append('\\'); }
- else if (n == 'n') { sb.Append('\n'); }
- else if (n == 'r') { sb.Append('\r'); }
- else if (n == 't') { sb.Append('\t'); }
- else { sb.Append(n); }
- }
- else
- {
- sb.Append(s[i]);
- }
- }
- return sb.ToString();
- }
- }
- internal static class MysqlUtil
- {
- static readonly Dictionary<string, string> Cache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- public static string FindTool(string name)
- {
- if (Cache.ContainsKey(name))
- {
- return Cache[name];
- }
- string exeName = name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ? name : name + ".exe";
- string pathEnv = Environment.GetEnvironmentVariable("PATH") ?? "";
- foreach (string dir in pathEnv.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
- {
- try
- {
- string p = Path.Combine(dir.Trim().Trim('"'), exeName);
- if (File.Exists(p))
- {
- Cache[name] = p;
- return p;
- }
- }
- catch { }
- }
- string[] roots = new[]
- {
- @"C:\Program Files\MySQL",
- @"C:\Program Files (x86)\MySQL",
- @"C:\mysql",
- @"D:\MySQL",
- @"E:\MySQL",
- @"C:\Program Files\MariaDB"
- };
- foreach (string root in roots)
- {
- if (!Directory.Exists(root))
- {
- continue;
- }
- try
- {
- foreach (string file in Directory.GetFiles(root, exeName, SearchOption.AllDirectories))
- {
- string bin = Path.GetFileName(Path.GetDirectoryName(file));
- if (string.Equals(bin, "bin", StringComparison.OrdinalIgnoreCase))
- {
- Cache[name] = file;
- return file;
- }
- }
- }
- catch { }
- }
- return null;
- }
- public static void WriteCnf(DbConfig cfg, string cnfPath)
- {
- string pwd = cfg.Password ?? "";
- pwd = pwd.Replace("\\", "\\\\").Replace("\"", "\\\"");
- string text =
- "[client]\r\n" +
- "host=" + cfg.Host + "\r\n" +
- "port=" + cfg.Port + "\r\n" +
- "user=" + cfg.User + "\r\n" +
- "password=\"" + pwd + "\"\r\n" +
- "default-character-set=utf8mb4\r\n";
- File.WriteAllText(cnfPath, text, Encoding.ASCII);
- }
- public static string TestConnection(DbConfig cfg)
- {
- string mysql = FindTool("mysql");
- if (mysql == null)
- {
- throw new Exception("找不到 mysql.exe,请安装 MySQL 客户端");
- }
- string cnf = Path.Combine(Path.GetTempPath(), "mes_ui_test_" + Process.GetCurrentProcess().Id + ".cnf");
- WriteCnf(cfg, cnf);
- try
- {
- string output;
- int code = Run(mysql, new[]
- {
- "--defaults-extra-file=" + cnf,
- "--connect-timeout=5",
- "-N", "-s", "-e", "SELECT 1"
- }, 15000, out output);
- if (code != 0)
- {
- throw new Exception("连接失败: " + (output ?? "").Trim());
- }
- }
- finally
- {
- TryDelete(cnf);
- }
- return "MySQL 连接成功: " + cfg.Host + ":" + cfg.Port + " / " + cfg.Database;
- }
- public static long GetDbSizeBytes(DbConfig cfg)
- {
- try
- {
- string mysql = FindTool("mysql");
- if (mysql == null)
- {
- return 0;
- }
- string cnf = Path.Combine(Path.GetTempPath(), "mes_ui_size_" + Process.GetCurrentProcess().Id + ".cnf");
- WriteCnf(cfg, cnf);
- try
- {
- string db = (cfg.Database ?? "").Replace("'", "''");
- string sql = "SELECT IFNULL(SUM(data_length+index_length),0) FROM information_schema.tables WHERE table_schema='" + db + "'";
- string output;
- int code = Run(mysql, new[]
- {
- "--defaults-extra-file=" + cnf,
- "--connect-timeout=3",
- "-N", "-s", "-e", sql
- }, 8000, out output);
- if (code != 0)
- {
- return 0;
- }
- long n;
- if (long.TryParse((output ?? "").Trim(), out n))
- {
- return n;
- }
- }
- finally
- {
- TryDelete(cnf);
- }
- }
- catch { }
- return 0;
- }
- static int Run(string file, string[] args, int timeoutMs, out string output)
- {
- var psi = new ProcessStartInfo();
- psi.FileName = file;
- psi.Arguments = QuoteArgs(args);
- psi.UseShellExecute = false;
- psi.CreateNoWindow = true;
- psi.RedirectStandardOutput = true;
- psi.RedirectStandardError = true;
- psi.StandardOutputEncoding = Encoding.UTF8;
- psi.StandardErrorEncoding = Encoding.UTF8;
- using (var proc = new Process())
- {
- proc.StartInfo = psi;
- var sb = new StringBuilder();
- proc.OutputDataReceived += delegate(object s, DataReceivedEventArgs e)
- {
- if (!string.IsNullOrEmpty(e.Data)) { sb.AppendLine(e.Data); }
- };
- proc.ErrorDataReceived += delegate(object s, DataReceivedEventArgs e)
- {
- if (!string.IsNullOrEmpty(e.Data)) { sb.AppendLine(e.Data); }
- };
- proc.Start();
- proc.BeginOutputReadLine();
- proc.BeginErrorReadLine();
- if (!proc.WaitForExit(timeoutMs))
- {
- try { proc.Kill(); } catch { }
- throw new Exception("等待超时");
- }
- proc.WaitForExit();
- output = sb.ToString();
- return proc.ExitCode;
- }
- }
- static string QuoteArgs(string[] args)
- {
- var sb = new StringBuilder();
- for (int i = 0; i < args.Length; i++)
- {
- if (i > 0) { sb.Append(' '); }
- string a = args[i] ?? "";
- sb.Append('"').Append(a.Replace("\"", "\\\"")).Append('"');
- }
- return sb.ToString();
- }
- static void TryDelete(string path)
- {
- try { if (File.Exists(path)) { File.Delete(path); } } catch { }
- }
- }
- internal sealed class MainForm : Form
- {
- static readonly string[] PackedFiles = new string[]
- {
- "backup-ui-server.ps1",
- "backup-db.ps1",
- "db-config.ps1",
- "backup-ui.html",
- "restore-db.ps1",
- "install-backup-task.bat",
- "使用说明.txt"
- };
- readonly string _workDir;
- readonly TextBox _host;
- readonly TextBox _port;
- readonly TextBox _database;
- readonly TextBox _user;
- readonly TextBox _password;
- readonly Label _cfgStatus;
- readonly Label _picked;
- readonly Label _spaceHint;
- readonly Label _progHint;
- readonly Label _runStatus;
- readonly CheckBox _keepOld;
- readonly AiProgressBar _bar;
- readonly TextBox _log;
- readonly Button _btnTest;
- readonly Button _btnSave;
- readonly Button _btnBrowse;
- readonly Button _btnBackup;
- readonly System.Windows.Forms.Timer _poll;
- string _selectedPath = "";
- Process _backupProc;
- string _outFile = "";
- DateTime _startedAt;
- long _dbBytes;
- bool _running;
- public MainForm()
- {
- string exeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
- if (string.IsNullOrEmpty(exeDir))
- {
- exeDir = AppDomain.CurrentDomain.BaseDirectory;
- }
- _workDir = PrepareWorkDir(exeDir);
- Text = "MES 智能备份控制台";
- Font = Theme.Ui;
- BackColor = Theme.Bg;
- ForeColor = Theme.Text;
- StartPosition = FormStartPosition.CenterScreen;
- MinimumSize = new Size(1020, 680);
- Size = new Size(1140, 740);
- DoubleBuffered = true;
- var header = new Panel
- {
- Dock = DockStyle.Top,
- Height = 96,
- BackColor = Theme.Header
- };
- header.Paint += delegate(object s, PaintEventArgs e)
- {
- e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
- e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
- using (var g = new LinearGradientBrush(header.ClientRectangle,
- Color.FromArgb(10, 14, 24), Color.FromArgb(18, 12, 36), LinearGradientMode.Horizontal))
- {
- e.Graphics.FillRectangle(g, header.ClientRectangle);
- }
- using (var glow = new Pen(Color.FromArgb(180, 94, 234, 212), 2F))
- {
- e.Graphics.DrawLine(glow, 0, 0, header.Width, 0);
- }
- using (var line = new Pen(Color.FromArgb(60, 129, 140, 248)))
- {
- e.Graphics.DrawLine(line, 0, header.Height - 1, header.Width, header.Height - 1);
- }
- var markRect = new Rectangle(26, 24, 48, 48);
- using (GraphicsPath path = Theme.RoundRect(markRect, 14))
- using (var g = new LinearGradientBrush(markRect, Theme.Cyan, Theme.Violet, 145f))
- {
- e.Graphics.FillPath(g, path);
- }
- TextRenderer.DrawText(e.Graphics, "M", Theme.Mark, markRect, Theme.Ink,
- TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
- TextRenderer.DrawText(e.Graphics, "NEURAL ARCHIVE", Theme.Caption, new Point(86, 16), Theme.Cyan);
- TextRenderer.DrawText(e.Graphics, "MES 智能备份控制台", Theme.Title, new Point(84, 34), Theme.HeaderText);
- TextRenderer.DrawText(e.Graphics, "独立工具 · 仅 MySQL · 不接入生产系统", Theme.Caption, new Point(86, 66), Theme.HeaderMuted);
- string[] chips = new[] { "AIR-GAPPED", "MYSQL", "LOCAL ONLY" };
- int x = header.Width - 22;
- for (int i = 0; i < chips.Length; i++)
- {
- Size sz = TextRenderer.MeasureText(chips[i], Theme.Caption);
- int w = sz.Width + 16;
- x -= w + 8;
- var chipRect = new Rectangle(x, 36, w, 24);
- using (GraphicsPath path = Theme.RoundRect(chipRect, 12))
- using (var fill = new SolidBrush(Color.FromArgb(40, 255, 255, 255)))
- using (var pen = new Pen(i == 2 ? Color.FromArgb(120, 94, 234, 212) : Color.FromArgb(50, 255, 255, 255)))
- {
- e.Graphics.FillPath(fill, path);
- e.Graphics.DrawPath(pen, path);
- }
- TextRenderer.DrawText(e.Graphics, chips[i], Theme.Caption, chipRect,
- i == 2 ? Theme.Cyan : Theme.Muted,
- TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
- }
- };
- var grid = new TableLayoutPanel();
- grid.Dock = DockStyle.Fill;
- grid.ColumnCount = 2;
- grid.RowCount = 1;
- grid.BackColor = Theme.Bg;
- grid.Padding = new Padding(18, 12, 14, 12);
- grid.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 56F));
- grid.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 44F));
- grid.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
- grid.Paint += PaintMesh;
- _host = MakeInput();
- _port = MakeInput();
- _database = MakeInput();
- _user = MakeInput();
- _password = MakeInput();
- _password.UseSystemPasswordChar = true;
- var engine = MakeInput();
- engine.Text = "MySQL";
- engine.ReadOnly = true;
- engine.BackColor = Theme.InputMuted;
- engine.ForeColor = Theme.Muted;
- engine.TabStop = false;
- var connCard = MakeCard();
- var connBody = MakeBody();
- connBody.RowCount = 4;
- connBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 36));
- connBody.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
- connBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
- connBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 24));
- var connHead = MakeHead("01", "连接矩阵", "先测试,再保存", null);
- var form = new TableLayoutPanel();
- form.Dock = DockStyle.Fill;
- form.ColumnCount = 2;
- form.RowCount = 3;
- form.BackColor = Theme.Card;
- form.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
- form.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
- for (int i = 0; i < 3; i++)
- {
- form.RowStyles.Add(new RowStyle(SizeType.Percent, 33.3F));
- }
- form.Controls.Add(MakeField("类型", engine), 0, 0);
- form.Controls.Add(MakeField("端口", _port), 1, 0);
- form.Controls.Add(MakeField("IP / 主机", _host), 0, 1);
- form.Controls.Add(MakeField("库名", _database), 1, 1);
- form.Controls.Add(MakeField("账号", _user), 0, 2);
- form.Controls.Add(MakeField("密码", _password), 1, 2);
- var connActions = new Panel { Dock = DockStyle.Fill, BackColor = Theme.Card };
- _btnTest = MakeButton("测试连接", false);
- _btnTest.Location = new Point(4, 4);
- _btnSave = MakeGoldButton("保存配置");
- _btnSave.Location = new Point(122, 4);
- _btnTest.Click += OnTest;
- _btnSave.Click += OnSave;
- connActions.Controls.Add(_btnTest);
- connActions.Controls.Add(_btnSave);
- _cfgStatus = new Label
- {
- Dock = DockStyle.Fill,
- TextAlign = ContentAlignment.MiddleLeft,
- ForeColor = Theme.Muted,
- BackColor = Theme.Card,
- Padding = new Padding(6, 0, 0, 0),
- Text = "填写 IP、账号、密码后点「测试连接」"
- };
- connBody.Controls.Add(connHead, 0, 0);
- connBody.Controls.Add(form, 0, 1);
- connBody.Controls.Add(connActions, 0, 2);
- connBody.Controls.Add(_cfgStatus, 0, 3);
- connCard.Controls.Add(connBody);
- var bakCard = MakeCard();
- var bakBody = MakeBody();
- bakBody.RowCount = 7;
- bakBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
- bakBody.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
- bakBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 22));
- bakBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 28));
- bakBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 12));
- bakBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 22));
- bakBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 48));
- _btnBrowse = MakeButton("浏览文件夹", false);
- _btnBrowse.Width = 128;
- _btnBrowse.Click += OnBrowse;
- var bakHead = MakeHead("02", "归档目标", null, _btnBrowse);
- _picked = new Label
- {
- Dock = DockStyle.Fill,
- BackColor = Theme.Input,
- ForeColor = Theme.Muted,
- Text = " 尚未选择保存位置",
- TextAlign = ContentAlignment.MiddleLeft,
- Padding = new Padding(12, 0, 12, 0)
- };
- _picked.Paint += delegate(object s, PaintEventArgs e)
- {
- using (var p = new Pen(Color.FromArgb(90, 94, 234, 212)) { DashStyle = DashStyle.Dash })
- {
- e.Graphics.DrawRectangle(p, 1, 1, _picked.Width - 3, _picked.Height - 3);
- }
- };
- _spaceHint = new Label
- {
- Dock = DockStyle.Fill,
- ForeColor = Theme.Muted,
- BackColor = Theme.Card,
- TextAlign = ContentAlignment.MiddleLeft,
- Text = "插入 U 盘或移动硬盘后选择目录,无需手输路径。"
- };
- _keepOld = new CheckBox
- {
- Text = "同时删除该目录中超过 7 天的旧备份(U 盘建议不勾选)",
- ForeColor = Theme.Muted,
- Dock = DockStyle.Fill,
- BackColor = Theme.Card,
- FlatStyle = FlatStyle.Flat,
- TextAlign = ContentAlignment.MiddleLeft
- };
- _bar = new AiProgressBar
- {
- Dock = DockStyle.Fill,
- Minimum = 0,
- Maximum = 100
- };
- _progHint = new Label
- {
- Dock = DockStyle.Fill,
- ForeColor = Theme.Muted,
- BackColor = Theme.Card,
- TextAlign = ContentAlignment.MiddleLeft,
- Text = ""
- };
- var bakGo = new Panel { Dock = DockStyle.Fill, BackColor = Theme.Card };
- _btnBackup = MakeButton("开始备份", true);
- _btnBackup.Width = 148;
- _btnBackup.Height = 36;
- _btnBackup.Font = new Font("Microsoft YaHei UI", 11F, FontStyle.Bold);
- _btnBackup.Location = new Point(4, 4);
- _btnBackup.Click += OnBackup;
- _runStatus = new Label
- {
- AutoSize = false,
- Location = new Point(164, 10),
- Size = new Size(360, 24),
- Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
- ForeColor = Theme.Muted,
- BackColor = Theme.Card,
- Text = ""
- };
- bakGo.Controls.Add(_btnBackup);
- bakGo.Controls.Add(_runStatus);
- bakBody.Controls.Add(bakHead, 0, 0);
- bakBody.Controls.Add(_picked, 0, 1);
- bakBody.Controls.Add(_spaceHint, 0, 2);
- bakBody.Controls.Add(_keepOld, 0, 3);
- bakBody.Controls.Add(_bar, 0, 4);
- bakBody.Controls.Add(_progHint, 0, 5);
- bakBody.Controls.Add(bakGo, 0, 6);
- bakCard.Controls.Add(bakBody);
- var logCard = MakeCard();
- var logBody = MakeBody();
- logBody.RowCount = 2;
- logBody.RowStyles.Add(new RowStyle(SizeType.Absolute, 36));
- logBody.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
- logBody.Controls.Add(MakeHead("03", "运行控制台", "实时输出", null), 0, 0);
- _log = new TextBox
- {
- Multiline = true,
- ReadOnly = true,
- ScrollBars = ScrollBars.Vertical,
- BorderStyle = BorderStyle.None,
- Dock = DockStyle.Fill,
- BackColor = Theme.LogBg,
- ForeColor = Theme.LogText,
- Font = Theme.Mono,
- Text = "等待操作…"
- };
- logBody.Controls.Add(_log, 0, 1);
- logCard.Controls.Add(logBody);
- var left = new TableLayoutPanel();
- left.Dock = DockStyle.Fill;
- left.BackColor = Color.Transparent;
- left.ColumnCount = 1;
- left.RowCount = 2;
- left.RowStyles.Add(new RowStyle(SizeType.Percent, 54F));
- left.RowStyles.Add(new RowStyle(SizeType.Percent, 46F));
- left.Controls.Add(connCard, 0, 0);
- left.Controls.Add(bakCard, 0, 1);
- grid.Controls.Add(left, 0, 0);
- grid.Controls.Add(logCard, 1, 0);
- Controls.Add(grid);
- Controls.Add(header);
- _poll = new System.Windows.Forms.Timer();
- _poll.Interval = 1000;
- _poll.Tick += OnPoll;
- Load += delegate { LoadConfigToUi(); };
- FormClosing += OnClosing;
- }
- static string PrepareWorkDir(string exeDir)
- {
- string serverScript = Path.Combine(exeDir, "backup-db.ps1");
- string marker = Path.Combine(exeDir, Program.MarkerName);
- bool original = File.Exists(serverScript) && !File.Exists(marker);
- if (!original)
- {
- ExtractPackedFiles(exeDir);
- File.WriteAllText(marker, "packed", Encoding.ASCII);
- }
- return exeDir;
- }
- static void ExtractPackedFiles(string dir)
- {
- Assembly asm = Assembly.GetExecutingAssembly();
- foreach (string name in PackedFiles)
- {
- Stream src = asm.GetManifestResourceStream(name);
- if (src == null)
- {
- continue;
- }
- using (src)
- using (FileStream fs = File.Create(Path.Combine(dir, name)))
- {
- src.CopyTo(fs);
- }
- }
- }
- static CardPanel MakeCard()
- {
- return new CardPanel { Dock = DockStyle.Fill };
- }
- static TableLayoutPanel MakeBody()
- {
- var body = new TableLayoutPanel();
- body.Dock = DockStyle.Fill;
- body.ColumnCount = 1;
- body.BackColor = Theme.Card;
- body.Padding = new Padding(10, 6, 12, 8);
- body.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
- return body;
- }
- /// <summary>
- /// 卡片标题行:序号 + 标题 + 说明,右侧可放按钮。
- /// </summary>
- static Panel MakeHead(string step, string title, string hint, Control right)
- {
- var head = new Panel { Dock = DockStyle.Fill, BackColor = Theme.Card };
- var st = MakeStep(step);
- st.Location = new Point(2, 8);
- var t = SectionLabel(title, 38, 8);
- head.Controls.Add(st);
- head.Controls.Add(t);
- if (!string.IsNullOrEmpty(hint))
- {
- int titleW = TextRenderer.MeasureText(title, Theme.Section).Width;
- var h = new Label
- {
- Text = hint,
- Font = Theme.Caption,
- ForeColor = Theme.Muted,
- AutoSize = true,
- Location = new Point(46 + titleW, 12),
- BackColor = Theme.Card
- };
- head.Controls.Add(h);
- }
- if (right != null)
- {
- right.Anchor = AnchorStyles.Top | AnchorStyles.Right;
- head.Controls.Add(right);
- EventHandler place = delegate
- {
- right.Location = new Point(Math.Max(120, head.Width - right.Width - 4), 4);
- };
- head.Resize += place;
- place(head, EventArgs.Empty);
- }
- return head;
- }
- /// <summary>
- /// 标签在上、输入在下,两列等宽,避免右侧字段被挤没。
- /// </summary>
- static Panel MakeField(string caption, Control input)
- {
- var box = new Panel();
- box.Dock = DockStyle.Fill;
- box.BackColor = Theme.Card;
- box.Margin = new Padding(4, 2, 8, 2);
- box.Padding = new Padding(0, 0, 4, 2);
- var lb = new Label();
- lb.Text = caption;
- lb.ForeColor = Theme.Muted;
- lb.Font = Theme.Caption;
- lb.Dock = DockStyle.Top;
- lb.Height = 20;
- lb.BackColor = Theme.Card;
- lb.TextAlign = ContentAlignment.BottomLeft;
- input.Dock = DockStyle.Fill;
- box.Controls.Add(input);
- box.Controls.Add(lb);
- return box;
- }
- static Label MakeStep(string no)
- {
- return new Label
- {
- Text = no,
- Font = new Font("Segoe UI", 8.5F, FontStyle.Bold),
- ForeColor = Theme.Ink,
- BackColor = Theme.Cyan,
- Size = new Size(28, 22),
- Location = new Point(0, 6),
- TextAlign = ContentAlignment.MiddleCenter
- };
- }
- static void PaintMesh(object sender, PaintEventArgs e)
- {
- var c = sender as Control;
- if (c == null)
- {
- return;
- }
- using (var p = new Pen(Theme.Mesh))
- {
- for (int x = 0; x < c.Width; x += 42)
- {
- e.Graphics.DrawLine(p, x, 0, x, c.Height);
- }
- for (int y = 0; y < c.Height; y += 42)
- {
- e.Graphics.DrawLine(p, 0, y, c.Width, y);
- }
- }
- }
- static Label SectionLabel(string text, int x, int y)
- {
- return new Label
- {
- Text = text,
- Font = Theme.Section,
- ForeColor = Theme.Text,
- AutoSize = true,
- Location = new Point(x, y),
- BackColor = Theme.Card
- };
- }
- static Label FieldLabel(string text)
- {
- return new Label
- {
- Text = text,
- ForeColor = Theme.Muted,
- Dock = DockStyle.Fill,
- TextAlign = ContentAlignment.MiddleLeft,
- BackColor = Theme.Card
- };
- }
- static TextBox MakeInput()
- {
- return new TextBox
- {
- Dock = DockStyle.Fill,
- BorderStyle = BorderStyle.FixedSingle,
- BackColor = Theme.Input,
- ForeColor = Theme.Text,
- Margin = new Padding(0)
- };
- }
- static Button MakeButton(string text, bool primary)
- {
- var b = new Button();
- b.Text = text;
- b.Width = 110;
- b.Height = 32;
- b.FlatStyle = FlatStyle.Flat;
- b.Cursor = Cursors.Hand;
- if (primary)
- {
- b.FlatAppearance.BorderSize = 0;
- b.BackColor = Theme.CyanDeep;
- b.ForeColor = Theme.Ink;
- b.MouseEnter += delegate { if (b.Enabled) { b.BackColor = Theme.Cyan; } };
- b.MouseLeave += delegate { b.BackColor = Theme.CyanDeep; };
- }
- else
- {
- b.FlatAppearance.BorderSize = 1;
- b.FlatAppearance.BorderColor = Color.FromArgb(70, 255, 255, 255);
- b.BackColor = Color.FromArgb(18, 22, 32);
- b.ForeColor = Theme.Text;
- b.MouseEnter += delegate { if (b.Enabled) { b.BackColor = Color.FromArgb(28, 36, 52); } };
- b.MouseLeave += delegate { b.BackColor = Color.FromArgb(18, 22, 32); };
- }
- return b;
- }
- static Button MakeGoldButton(string text)
- {
- var b = new Button();
- b.Text = text;
- b.Width = 110;
- b.Height = 32;
- b.FlatStyle = FlatStyle.Flat;
- b.FlatAppearance.BorderSize = 0;
- b.BackColor = Theme.Cyan;
- b.ForeColor = Theme.Ink;
- b.Cursor = Cursors.Hand;
- b.MouseEnter += delegate { if (b.Enabled) { b.BackColor = Theme.CyanDeep; } };
- b.MouseLeave += delegate { b.BackColor = Theme.Cyan; };
- return b;
- }
- DbConfig ReadUiConfig()
- {
- var cfg = new DbConfig();
- cfg.Host = (_host.Text ?? "").Trim();
- int port;
- if (!int.TryParse((_port.Text ?? "").Trim(), out port))
- {
- port = 3306;
- }
- cfg.Port = port;
- cfg.Database = (_database.Text ?? "").Trim();
- cfg.User = (_user.Text ?? "").Trim();
- cfg.Password = _password.Text ?? "";
- return cfg;
- }
- void LoadConfigToUi()
- {
- DbConfig cfg = DbConfigStore.Load(_workDir);
- _host.Text = cfg.Host;
- _port.Text = cfg.Port.ToString();
- _database.Text = cfg.Database;
- _user.Text = cfg.User;
- _password.Text = cfg.Password;
- }
- void OnTest(object sender, EventArgs e)
- {
- _btnTest.Enabled = false;
- SetCfg("正在测试连接,请稍候…", Theme.Warn);
- DbConfig cfg = ReadUiConfig();
- ThreadPool.QueueUserWorkItem(delegate
- {
- try
- {
- string msg = MysqlUtil.TestConnection(cfg);
- BeginInvoke(new Action(delegate
- {
- SetCfg(msg, Theme.Ok);
- _btnTest.Enabled = true;
- }));
- }
- catch (Exception ex)
- {
- BeginInvoke(new Action(delegate
- {
- SetCfg(ex.Message, Theme.Bad);
- _btnTest.Enabled = true;
- }));
- }
- });
- }
- void OnSave(object sender, EventArgs e)
- {
- try
- {
- DbConfig cfg = ReadUiConfig();
- DbConfigStore.Save(_workDir, cfg);
- SetCfg("已保存 MySQL 配置", Theme.Ok);
- }
- catch (Exception ex)
- {
- SetCfg(ex.Message, Theme.Bad);
- }
- }
- void OnBrowse(object sender, EventArgs e)
- {
- using (var dlg = new FolderBrowserDialog())
- {
- dlg.Description = "选择备份文件保存位置(可选手动插入的 U 盘或移动硬盘)";
- dlg.ShowNewFolderButton = true;
- dlg.RootFolder = Environment.SpecialFolder.MyComputer;
- if (!string.IsNullOrEmpty(_selectedPath) && Directory.Exists(_selectedPath))
- {
- dlg.SelectedPath = _selectedPath;
- }
- if (dlg.ShowDialog(this) != DialogResult.OK)
- {
- return;
- }
- try
- {
- _selectedPath = GetSafePath(dlg.SelectedPath);
- _picked.Text = " " + _selectedPath;
- _picked.ForeColor = Theme.Text;
- long free = GetFreeBytes(_selectedPath);
- if (free > 0)
- {
- _spaceHint.Text = "该磁盘剩余 " + Gb(free) + " GB。";
- }
- SetRun("已选择保存位置", Theme.Ok);
- }
- catch (Exception ex)
- {
- SetRun(ex.Message, Theme.Bad);
- }
- }
- }
- void OnBackup(object sender, EventArgs e)
- {
- if (string.IsNullOrWhiteSpace(_selectedPath))
- {
- SetRun("请先点「浏览文件夹」选择保存位置", Theme.Bad);
- return;
- }
- string script = Path.Combine(_workDir, "backup-db.ps1");
- if (!File.Exists(script))
- {
- SetRun("找不到 backup-db.ps1", Theme.Bad);
- return;
- }
- try
- {
- DbConfig cfg = ReadUiConfig();
- DbConfigStore.Save(_workDir, cfg);
- string dir = GetSafePath(_selectedPath);
- long free = GetFreeBytes(dir);
- _dbBytes = MysqlUtil.GetDbSizeBytes(cfg);
- if (_dbBytes > 0 && free > 0 && free < (long)(_dbBytes * 1.2))
- {
- throw new Exception("目标盘剩余空间不足:约需 " + Gb((long)(_dbBytes * 1.2)) +
- " GB,当前剩余 " + Gb(free) + " GB。请换更大的 U 盘/硬盘。");
- }
- if (!Directory.Exists(dir))
- {
- Directory.CreateDirectory(dir);
- }
- int keep = _keepOld.Checked ? 7 : 0;
- string powershell = Path.Combine(Environment.SystemDirectory, @"WindowsPowerShell\v1.0\powershell.exe");
- if (!File.Exists(powershell))
- {
- powershell = "powershell.exe";
- }
- _outFile = Path.Combine(Path.GetTempPath(), "mes_backup_ui_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".log");
- string errFile = _outFile + ".err";
- TryDelete(_outFile);
- TryDelete(errFile);
- var psi = new ProcessStartInfo();
- psi.FileName = powershell;
- psi.Arguments = "-NoProfile -STA -ExecutionPolicy Bypass -File \"" + script +
- "\" -BackupDir \"" + dir + "\" -KeepDaysParam " + keep;
- psi.WorkingDirectory = _workDir;
- psi.UseShellExecute = false;
- psi.CreateNoWindow = true;
- psi.RedirectStandardOutput = true;
- psi.RedirectStandardError = true;
- psi.StandardOutputEncoding = Encoding.UTF8;
- psi.StandardErrorEncoding = Encoding.UTF8;
- _backupProc = new Process();
- _backupProc.StartInfo = psi;
- _backupProc.EnableRaisingEvents = true;
- _backupProc.OutputDataReceived += OnBackupOutput;
- _backupProc.ErrorDataReceived += OnBackupOutput;
- if (!_backupProc.Start())
- {
- throw new Exception("无法启动备份进程");
- }
- _backupProc.BeginOutputReadLine();
- _backupProc.BeginErrorReadLine();
- _running = true;
- _startedAt = DateTime.Now;
- _selectedPath = dir;
- _btnBackup.Enabled = false;
- _bar.Style = ProgressBarStyle.Marquee;
- _bar.Value = 0;
- SetLog(new[] { "开始备份到: " + dir });
- if (_dbBytes > 0)
- {
- AppendLog("库数据约 " + Gb(_dbBytes) + " GB,采用边导出边压缩,请耐心等待,勿拔盘");
- }
- SetRun("正在备份,数据量大时可能需要较长时间...", Theme.Warn);
- _poll.Start();
- }
- catch (Exception ex)
- {
- SetRun(ex.Message, Theme.Bad);
- _btnBackup.Enabled = true;
- }
- }
- void OnBackupOutput(object sender, DataReceivedEventArgs e)
- {
- if (string.IsNullOrEmpty(e.Data))
- {
- return;
- }
- BeginInvoke(new Action(delegate { AppendLog(e.Data); }));
- }
- void OnPoll(object sender, EventArgs e)
- {
- if (!_running)
- {
- return;
- }
- long written;
- string fileName;
- GetWrittenBackup(_selectedPath, out written, out fileName);
- if (_dbBytes > 0 && written > 1024)
- {
- _bar.Style = ProgressBarStyle.Continuous;
- int pct = (int)Math.Max(2, Math.Min(99, written * 100 / _dbBytes));
- _bar.Value = pct;
- }
- _progHint.Text = "已写入 " + Mb(written) + " MB" + (string.IsNullOrEmpty(fileName) ? "" : "(" + fileName + ")") + ",请勿拔盘";
- Process p = _backupProc;
- bool exited = false;
- int code = -1;
- if (p == null)
- {
- exited = true;
- }
- else
- {
- try
- {
- p.Refresh();
- exited = p.HasExited;
- if (exited)
- {
- try { p.WaitForExit(500); } catch { }
- try { code = p.ExitCode; } catch { code = -1; }
- }
- }
- catch
- {
- exited = true;
- }
- }
- if (!exited)
- {
- return;
- }
- _poll.Stop();
- _running = false;
- _btnBackup.Enabled = true;
- _bar.Style = ProgressBarStyle.Continuous;
- bool fileOk = TestBackupFileOk(_selectedPath, _startedAt);
- bool success = (code == 0) || fileOk;
- if (success)
- {
- _bar.Value = 100;
- GetWrittenBackup(_selectedPath, out written, out fileName);
- _progHint.Text = "完成,文件约 " + Mb(written) + " MB:" + (fileName ?? "");
- AppendLog("备份完成");
- SetRun("备份完成", Theme.Ok);
- }
- else
- {
- string codeText = code < 0 ? "未知" : code.ToString();
- string err = "备份失败,退出码 " + codeText;
- AppendLog(err);
- SetRun(err, Theme.Bad);
- }
- _backupProc = null;
- }
- void OnClosing(object sender, FormClosingEventArgs e)
- {
- if (_running)
- {
- DialogResult r = MessageBox.Show(this,
- "备份正在进行,关闭窗口会中断备份。确定退出吗?",
- "MES 数据库备份",
- MessageBoxButtons.YesNo,
- MessageBoxIcon.Warning);
- if (r != DialogResult.Yes)
- {
- e.Cancel = true;
- return;
- }
- }
- _poll.Stop();
- Process p = _backupProc;
- _backupProc = null;
- if (p != null)
- {
- try
- {
- if (!p.HasExited)
- {
- KillProcessTree(p.Id);
- }
- }
- catch { }
- try { p.Dispose(); } catch { }
- }
- }
- static void GetWrittenBackup(string dir, out long bytes, out string name)
- {
- bytes = 0;
- name = "";
- if (string.IsNullOrEmpty(dir) || !Directory.Exists(dir))
- {
- return;
- }
- FileInfo pick = null;
- try
- {
- var files = new List<FileInfo>(new DirectoryInfo(dir).GetFiles("mes_cloud_*.sql.gz*"));
- files.Sort(delegate(FileInfo a, FileInfo b) { return b.LastWriteTime.CompareTo(a.LastWriteTime); });
- foreach (FileInfo f in files)
- {
- if (f.Name.IndexOf(".tmp", StringComparison.OrdinalIgnoreCase) < 0)
- {
- pick = f;
- break;
- }
- }
- if (pick == null && files.Count > 0)
- {
- pick = files[0];
- }
- }
- catch { }
- if (pick != null)
- {
- name = pick.Name;
- bytes = pick.Length;
- }
- }
- static bool TestBackupFileOk(string dir, DateTime started)
- {
- if (string.IsNullOrEmpty(dir) || !Directory.Exists(dir))
- {
- return false;
- }
- FileInfo gz = null;
- try
- {
- foreach (FileInfo f in new DirectoryInfo(dir).GetFiles("mes_cloud_*.sql.gz"))
- {
- if (gz == null || f.LastWriteTime > gz.LastWriteTime)
- {
- gz = f;
- }
- }
- }
- catch { return false; }
- if (gz == null || gz.Length <= 100)
- {
- return false;
- }
- if (gz.LastWriteTime < started.AddMinutes(-2))
- {
- return false;
- }
- return true;
- }
- static string GetSafePath(string path)
- {
- if (string.IsNullOrWhiteSpace(path))
- {
- throw new Exception("请先选择保存位置");
- }
- string p = path.Trim().Trim('"');
- if (!Regex.IsMatch(p, @"^[A-Za-z]:\\"))
- {
- throw new Exception("只允许本机磁盘路径");
- }
- string full = Path.GetFullPath(p);
- if (Regex.IsMatch(full, @"^[A-Za-z]:\\?$"))
- {
- return char.ToUpperInvariant(full[0]) + @":\";
- }
- return full.TrimEnd('\\');
- }
- static long GetFreeBytes(string path)
- {
- try
- {
- string full = GetSafePath(path);
- string root = Path.GetPathRoot(full);
- var di = new DriveInfo(root);
- if (di.IsReady)
- {
- return di.AvailableFreeSpace;
- }
- }
- catch { }
- return 0;
- }
- static void KillProcessTree(int pid)
- {
- try
- {
- var psi = new ProcessStartInfo();
- psi.FileName = "taskkill";
- psi.Arguments = "/PID " + pid + " /T /F";
- psi.CreateNoWindow = true;
- psi.UseShellExecute = false;
- using (Process k = Process.Start(psi))
- {
- if (k != null)
- {
- k.WaitForExit(5000);
- }
- }
- }
- catch { }
- }
- static void TryDelete(string path)
- {
- try { if (File.Exists(path)) { File.Delete(path); } } catch { }
- }
- static string Gb(long n)
- {
- return (n / 1073741824.0).ToString("0.00");
- }
- static string Mb(long n)
- {
- return (n / 1048576.0).ToString("0.0");
- }
- void SetCfg(string text, Color color)
- {
- _cfgStatus.Text = text ?? "";
- _cfgStatus.ForeColor = color;
- }
- void SetRun(string text, Color color)
- {
- _runStatus.Text = text ?? "";
- _runStatus.ForeColor = color;
- }
- void SetLog(string[] lines)
- {
- _log.Text = (lines != null && lines.Length > 0) ? string.Join(Environment.NewLine, lines) : "等待操作…";
- _log.SelectionStart = _log.TextLength;
- _log.ScrollToCaret();
- }
- void AppendLog(string line)
- {
- if (_log.Text == "等待操作…" || _log.Text == "等待操作...")
- {
- _log.Text = "";
- }
- if (_log.TextLength > 60000)
- {
- _log.Text = _log.Text.Substring(_log.TextLength - 40000);
- }
- _log.AppendText(line + Environment.NewLine);
- }
- }
- }
|