From f3bb505cfeb0d46df9865b5cd119ba20e11470f9 Mon Sep 17 00:00:00 2001 From: zmjack Date: Fri, 10 Mar 2023 03:32:22 +0800 Subject: [PATCH] PortProxyGUI.1.4.0 --- PortProxyGUI/About.cs | 5 +- PortProxyGUI/Data/AppConfig.cs | 53 ++++ PortProxyGUI/Data/ApplicationDbScope.cs | 57 +++- PortProxyGUI/Data/Config.cs | 9 + ...ionDbMigrationUtil.cs => MigrationUtil.cs} | 28 +- PortProxyGUI/Data/Rule.cs | 5 + PortProxyGUI/Native/GenericRights.cs | 10 + PortProxyGUI/Native/NativeMethods.cs | 22 ++ PortProxyGUI/Native/ServiceControls.cs | 10 + PortProxyGUI/Native/ServiceRights.cs | 10 + PortProxyGUI/Native/ServiceStatus.cs | 16 + PortProxyGUI/PortProxyGUI.Designer.cs | 255 ++++++++------- PortProxyGUI/PortProxyGUI.cs | 152 ++++++--- PortProxyGUI/PortProxyGUI.csproj | 4 +- PortProxyGUI/PortProxyGUI.resx | 296 +++++++++++++----- PortProxyGUI/PortProxyGUI.zh-CN.resx | 32 +- PortProxyGUI/Program.cs | 7 +- PortProxyGUI/SetProxy.cs | 18 +- .../{~DS => UI}/ListViewColumnSorter.cs | 4 +- PortProxyGUI/Utils/DnsUtil.cs | 18 ++ .../{~DS/Util.cs => Utils/InterfaceUtil.cs} | 5 +- PortProxyGUI/Utils/PortPorxyUtil.cs | 114 +++++++ PortProxyGUI/~DS/CmdRunner.cs | 27 -- PortProxyGUI/~DS/CmdUtil.cs | 89 ------ PortProxyGUI/~DS/PortProxyUtil.cs | 71 ----- PortProxyGUI/~DS/ProxyType.cs | 28 -- docs/ui.png | Bin 18279 -> 17868 bytes 27 files changed, 867 insertions(+), 478 deletions(-) create mode 100644 PortProxyGUI/Data/AppConfig.cs create mode 100644 PortProxyGUI/Data/Config.cs rename PortProxyGUI/Data/{ApplicationDbMigrationUtil.cs => MigrationUtil.cs} (82%) create mode 100644 PortProxyGUI/Native/GenericRights.cs create mode 100644 PortProxyGUI/Native/NativeMethods.cs create mode 100644 PortProxyGUI/Native/ServiceControls.cs create mode 100644 PortProxyGUI/Native/ServiceRights.cs create mode 100644 PortProxyGUI/Native/ServiceStatus.cs rename PortProxyGUI/{~DS => UI}/ListViewColumnSorter.cs (98%) create mode 100644 PortProxyGUI/Utils/DnsUtil.cs rename PortProxyGUI/{~DS/Util.cs => Utils/InterfaceUtil.cs} (86%) create mode 100644 PortProxyGUI/Utils/PortPorxyUtil.cs delete mode 100644 PortProxyGUI/~DS/CmdRunner.cs delete mode 100644 PortProxyGUI/~DS/CmdUtil.cs delete mode 100644 PortProxyGUI/~DS/PortProxyUtil.cs delete mode 100644 PortProxyGUI/~DS/ProxyType.cs diff --git a/PortProxyGUI/About.cs b/PortProxyGUI/About.cs index 9f52ee9..709bd99 100644 --- a/PortProxyGUI/About.cs +++ b/PortProxyGUI/About.cs @@ -1,4 +1,5 @@ -using System; +using PortProxyGUI.Utils; +using System; using System.Diagnostics; using System.Drawing; using System.Windows.Forms; @@ -14,7 +15,7 @@ namespace PortProxyGUI PortProxyGUI = portProxyGUI; InitializeComponent(); - Font = Util.UiFont; + Font = InterfaceUtil.UiFont; label_version.Text = label_version.Text + " v" + Application.ProductVersion; } diff --git a/PortProxyGUI/Data/AppConfig.cs b/PortProxyGUI/Data/AppConfig.cs new file mode 100644 index 0000000..7beae1b --- /dev/null +++ b/PortProxyGUI/Data/AppConfig.cs @@ -0,0 +1,53 @@ +using System; +using System.Drawing; +using System.Linq; +using System.Text.RegularExpressions; + +namespace PortProxyGUI.Data +{ + public class AppConfig + { + public Size MainWindowSize = new(720, 500); + public int[] PortProxyColumnWidths = new int[] { 24, 64, 140, 100, 140, 100, 100 }; + + private readonly Regex _intArrayRegex = new(@"^\[\s*(\d+)(?:\s*,\s*(\d+))*\s*\]$"); + + public AppConfig() { } + public AppConfig(Config[] rows) + { + { + var item = rows.Where(x => x.Item == "MainWindow"); + if (int.TryParse(item.FirstOrDefault(x => x.Key == "Width")?.Value, out var width) + && int.TryParse(item.FirstOrDefault(x => x.Key == "Height")?.Value, out var height)) + { + MainWindowSize = new Size(width, height); + } + else MainWindowSize = new Size(720, 500); + } + + { + var item = rows.Where(x => x.Item == "PortProxy"); + var s_ColumnWidths = item.FirstOrDefault(x => x.Key == "ColumnWidths").Value; + var match = _intArrayRegex.Match(s_ColumnWidths); + + if (match.Success) + { + PortProxyColumnWidths = match.Groups + .OfType().Skip(1) + .SelectMany(x => x.Captures.OfType()) + .Select(x => int.Parse(x.Value)) + .ToArray(); + } + else + { +#if NETCOREAPP3_0_OR_GREATER + PortProxyColumnWidths = Array.Empty(); +#else + PortProxyColumnWidths = new int[0]; +#endif + } + } + } + + } +} diff --git a/PortProxyGUI/Data/ApplicationDbScope.cs b/PortProxyGUI/Data/ApplicationDbScope.cs index 856dd3f..99c8a0a 100644 --- a/PortProxyGUI/Data/ApplicationDbScope.cs +++ b/PortProxyGUI/Data/ApplicationDbScope.cs @@ -1,4 +1,5 @@ -using SQLib.Sqlite; +using NStandard; +using SQLib.Sqlite; using System; using System.Collections.Generic; using System.IO; @@ -8,11 +9,27 @@ namespace PortProxyGUI.Data { public class ApplicationDbScope : SqliteScope { - public static readonly string DbDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "PortProxyGUI"); - public static readonly string DbFile = Path.Combine(DbDirectory, "config.db"); - private static readonly string ConnectionString = $"Data Source={DbFile}"; + public static readonly string AppDbDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "PortProxyGUI"); + public static readonly string AppDbFile = Path.Combine(AppDbDirectory, "config.db"); - public static ApplicationDbScope UseDefault() => new ApplicationDbScope(ConnectionString); + public static ApplicationDbScope FromFile(string file) + { + var dir = Path.GetDirectoryName(file); + var fileName = Path.GetFileName(file); + + if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); + if (!File.Exists(fileName)) + { +#if NETCOREAPP3_0_OR_GREATER +#else + System.Data.SQLite.SQLiteConnection.CreateFile(file); +#endif + } + + var scope = new ApplicationDbScope($"Data Source=\"{file}\""); + scope.Migrate(); + return scope; + } public ApplicationDbScope(string connectionString) : base(connectionString) { @@ -20,16 +37,14 @@ namespace PortProxyGUI.Data public override void Initialize() { - if (!Directory.Exists(DbDirectory)) Directory.CreateDirectory(DbDirectory); - if (!File.Exists(DbFile)) - { -#if NET35 || NET45 - System.Data.SQLite.SQLiteConnection.CreateFile(DbFile); -#endif - } } - public void Migrate() => new ApplicationDbMigrationUtil(this).MigrateToLast(); + public void Migrate() => new MigrationUtil(this).MigrateToLast(); + + public Migration GetLastMigration() + { + return SqlQuery($"SELECT * FROM __history ORDER BY MigrationId DESC LIMIT 1;").First(); + } public IEnumerable Rules => SqlQuery($"SELECT * FROM Rules;"); @@ -79,5 +94,21 @@ namespace PortProxyGUI.Data foreach (var obj in objs) Remove(obj); } + public AppConfig GetAppConfig() + { + var configRows = SqlQuery($"SELECT * FROM Configs;"); + var appConfig = new AppConfig(configRows); + return appConfig; + } + + public void SaveAppConfig(AppConfig appConfig) + { + Sql($"UPDATE Configs SET Value = {appConfig.MainWindowSize.Width} WHERE Item = 'MainWindow' AND `Key` = 'Width';"); + Sql($"UPDATE Configs SET Value = {appConfig.MainWindowSize.Height} WHERE Item = 'MainWindow' AND `Key` = 'Height';"); + + var s_portProxyColumnWidths = $"[{appConfig.PortProxyColumnWidths.Select(x => x.ToString()).Join(", ")}]"; + Sql($"UPDATE Configs SET Value = {s_portProxyColumnWidths} WHERE Item = 'PortProxy' AND `Key` = 'ColumnWidths';"); + } + } } diff --git a/PortProxyGUI/Data/Config.cs b/PortProxyGUI/Data/Config.cs new file mode 100644 index 0000000..c8bb6d8 --- /dev/null +++ b/PortProxyGUI/Data/Config.cs @@ -0,0 +1,9 @@ +namespace PortProxyGUI.Data +{ + public class Config + { + public string Item { get; set; } + public string Key { get; set; } + public string Value { get; set; } + } +} diff --git a/PortProxyGUI/Data/ApplicationDbMigrationUtil.cs b/PortProxyGUI/Data/MigrationUtil.cs similarity index 82% rename from PortProxyGUI/Data/ApplicationDbMigrationUtil.cs rename to PortProxyGUI/Data/MigrationUtil.cs index 41c6b9a..3dec1e0 100644 --- a/PortProxyGUI/Data/ApplicationDbMigrationUtil.cs +++ b/PortProxyGUI/Data/MigrationUtil.cs @@ -7,11 +7,11 @@ using System.Windows.Forms; namespace PortProxyGUI.Data { - public class ApplicationDbMigrationUtil + public class MigrationUtil { public ApplicationDbScope DbScope { get; private set; } - public ApplicationDbMigrationUtil(ApplicationDbScope context) + public MigrationUtil(ApplicationDbScope context) { DbScope = context; EnsureHistoryTable(); @@ -29,7 +29,7 @@ namespace PortProxyGUI.Data public void EnsureUpdateVersion() { - var migration = GetLastMigration(); + var migration = DbScope.GetLastMigration(); var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version; if (new Version(migration.ProductVersion) > assemblyVersion) @@ -47,14 +47,9 @@ Would you like to download it now?", "Upgrade", MessageBoxButtons.YesNo, Message } } - public Migration GetLastMigration() - { - return DbScope.SqlQuery($"SELECT * FROM __history ORDER BY MigrationId DESC LIMIT 1;").First(); - } - public void MigrateToLast() { - var migration = GetLastMigration(); + var migration = DbScope.GetLastMigration(); var migrationId = migration.MigrationId; var pendingMigrations = migrationId != "000000000000" ? History.SkipWhile(pair => pair.Key.MigrationId != migrationId).Skip(1) @@ -112,6 +107,21 @@ Would you like to download it now?", "Upgrade", MessageBoxButtons.YesNo, Message "INSERT INTO rules SELECT Id, Type, ListenOn, ListenPort, ConnectTo, ConnectPort, Note, `Group` FROM rulesOld;", "DROP TABLE rulesOld;", }, + + [new MigrationKey { MigrationId = "202303092024", ProductVersion = "1.4.0" }] = new[] + { + @"CREATE TABLE configs ( + Item text, + `Key` text, + Value text +);", + +"CREATE UNIQUE INDEX IX_Configs_Key ON configs ( Item, `Key` );", + +"INSERT INTO configs ( Item, `Key`, Value ) VALUES ( 'MainWindow', 'Width', '720' );", +"INSERT INTO configs ( Item, `Key`, Value ) VALUES ( 'MainWindow', 'Height', '500' );", +"INSERT INTO configs ( Item, `Key`, Value ) VALUES ( 'PortProxy', 'ColumnWidths', '[24, 64, 140, 100, 140, 100, 100]' );", + }, }; } } diff --git a/PortProxyGUI/Data/Rule.cs b/PortProxyGUI/Data/Rule.cs index a777399..9e0d491 100644 --- a/PortProxyGUI/Data/Rule.cs +++ b/PortProxyGUI/Data/Rule.cs @@ -60,5 +60,10 @@ namespace PortProxyGUI.Data if (int.TryParse(portString, out var port) && 0 < port && port < 65536) return port; else throw new NotSupportedException($"Invalid port string. ({portString})"); } + + public override bool Equals(object obj) + { + return Equals(obj as Rule); + } } } diff --git a/PortProxyGUI/Native/GenericRights.cs b/PortProxyGUI/Native/GenericRights.cs new file mode 100644 index 0000000..fdaad98 --- /dev/null +++ b/PortProxyGUI/Native/GenericRights.cs @@ -0,0 +1,10 @@ +using System; + +namespace PortProxyGUI.Native +{ + [Flags] + public enum GenericRights : uint + { + GENERIC_READ = 0x80000000, + } +} diff --git a/PortProxyGUI/Native/NativeMethods.cs b/PortProxyGUI/Native/NativeMethods.cs new file mode 100644 index 0000000..7f442f1 --- /dev/null +++ b/PortProxyGUI/Native/NativeMethods.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.InteropServices; + +namespace PortProxyGUI.Native +{ + internal class NativeMethods + { + [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess); + + [DllImport("advapi32.dll", EntryPoint = "OpenServiceW", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, ServiceRights dwDesiredAccess); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ControlService(IntPtr hService, ServiceControls dwControl, ref ServiceStatus lpServiceStatus); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseServiceHandle(IntPtr hSCObject); + } +} diff --git a/PortProxyGUI/Native/ServiceControls.cs b/PortProxyGUI/Native/ServiceControls.cs new file mode 100644 index 0000000..a4b2b32 --- /dev/null +++ b/PortProxyGUI/Native/ServiceControls.cs @@ -0,0 +1,10 @@ +using System; + +namespace PortProxyGUI.Native +{ + [Flags] + public enum ServiceControls : uint + { + SERVICE_CONTROL_PARAMCHANGE = 0x00000006, + } +} diff --git a/PortProxyGUI/Native/ServiceRights.cs b/PortProxyGUI/Native/ServiceRights.cs new file mode 100644 index 0000000..104a09d --- /dev/null +++ b/PortProxyGUI/Native/ServiceRights.cs @@ -0,0 +1,10 @@ +using System; + +namespace PortProxyGUI.Native +{ + [Flags] + public enum ServiceRights : uint + { + SERVICE_PAUSE_CONTINUE = 0x0040, + } +} diff --git a/PortProxyGUI/Native/ServiceStatus.cs b/PortProxyGUI/Native/ServiceStatus.cs new file mode 100644 index 0000000..3ad590f --- /dev/null +++ b/PortProxyGUI/Native/ServiceStatus.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace PortProxyGUI.Native +{ + [StructLayout(LayoutKind.Sequential)] + public struct ServiceStatus + { + public uint dwServiceType; + public uint dwCurrentState; + public uint dwControlsAccepted; + public uint dwWin32ExitCode; + public uint dwServiceSpecificExitCode; + public uint dwCheckPoint; + public uint dwWaitHint; + } +} diff --git a/PortProxyGUI/PortProxyGUI.Designer.cs b/PortProxyGUI/PortProxyGUI.Designer.cs index 7c0962c..b405866 100644 --- a/PortProxyGUI/PortProxyGUI.Designer.cs +++ b/PortProxyGUI/PortProxyGUI.Designer.cs @@ -28,184 +28,219 @@ /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); + components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PortProxyGUI)); - this.listViewProxies = new System.Windows.Forms.ListView(); - this.columnHeader1 = new System.Windows.Forms.ColumnHeader(); - this.columnHeader2 = new System.Windows.Forms.ColumnHeader(); - this.columnHeader3 = new System.Windows.Forms.ColumnHeader(); - this.columnHeader4 = new System.Windows.Forms.ColumnHeader(); - this.columnHeader5 = new System.Windows.Forms.ColumnHeader(); - this.columnHeader6 = new System.Windows.Forms.ColumnHeader(); - this.columnHeader7 = new System.Windows.Forms.ColumnHeader(); - this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); - this.toolStripMenuItem_Enable = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem_Disable = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); - this.toolStripMenuItem_Refresh = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem_FlushDnsCache = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); - this.toolStripMenuItem_New = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem_Modify = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem_Delete = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); - this.toolStripMenuItem_About = new System.Windows.Forms.ToolStripMenuItem(); - this.imageListProxies = new System.Windows.Forms.ImageList(this.components); - this.contextMenuStrip1.SuspendLayout(); - this.SuspendLayout(); + listViewProxies = new System.Windows.Forms.ListView(); + columnHeader1 = new System.Windows.Forms.ColumnHeader(); + columnHeader2 = new System.Windows.Forms.ColumnHeader(); + columnHeader3 = new System.Windows.Forms.ColumnHeader(); + columnHeader4 = new System.Windows.Forms.ColumnHeader(); + columnHeader5 = new System.Windows.Forms.ColumnHeader(); + columnHeader6 = new System.Windows.Forms.ColumnHeader(); + columnHeader7 = new System.Windows.Forms.ColumnHeader(); + contextMenuStrip_RightClick = new System.Windows.Forms.ContextMenuStrip(components); + toolStripMenuItem_Enable = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem_Disable = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + toolStripMenuItem_Refresh = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem_FlushDnsCache = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + toolStripMenuItem_New = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem_Modify = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem_Delete = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + toolStripMenuItem_More = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem_Import = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem_Export = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + toolStripMenuItem_About = new System.Windows.Forms.ToolStripMenuItem(); + imageListProxies = new System.Windows.Forms.ImageList(components); + saveFileDialog_Export = new System.Windows.Forms.SaveFileDialog(); + openFileDialog_Import = new System.Windows.Forms.OpenFileDialog(); + toolStripMenuItem_ResetWindowSize = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); + contextMenuStrip_RightClick.SuspendLayout(); + SuspendLayout(); // // listViewProxies // - this.listViewProxies.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.listViewProxies.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeader1, - this.columnHeader2, - this.columnHeader3, - this.columnHeader4, - this.columnHeader5, - this.columnHeader6, - this.columnHeader7}); - this.listViewProxies.ContextMenuStrip = this.contextMenuStrip1; - resources.ApplyResources(this.listViewProxies, "listViewProxies"); - this.listViewProxies.FullRowSelect = true; - this.listViewProxies.HideSelection = false; - this.listViewProxies.Name = "listViewProxies"; - this.listViewProxies.SmallImageList = this.imageListProxies; - this.listViewProxies.UseCompatibleStateImageBehavior = false; - this.listViewProxies.View = System.Windows.Forms.View.Details; - this.listViewProxies.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.listView1_ColumnClick); - this.listViewProxies.DoubleClick += new System.EventHandler(this.listView1_DoubleClick); - this.listViewProxies.KeyUp += new System.Windows.Forms.KeyEventHandler(this.listViewProxies_KeyUp); - this.listViewProxies.MouseUp += new System.Windows.Forms.MouseEventHandler(this.listView1_MouseUp); + listViewProxies.BorderStyle = System.Windows.Forms.BorderStyle.None; + listViewProxies.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { columnHeader1, columnHeader2, columnHeader3, columnHeader4, columnHeader5, columnHeader6, columnHeader7 }); + listViewProxies.ContextMenuStrip = contextMenuStrip_RightClick; + resources.ApplyResources(listViewProxies, "listViewProxies"); + listViewProxies.FullRowSelect = true; + listViewProxies.HideSelection = false; + listViewProxies.Name = "listViewProxies"; + listViewProxies.SmallImageList = imageListProxies; + listViewProxies.UseCompatibleStateImageBehavior = false; + listViewProxies.View = System.Windows.Forms.View.Details; + listViewProxies.ColumnClick += listView1_ColumnClick; + listViewProxies.ColumnWidthChanged += listViewProxies_ColumnWidthChanged; + listViewProxies.DoubleClick += listView1_DoubleClick; + listViewProxies.KeyUp += listViewProxies_KeyUp; + listViewProxies.MouseUp += listView1_MouseUp; // // columnHeader1 // - resources.ApplyResources(this.columnHeader1, "columnHeader1"); + resources.ApplyResources(columnHeader1, "columnHeader1"); // // columnHeader2 // - resources.ApplyResources(this.columnHeader2, "columnHeader2"); + resources.ApplyResources(columnHeader2, "columnHeader2"); // // columnHeader3 // - resources.ApplyResources(this.columnHeader3, "columnHeader3"); + resources.ApplyResources(columnHeader3, "columnHeader3"); // // columnHeader4 // - this.columnHeader4.Tag = ""; - resources.ApplyResources(this.columnHeader4, "columnHeader4"); + columnHeader4.Tag = ""; + resources.ApplyResources(columnHeader4, "columnHeader4"); // // columnHeader5 // - resources.ApplyResources(this.columnHeader5, "columnHeader5"); + resources.ApplyResources(columnHeader5, "columnHeader5"); // // columnHeader6 // - this.columnHeader6.Tag = ""; - resources.ApplyResources(this.columnHeader6, "columnHeader6"); + columnHeader6.Tag = ""; + resources.ApplyResources(columnHeader6, "columnHeader6"); // // columnHeader7 // - resources.ApplyResources(this.columnHeader7, "columnHeader7"); + resources.ApplyResources(columnHeader7, "columnHeader7"); // - // contextMenuStrip1 + // contextMenuStrip_RightClick // - this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.toolStripMenuItem_Enable, - this.toolStripMenuItem_Disable, - this.toolStripSeparator3, - this.toolStripMenuItem_Refresh, - this.toolStripMenuItem_FlushDnsCache, - this.toolStripSeparator2, - this.toolStripMenuItem_New, - this.toolStripMenuItem_Modify, - this.toolStripMenuItem_Delete, - this.toolStripSeparator1, - this.toolStripMenuItem_About}); - this.contextMenuStrip1.Name = "contextMenuStrip1"; - resources.ApplyResources(this.contextMenuStrip1, "contextMenuStrip1"); - this.contextMenuStrip1.MouseClick += new System.Windows.Forms.MouseEventHandler(this.contextMenuStrip1_MouseClick); + contextMenuStrip_RightClick.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripMenuItem_Enable, toolStripMenuItem_Disable, toolStripSeparator3, toolStripMenuItem_Refresh, toolStripMenuItem_FlushDnsCache, toolStripSeparator2, toolStripMenuItem_New, toolStripMenuItem_Modify, toolStripMenuItem_Delete, toolStripSeparator1, toolStripMenuItem_More, toolStripSeparator4, toolStripMenuItem_About }); + contextMenuStrip_RightClick.Name = "contextMenuStrip1"; + resources.ApplyResources(contextMenuStrip_RightClick, "contextMenuStrip_RightClick"); + contextMenuStrip_RightClick.MouseClick += contextMenuStrip_RightClick_MouseClick; // // toolStripMenuItem_Enable // - this.toolStripMenuItem_Enable.Name = "toolStripMenuItem_Enable"; - resources.ApplyResources(this.toolStripMenuItem_Enable, "toolStripMenuItem_Enable"); + toolStripMenuItem_Enable.Name = "toolStripMenuItem_Enable"; + resources.ApplyResources(toolStripMenuItem_Enable, "toolStripMenuItem_Enable"); // // toolStripMenuItem_Disable // - this.toolStripMenuItem_Disable.Name = "toolStripMenuItem_Disable"; - resources.ApplyResources(this.toolStripMenuItem_Disable, "toolStripMenuItem_Disable"); + toolStripMenuItem_Disable.Name = "toolStripMenuItem_Disable"; + resources.ApplyResources(toolStripMenuItem_Disable, "toolStripMenuItem_Disable"); // // toolStripSeparator3 // - this.toolStripSeparator3.Name = "toolStripSeparator3"; - resources.ApplyResources(this.toolStripSeparator3, "toolStripSeparator3"); + toolStripSeparator3.Name = "toolStripSeparator3"; + resources.ApplyResources(toolStripSeparator3, "toolStripSeparator3"); // // toolStripMenuItem_Refresh // - this.toolStripMenuItem_Refresh.Name = "toolStripMenuItem_Refresh"; - resources.ApplyResources(this.toolStripMenuItem_Refresh, "toolStripMenuItem_Refresh"); + toolStripMenuItem_Refresh.Name = "toolStripMenuItem_Refresh"; + resources.ApplyResources(toolStripMenuItem_Refresh, "toolStripMenuItem_Refresh"); // // toolStripMenuItem_FlushDnsCache // - this.toolStripMenuItem_FlushDnsCache.Name = "toolStripMenuItem_FlushDnsCache"; - resources.ApplyResources(this.toolStripMenuItem_FlushDnsCache, "toolStripMenuItem_FlushDnsCache"); + toolStripMenuItem_FlushDnsCache.Name = "toolStripMenuItem_FlushDnsCache"; + resources.ApplyResources(toolStripMenuItem_FlushDnsCache, "toolStripMenuItem_FlushDnsCache"); // // toolStripSeparator2 // - this.toolStripSeparator2.Name = "toolStripSeparator2"; - resources.ApplyResources(this.toolStripSeparator2, "toolStripSeparator2"); + toolStripSeparator2.Name = "toolStripSeparator2"; + resources.ApplyResources(toolStripSeparator2, "toolStripSeparator2"); // // toolStripMenuItem_New // - this.toolStripMenuItem_New.Name = "toolStripMenuItem_New"; - resources.ApplyResources(this.toolStripMenuItem_New, "toolStripMenuItem_New"); + toolStripMenuItem_New.Name = "toolStripMenuItem_New"; + resources.ApplyResources(toolStripMenuItem_New, "toolStripMenuItem_New"); // // toolStripMenuItem_Modify // - this.toolStripMenuItem_Modify.Name = "toolStripMenuItem_Modify"; - resources.ApplyResources(this.toolStripMenuItem_Modify, "toolStripMenuItem_Modify"); + toolStripMenuItem_Modify.Name = "toolStripMenuItem_Modify"; + resources.ApplyResources(toolStripMenuItem_Modify, "toolStripMenuItem_Modify"); // // toolStripMenuItem_Delete // - this.toolStripMenuItem_Delete.Name = "toolStripMenuItem_Delete"; - resources.ApplyResources(this.toolStripMenuItem_Delete, "toolStripMenuItem_Delete"); + toolStripMenuItem_Delete.Name = "toolStripMenuItem_Delete"; + resources.ApplyResources(toolStripMenuItem_Delete, "toolStripMenuItem_Delete"); // // toolStripSeparator1 // - this.toolStripSeparator1.Name = "toolStripSeparator1"; - resources.ApplyResources(this.toolStripSeparator1, "toolStripSeparator1"); + toolStripSeparator1.Name = "toolStripSeparator1"; + resources.ApplyResources(toolStripSeparator1, "toolStripSeparator1"); + // + // toolStripMenuItem_More + // + toolStripMenuItem_More.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripMenuItem_Import, toolStripMenuItem_Export, toolStripSeparator5, toolStripMenuItem_ResetWindowSize }); + toolStripMenuItem_More.Name = "toolStripMenuItem_More"; + resources.ApplyResources(toolStripMenuItem_More, "toolStripMenuItem_More"); + // + // toolStripMenuItem_Import + // + toolStripMenuItem_Import.Name = "toolStripMenuItem_Import"; + resources.ApplyResources(toolStripMenuItem_Import, "toolStripMenuItem_Import"); + toolStripMenuItem_Import.Click += toolStripMenuItem_Import_Click; + // + // toolStripMenuItem_Export + // + toolStripMenuItem_Export.Name = "toolStripMenuItem_Export"; + resources.ApplyResources(toolStripMenuItem_Export, "toolStripMenuItem_Export"); + toolStripMenuItem_Export.Click += toolStripMenuItem_Export_Click; + // + // toolStripSeparator4 + // + toolStripSeparator4.Name = "toolStripSeparator4"; + resources.ApplyResources(toolStripSeparator4, "toolStripSeparator4"); // // toolStripMenuItem_About // - this.toolStripMenuItem_About.Name = "toolStripMenuItem_About"; - resources.ApplyResources(this.toolStripMenuItem_About, "toolStripMenuItem_About"); + toolStripMenuItem_About.Name = "toolStripMenuItem_About"; + resources.ApplyResources(toolStripMenuItem_About, "toolStripMenuItem_About"); // // imageListProxies // - this.imageListProxies.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit; - this.imageListProxies.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageListProxies.ImageStream"))); - this.imageListProxies.TransparentColor = System.Drawing.Color.Transparent; - this.imageListProxies.Images.SetKeyName(0, "disable.png"); - this.imageListProxies.Images.SetKeyName(1, "enable.png"); + imageListProxies.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit; + imageListProxies.ImageStream = (System.Windows.Forms.ImageListStreamer)resources.GetObject("imageListProxies.ImageStream"); + imageListProxies.TransparentColor = System.Drawing.Color.Transparent; + imageListProxies.Images.SetKeyName(0, "disable.png"); + imageListProxies.Images.SetKeyName(1, "enable.png"); + // + // saveFileDialog_Export + // + resources.ApplyResources(saveFileDialog_Export, "saveFileDialog_Export"); + // + // openFileDialog_Import + // + openFileDialog_Import.FileName = "openFileDialog1"; + resources.ApplyResources(openFileDialog_Import, "openFileDialog_Import"); + // + // toolStripMenuItem_ResetWindowSize + // + toolStripMenuItem_ResetWindowSize.Name = "toolStripMenuItem_ResetWindowSize"; + resources.ApplyResources(toolStripMenuItem_ResetWindowSize, "toolStripMenuItem_ResetWindowSize"); + toolStripMenuItem_ResetWindowSize.Click += toolStripMenuItem_ResetWindowSize_Click; + // + // toolStripSeparator5 + // + toolStripSeparator5.Name = "toolStripSeparator5"; + resources.ApplyResources(toolStripSeparator5, "toolStripSeparator5"); // // PortProxyGUI // resources.ApplyResources(this, "$this"); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.listViewProxies); - this.Name = "PortProxyGUI"; - this.Load += new System.EventHandler(this.PortProxyGUI_Load); - this.Shown += new System.EventHandler(this.PortProxyGUI_Shown); - this.contextMenuStrip1.ResumeLayout(false); - this.ResumeLayout(false); - + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + Controls.Add(listViewProxies); + Name = "PortProxyGUI"; + FormClosing += PortProxyGUI_FormClosing; + Load += PortProxyGUI_Load; + Shown += PortProxyGUI_Shown; + Resize += PortProxyGUI_Resize; + contextMenuStrip_RightClick.ResumeLayout(false); + ResumeLayout(false); } #endregion private System.Windows.Forms.ColumnHeader columnHeader2; private System.Windows.Forms.ColumnHeader columnHeader3; - private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; + private System.Windows.Forms.ContextMenuStrip contextMenuStrip_RightClick; private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem_New; private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem_Delete; private System.Windows.Forms.ColumnHeader columnHeader4; @@ -224,6 +259,14 @@ private System.Windows.Forms.ColumnHeader columnHeader7; internal System.Windows.Forms.ListView listViewProxies; private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem_FlushDnsCache; + private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem_More; + private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem_Export; + private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem_Import; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; + private System.Windows.Forms.SaveFileDialog saveFileDialog_Export; + private System.Windows.Forms.OpenFileDialog openFileDialog_Import; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem_ResetWindowSize; } } diff --git a/PortProxyGUI/PortProxyGUI.cs b/PortProxyGUI/PortProxyGUI.cs index f894cac..d7a5e6a 100644 --- a/PortProxyGUI/PortProxyGUI.cs +++ b/PortProxyGUI/PortProxyGUI.cs @@ -1,6 +1,11 @@ using NStandard; +using PortProxyGUI.Data; +using PortProxyGUI.UI; +using PortProxyGUI.Utils; using System; using System.Data; +using System.Drawing; +using System.IO; using System.Linq; using System.Windows.Forms; using static System.Windows.Forms.ListViewItem; @@ -9,21 +14,29 @@ namespace PortProxyGUI { public partial class PortProxyGUI : Form { + private readonly ListViewColumnSorter lvwColumnSorter = new ListViewColumnSorter(); + public SetProxy SetProxyForm; public About AboutForm; - private ListViewColumnSorter lvwColumnSorter; + private AppConfig AppConfig; public PortProxyGUI() { InitializeComponent(); - Font = Util.UiFont; + Font = InterfaceUtil.UiFont; - lvwColumnSorter = new ListViewColumnSorter(); listViewProxies.ListViewItemSorter = lvwColumnSorter; } private void PortProxyGUI_Load(object sender, EventArgs e) { + AppConfig = Program.Database.GetAppConfig(); + + var size = AppConfig.MainWindowSize; + Left -= (size.Width - Width) / 2; + Top -= (size.Height - Height) / 2; + + ResetWindowSize(); } private void PortProxyGUI_Shown(object sender, EventArgs e) @@ -31,6 +44,21 @@ namespace PortProxyGUI RefreshProxyList(); } + private void ResetWindowSize() + { + Size = AppConfig.MainWindowSize; + + if (AppConfig.PortProxyColumnWidths.Length != listViewProxies.Columns.Count) + { + Any.ReDim(ref AppConfig.PortProxyColumnWidths, listViewProxies.Columns.Count); + } + + foreach (var (column, configWidth) in Any.Zip(listViewProxies.Columns.OfType(), AppConfig.PortProxyColumnWidths)) + { + column.Width = configWidth; + } + } + private Data.Rule ParseRule(ListViewItem item) { var subItems = item.SubItems.OfType().ToArray(); @@ -62,7 +90,7 @@ namespace PortProxyGUI try { var rule = ParseRule(item); - PortProxyUtil.AddOrUpdateProxy(rule); + PortPorxyUtil.AddOrUpdateProxy(rule); } catch (NotSupportedException ex) { @@ -70,6 +98,7 @@ namespace PortProxyGUI return; } } + PortPorxyUtil.ParamChange(); } private void DisableSelectedProxies() @@ -82,7 +111,7 @@ namespace PortProxyGUI try { var rule = ParseRule(item); - PortProxyUtil.DeleteProxy(rule); + PortPorxyUtil.DeleteProxy(rule); } catch (NotSupportedException ex) { @@ -90,13 +119,14 @@ namespace PortProxyGUI return; } } + PortPorxyUtil.ParamChange(); } private void DeleteSelectedProxies() { var items = listViewProxies.SelectedItems.OfType(); DisableSelectedProxies(); - Program.SqliteDbScope.RemoveRange(items.Select(x => new Data.Rule { Id = x.Tag.ToString() })); + Program.Database.RemoveRange(items.Select(x => new Data.Rule { Id = x.Tag.ToString() })); foreach (var item in items) listViewProxies.Items.Remove(item); } @@ -172,8 +202,8 @@ namespace PortProxyGUI public void RefreshProxyList() { - var proxies = PortProxyUtil.GetProxies(); - var rules = Program.SqliteDbScope.Rules.ToArray(); + var proxies = PortPorxyUtil.GetProxies(); + var rules = Program.Database.Rules.ToArray(); foreach (var proxy in proxies) { var matchedRule = rules.FirstOrDefault(r => r.EqualsWithKeys(proxy)); @@ -188,33 +218,19 @@ namespace PortProxyGUI where proxy.Valid && proxy.Id is not null select proxy; - Program.SqliteDbScope.AddRange(pendingAdds); - Program.SqliteDbScope.UpdateRange(pendingUpdates); + Program.Database.AddRange(pendingAdds); + Program.Database.UpdateRange(pendingUpdates); - rules = Program.SqliteDbScope.Rules.ToArray(); + rules = Program.Database.Rules.ToArray(); InitProxyGroups(rules); InitProxyItems(rules, proxies); } - public void FlushDnsCache() + private void contextMenuStrip_RightClick_MouseClick(object sender, MouseEventArgs e) { - try + if (sender is ContextMenuStrip strip) { - CmdUtil.FlushDNSCache(); - RefreshProxyList(); - } - catch (NotSupportedException ex) - { - MessageBox.Show(ex.Message, "Exclamation", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); - return; - } - } - - private void contextMenuStrip1_MouseClick(object sender, MouseEventArgs e) - { - if (sender is ContextMenuStrip _sender) - { - var selected = _sender.Items.OfType().Where(x => x.Selected).FirstOrDefault(); + var selected = strip.Items.OfType().Where(x => x.Selected).FirstOrDefault(); if (selected is null || !selected.Enabled) return; switch (selected) @@ -238,7 +254,7 @@ namespace PortProxyGUI RefreshProxyList(); break; case ToolStripMenuItem item when item == toolStripMenuItem_FlushDnsCache: - FlushDnsCache(); + DnsUtil.FlushCache(); break; case ToolStripMenuItem item when item == toolStripMenuItem_Delete: DeleteSelectedProxies(); break; @@ -257,21 +273,21 @@ namespace PortProxyGUI private void listView1_MouseUp(object sender, MouseEventArgs e) { - if (sender is ListView _sender) + if (sender is ListView listView) { - toolStripMenuItem_Enable.Enabled = e.Button == MouseButtons.Right && _sender.SelectedItems.OfType().Any(x => x.ImageIndex == 0); - toolStripMenuItem_Disable.Enabled = e.Button == MouseButtons.Right && _sender.SelectedItems.OfType().Any(x => x.ImageIndex == 1); + toolStripMenuItem_Enable.Enabled = e.Button == MouseButtons.Right && listView.SelectedItems.OfType().Any(x => x.ImageIndex == 0); + toolStripMenuItem_Disable.Enabled = e.Button == MouseButtons.Right && listView.SelectedItems.OfType().Any(x => x.ImageIndex == 1); - toolStripMenuItem_Delete.Enabled = e.Button == MouseButtons.Right && _sender.SelectedItems.OfType().Any(); - toolStripMenuItem_Modify.Enabled = e.Button == MouseButtons.Right && _sender.SelectedItems.OfType().Count() == 1; + toolStripMenuItem_Delete.Enabled = e.Button == MouseButtons.Right && listView.SelectedItems.OfType().Any(); + toolStripMenuItem_Modify.Enabled = e.Button == MouseButtons.Right && listView.SelectedItems.OfType().Count() == 1; } } private void listView1_DoubleClick(object sender, EventArgs e) { - if (sender is ListView _sender) + if (sender is ListView listView) { - var selectAny = _sender.SelectedItems.OfType().Any(); + var selectAny = listView.SelectedItems.OfType().Any(); if (selectAny) { if (SetProxyForm == null) SetProxyForm = new SetProxy(this); @@ -314,5 +330,69 @@ namespace PortProxyGUI if (e.KeyCode == Keys.Delete) DeleteSelectedProxies(); } } + + private void listViewProxies_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e) + { + if (AppConfig is not null && sender is ListView listView) + { + AppConfig.PortProxyColumnWidths[e.ColumnIndex] = listView.Columns[e.ColumnIndex].Width; + } + } + + private void PortProxyGUI_FormClosing(object sender, FormClosingEventArgs e) + { + Program.Database.SaveAppConfig(AppConfig); + } + + private void PortProxyGUI_Resize(object sender, EventArgs e) + { + if (AppConfig is not null && sender is Form form) + { + AppConfig.MainWindowSize = form.Size; + } + } + + private void toolStripMenuItem_Export_Click(object sender, EventArgs e) + { + using var dialog = saveFileDialog_Export; + + var result = dialog.ShowDialog(); + if (result == DialogResult.OK) + { + var fileName = dialog.FileName; + File.Copy(ApplicationDbScope.AppDbFile, fileName, true); + } + } + + private void toolStripMenuItem_Import_Click(object sender, EventArgs e) + { + using var dialog = openFileDialog_Import; + + var result = dialog.ShowDialog(); + if (result == DialogResult.OK) + { + var fileName = dialog.FileName; + using (var scope = ApplicationDbScope.FromFile(fileName)) + { + foreach (var rule in scope.Rules) + { + var exsist = Program.Database.GetRule(rule.Type, rule.ListenOn, rule.ListenPort); + if (exsist is null) + { + rule.Id = Guid.NewGuid().ToString(); + Program.Database.Add(rule); + } + } + } + + RefreshProxyList(); + } + } + + private void toolStripMenuItem_ResetWindowSize_Click(object sender, EventArgs e) + { + AppConfig = new AppConfig(); + ResetWindowSize(); + } } } diff --git a/PortProxyGUI/PortProxyGUI.csproj b/PortProxyGUI/PortProxyGUI.csproj index 5147f69..915d212 100644 --- a/PortProxyGUI/PortProxyGUI.csproj +++ b/PortProxyGUI/PortProxyGUI.csproj @@ -14,7 +14,7 @@ portproxy TCP/IP redirector LICENSE.md Copyright © nstandard.net 2020 - 1.3.2 + 1.4.0 icon.ico Microsoft Sans Serif, 8pt PPGUI @@ -39,7 +39,7 @@ - + diff --git a/PortProxyGUI/PortProxyGUI.resx b/PortProxyGUI/PortProxyGUI.resx index 55babf0..822a02e 100644 --- a/PortProxyGUI/PortProxyGUI.resx +++ b/PortProxyGUI/PortProxyGUI.resx @@ -1,4 +1,64 @@ - + + + @@ -52,10 +112,10 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -98,82 +158,112 @@ Comment - 104 + 100 - - 17, 17 - + + 17, 17 + - 180, 22 + 174, 22 Enable (&E) - 180, 22 + 174, 22 Disable (&I) - 177, 6 + 171, 6 - 180, 22 + 174, 22 Refresh (&F) - 180, 22 + 174, 22 Flush DNS Cache - Will perform ipconfig/flushDNS + Perform ipconfig /flushDNS - 177, 6 + 171, 6 - 180, 22 + 174, 22 New (&N) ... - 180, 22 + 174, 22 Modify (&M) ... - 180, 22 + 174, 22 Delete (&D) - 177, 6 + 171, 6 + + + 186, 22 + + + Import + + + 186, 22 + + + Export + + + 183, 6 + + + 186, 22 + + + Reset Window + + + 174, 22 + + + More + + + 171, 6 - 180, 22 + 174, 22 About - - 181, 220 + + 175, 226 - - contextMenuStrip1 + + contextMenuStrip_RightClick - + System.Windows.Forms.ContextMenuStrip, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + Fill @@ -187,54 +277,54 @@ 4, 3, 4, 3 - 704, 456 + 704, 461 + + + 239, 17 - - 182, 17 - - AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs - LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu - SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA3AgAAAJNU0Z0AUkBTAIBAQIB - AAEwAQEBMAEBARABAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMAAUADAAEQAwABAQEAAQgG - AAEEGAABgAIAAYADAAKAAQABgAMAAYABAAGAAQACgAIAA8ABAAHAAdwBwAEAAfABygGmAQABMwUAATMB - AAEzAQABMwEAAjMCAAMWAQADHAEAAyIBAAMpAQADVQEAA00BAANCAQADOQEAAYABfAH/AQACUAH/AQAB - kwEAAdYBAAH/AewBzAEAAcYB1gHvAQAB1gLnAQABkAGpAa0CAAH/ATMDAAFmAwABmQMAAcwCAAEzAwAC - MwIAATMBZgIAATMBmQIAATMBzAIAATMB/wIAAWYDAAFmATMCAAJmAgABZgGZAgABZgHMAgABZgH/AgAB - mQMAAZkBMwIAAZkBZgIAApkCAAGZAcwCAAGZAf8CAAHMAwABzAEzAgABzAFmAgABzAGZAgACzAIAAcwB - /wIAAf8BZgIAAf8BmQIAAf8BzAEAATMB/wIAAf8BAAEzAQABMwEAAWYBAAEzAQABmQEAATMBAAHMAQAB - MwEAAf8BAAH/ATMCAAMzAQACMwFmAQACMwGZAQACMwHMAQACMwH/AQABMwFmAgABMwFmATMBAAEzAmYB - AAEzAWYBmQEAATMBZgHMAQABMwFmAf8BAAEzAZkCAAEzAZkBMwEAATMBmQFmAQABMwKZAQABMwGZAcwB - AAEzAZkB/wEAATMBzAIAATMBzAEzAQABMwHMAWYBAAEzAcwBmQEAATMCzAEAATMBzAH/AQABMwH/ATMB - AAEzAf8BZgEAATMB/wGZAQABMwH/AcwBAAEzAv8BAAFmAwABZgEAATMBAAFmAQABZgEAAWYBAAGZAQAB - ZgEAAcwBAAFmAQAB/wEAAWYBMwIAAWYCMwEAAWYBMwFmAQABZgEzAZkBAAFmATMBzAEAAWYBMwH/AQAC - ZgIAAmYBMwEAA2YBAAJmAZkBAAJmAcwBAAFmAZkCAAFmAZkBMwEAAWYBmQFmAQABZgKZAQABZgGZAcwB - AAFmAZkB/wEAAWYBzAIAAWYBzAEzAQABZgHMAZkBAAFmAswBAAFmAcwB/wEAAWYB/wIAAWYB/wEzAQAB - ZgH/AZkBAAFmAf8BzAEAAcwBAAH/AQAB/wEAAcwBAAKZAgABmQEzAZkBAAGZAQABmQEAAZkBAAHMAQAB - mQMAAZkCMwEAAZkBAAFmAQABmQEzAcwBAAGZAQAB/wEAAZkBZgIAAZkBZgEzAQABmQEzAWYBAAGZAWYB - mQEAAZkBZgHMAQABmQEzAf8BAAKZATMBAAKZAWYBAAOZAQACmQHMAQACmQH/AQABmQHMAgABmQHMATMB - AAFmAcwBZgEAAZkBzAGZAQABmQLMAQABmQHMAf8BAAGZAf8CAAGZAf8BMwEAAZkBzAFmAQABmQH/AZkB - AAGZAf8BzAEAAZkC/wEAAcwDAAGZAQABMwEAAcwBAAFmAQABzAEAAZkBAAHMAQABzAEAAZkBMwIAAcwC - MwEAAcwBMwFmAQABzAEzAZkBAAHMATMBzAEAAcwBMwH/AQABzAFmAgABzAFmATMBAAGZAmYBAAHMAWYB - mQEAAcwBZgHMAQABmQFmAf8BAAHMAZkCAAHMAZkBMwEAAcwBmQFmAQABzAKZAQABzAGZAcwBAAHMAZkB - /wEAAswCAALMATMBAALMAWYBAALMAZkBAAPMAQACzAH/AQABzAH/AgABzAH/ATMBAAGZAf8BZgEAAcwB - /wGZAQABzAH/AcwBAAHMAv8BAAHMAQABMwEAAf8BAAFmAQAB/wEAAZkBAAHMATMCAAH/AjMBAAH/ATMB - ZgEAAf8BMwGZAQAB/wEzAcwBAAH/ATMB/wEAAf8BZgIAAf8BZgEzAQABzAJmAQAB/wFmAZkBAAH/AWYB - zAEAAcwBZgH/AQAB/wGZAgAB/wGZATMBAAH/AZkBZgEAAf8CmQEAAf8BmQHMAQAB/wGZAf8BAAH/AcwC - AAH/AcwBMwEAAf8BzAFmAQAB/wHMAZkBAAH/AswBAAH/AcwB/wEAAv8BMwEAAcwB/wFmAQAC/wGZAQAC - /wHMAQACZgH/AQABZgH/AWYBAAFmAv8BAAH/AmYBAAH/AWYB/wEAAv8BZgEAASEBAAGlAQADXwEAA3cB - AAOGAQADlgEAA8sBAAOyAQAD1wEAA90BAAPjAQAD6gEAA/EBAAP4AQAB8AH7Af8BAAGkAqABAAOAAwAB - /wIAAf8DAAL/AQAB/wMAAf8BAAH/AQAC/wIAA/8EAAr0NAAB/wGUChcBlAH/AgAB/wG6CpUBugH/IgAB - lAwXAZQCAAG6DJUBuiIADhcCAA6VIgAEFwEWAfQC/wH0ARYEFwIADpUiAAMXARYB/wG9ARYBlAH0Af8B - FgMXAgAFlQK7B5UiAAMXAfQBvQIXAZQB/wL0AxcCAASVAboC/wHBBpUiAAMXAf8BFgEXAZQB/wKUAf8D - FwIAA5UBugH/AcEBugH/AcEFlSIAAxcB/wKUAf8BlAEXARYB/wMXAgADlQH/AcEClQG6Af8BwQSVIgAD - FwL0Af8BlAIXAb0B9AMXAgAIlQG6Af8BwQOVIgADFwEWAf8B9AGUARYBvQH/ARYDFwIACZUBugH/A5Ui - AAQXARYB9AL/AfQBFgQXAf8BAA6VAf8hAA4XAf8BAA6VAf8hAAGUDBcBlAIAAboMlQG6IgAB/wGUChcB - lAH/AgAB/wG6CpUBugH/NAAB/wj0JAABQgFNAT4HAAE+AwABKAMAAUADAAEQAwABAQEAAQEFAAGAFwAD - /wEAAeABBwL/BAABgAEBAYABAQQAAYABAQGAAQEEAAGAAQEBgAEBBAABgAEBAYABAQQAAYABAQGAAQEE - AAGAAQEBgAEBBAABgAEBAYABAQQAAYABAQGAAQEEAAGAAQEBgAEBBAABgAEBAYABAQQAAYABAAGABQAB - gAEAAYAFAAGAAQEBgAEBBAABgAEBAYABAQQAAv8B4AEPBAAL + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADc + CAAAAk1TRnQBSQFMAgEBAgEAAYwBAQGQAQEBEAEAARABAAT/AQkBEAj/AUIBTQE2AQQGAAE2AQQCAAEo + AwABQAMAARADAAEBAQABCAYAAQQYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA + AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5 + AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA + AWYDAAGZAwABzAIAATMDAAIzAgABMwFmAgABMwGZAgABMwHMAgABMwH/AgABZgMAAWYBMwIAAmYCAAFm + AZkCAAFmAcwCAAFmAf8CAAGZAwABmQEzAgABmQFmAgACmQIAAZkBzAIAAZkB/wIAAcwDAAHMATMCAAHM + AWYCAAHMAZkCAALMAgABzAH/AgAB/wFmAgAB/wGZAgAB/wHMAQABMwH/AgAB/wEAATMBAAEzAQABZgEA + ATMBAAGZAQABMwEAAcwBAAEzAQAB/wEAAf8BMwIAAzMBAAIzAWYBAAIzAZkBAAIzAcwBAAIzAf8BAAEz + AWYCAAEzAWYBMwEAATMCZgEAATMBZgGZAQABMwFmAcwBAAEzAWYB/wEAATMBmQIAATMBmQEzAQABMwGZ + AWYBAAEzApkBAAEzAZkBzAEAATMBmQH/AQABMwHMAgABMwHMATMBAAEzAcwBZgEAATMBzAGZAQABMwLM + AQABMwHMAf8BAAEzAf8BMwEAATMB/wFmAQABMwH/AZkBAAEzAf8BzAEAATMC/wEAAWYDAAFmAQABMwEA + AWYBAAFmAQABZgEAAZkBAAFmAQABzAEAAWYBAAH/AQABZgEzAgABZgIzAQABZgEzAWYBAAFmATMBmQEA + AWYBMwHMAQABZgEzAf8BAAJmAgACZgEzAQADZgEAAmYBmQEAAmYBzAEAAWYBmQIAAWYBmQEzAQABZgGZ + AWYBAAFmApkBAAFmAZkBzAEAAWYBmQH/AQABZgHMAgABZgHMATMBAAFmAcwBmQEAAWYCzAEAAWYBzAH/ + AQABZgH/AgABZgH/ATMBAAFmAf8BmQEAAWYB/wHMAQABzAEAAf8BAAH/AQABzAEAApkCAAGZATMBmQEA + AZkBAAGZAQABmQEAAcwBAAGZAwABmQIzAQABmQEAAWYBAAGZATMBzAEAAZkBAAH/AQABmQFmAgABmQFm + ATMBAAGZATMBZgEAAZkBZgGZAQABmQFmAcwBAAGZATMB/wEAApkBMwEAApkBZgEAA5kBAAKZAcwBAAKZ + Af8BAAGZAcwCAAGZAcwBMwEAAWYBzAFmAQABmQHMAZkBAAGZAswBAAGZAcwB/wEAAZkB/wIAAZkB/wEz + AQABmQHMAWYBAAGZAf8BmQEAAZkB/wHMAQABmQL/AQABzAMAAZkBAAEzAQABzAEAAWYBAAHMAQABmQEA + AcwBAAHMAQABmQEzAgABzAIzAQABzAEzAWYBAAHMATMBmQEAAcwBMwHMAQABzAEzAf8BAAHMAWYCAAHM + AWYBMwEAAZkCZgEAAcwBZgGZAQABzAFmAcwBAAGZAWYB/wEAAcwBmQIAAcwBmQEzAQABzAGZAWYBAAHM + ApkBAAHMAZkBzAEAAcwBmQH/AQACzAIAAswBMwEAAswBZgEAAswBmQEAA8wBAALMAf8BAAHMAf8CAAHM + Af8BMwEAAZkB/wFmAQABzAH/AZkBAAHMAf8BzAEAAcwC/wEAAcwBAAEzAQAB/wEAAWYBAAH/AQABmQEA + AcwBMwIAAf8CMwEAAf8BMwFmAQAB/wEzAZkBAAH/ATMBzAEAAf8BMwH/AQAB/wFmAgAB/wFmATMBAAHM + AmYBAAH/AWYBmQEAAf8BZgHMAQABzAFmAf8BAAH/AZkCAAH/AZkBMwEAAf8BmQFmAQAB/wKZAQAB/wGZ + AcwBAAH/AZkB/wEAAf8BzAIAAf8BzAEzAQAB/wHMAWYBAAH/AcwBmQEAAf8CzAEAAf8BzAH/AQAC/wEz + AQABzAH/AWYBAAL/AZkBAAL/AcwBAAJmAf8BAAFmAf8BZgEAAWYC/wEAAf8CZgEAAf8BZgH/AQAC/wFm + AQABIQEAAaUBAANfAQADdwEAA4YBAAOWAQADywEAA7IBAAPXAQAD3QEAA+MBAAPqAQAD8QEAA/gBAAHw + AfsB/wEAAaQCoAEAA4ADAAH/AgAB/wMAAv8BAAH/AwAB/wEAAf8BAAL/AgAD/wQACvQ0AAH/AZQKFwGU + Af8CAAH/AboKlQG6Af8iAAGUDBcBlAIAAboMlQG6IgAOFwIADpUiAAQXARYB9AL/AfQBFgQXAgAOlSIA + AxcBFgH/Ab0BFgGUAfQB/wEWAxcCAAWVArsHlSIAAxcB9AG9AhcBlAH/AvQDFwIABJUBugL/AcEGlSIA + AxcB/wEWARcBlAH/ApQB/wMXAgADlQG6Af8BwQG6Af8BwQWVIgADFwH/ApQB/wGUARcBFgH/AxcCAAOV + Af8BwQKVAboB/wHBBJUiAAMXAvQB/wGUAhcBvQH0AxcCAAiVAboB/wHBA5UiAAMXARYB/wH0AZQBFgG9 + Af8BFgMXAgAJlQG6Af8DlSIABBcBFgH0Av8B9AEWBBcB/wEADpUB/yEADhcB/wEADpUB/yEAAZQMFwGU + AgABugyVAboiAAH/AZQKFwGUAf8CAAH/AboKlQG6Af80AAH/CPQkAAFCAU0BPgcAAT4DAAEoAwABQAMA + ARADAAEBAQABAQUAAYAXAAP/AQAB4AEHAv8EAAGAAQEBgAEBBAABgAEBAYABAQQAAYABAQGAAQEEAAGA + AQEBgAEBBAABgAEBAYABAQQAAYABAQGAAQEEAAGAAQEBgAEBBAABgAEBAYABAQQAAYABAQGAAQEEAAGA + AQEBgAEBBAABgAEAAYAFAAGAAQABgAUAAYABAQGAAQEEAAGAAQEBgAEBBAAC/wHgAQ8aAAs= @@ -252,14 +342,26 @@ 1 - + + 389, 17 + + + Database File|*.db + + + 569, 17 + + + Database File|*.db + + True - + 6, 13 - 704, 456 + 704, 461 @@ -2530,6 +2632,30 @@ System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + toolStripMenuItem_More + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripMenuItem_Import + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripMenuItem_Export + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator4 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + toolStripMenuItem_About @@ -2542,6 +2668,30 @@ System.Windows.Forms.ImageList, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + saveFileDialog_Export + + + System.Windows.Forms.SaveFileDialog, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + openFileDialog_Import + + + System.Windows.Forms.OpenFileDialog, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripMenuItem_ResetWindowSize + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator5 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089 + PortProxyGUI diff --git a/PortProxyGUI/PortProxyGUI.zh-CN.resx b/PortProxyGUI/PortProxyGUI.zh-CN.resx index 6bc18b9..e001a81 100644 --- a/PortProxyGUI/PortProxyGUI.zh-CN.resx +++ b/PortProxyGUI/PortProxyGUI.zh-CN.resx @@ -112,15 +112,15 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 24 @@ -160,7 +160,7 @@ 104 - + 17, 17 @@ -173,7 +173,7 @@ System.Windows.Forms.ContextMenuStrip, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + Fill @@ -194,10 +194,10 @@ - AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj0yLjAuMC4w + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADc - CAAAAk1TRnQBSQFMAgEBAgEAASABAQEkAQEBEAEAARABAAT/AQkBEAj/AUIBTQE2AQQGAAE2AQQCAAEo + CAAAAk1TRnQBSQFMAgEBAgEAASABAQEwAQEBEAEAARABAAT/AQkBEAj/AUIBTQE2AQQGAAE2AQQCAAEo AwABQAMAARADAAEBAQABCAYAAQQYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5 AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA @@ -2593,4 +2593,22 @@ System.Windows.Forms.Form, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 清理 DNS 缓存 + + + 执行 ipconfig /flushDNS + + + 导出 + + + 导入 + + + 更多 + + + 重置窗口 + \ No newline at end of file diff --git a/PortProxyGUI/Program.cs b/PortProxyGUI/Program.cs index 0670b84..7fa50be 100644 --- a/PortProxyGUI/Program.cs +++ b/PortProxyGUI/Program.cs @@ -1,12 +1,15 @@ using PortProxyGUI.Data; using System; +using System.Globalization; +using System.IO; +using System.Threading; using System.Windows.Forms; namespace PortProxyGUI { static class Program { - public static readonly ApplicationDbScope SqliteDbScope = ApplicationDbScope.UseDefault(); + public static readonly ApplicationDbScope Database = ApplicationDbScope.FromFile(ApplicationDbScope.AppDbFile); /// /// The main entry point for the application. @@ -14,8 +17,6 @@ namespace PortProxyGUI [STAThread] static void Main() { - SqliteDbScope.Migrate(); - // To customize application configuration such as set high DPI settings or default font, // see https://aka.ms/applicationconfiguration. #if NET6_0_OR_GREATER diff --git a/PortProxyGUI/SetProxy.cs b/PortProxyGUI/SetProxy.cs index 1020ae2..ff9dfcf 100644 --- a/PortProxyGUI/SetProxy.cs +++ b/PortProxyGUI/SetProxy.cs @@ -1,5 +1,6 @@ using NStandard; using PortProxyGUI.Data; +using PortProxyGUI.Utils; using System; using System.Drawing; using System.Linq; @@ -22,7 +23,7 @@ namespace PortProxyGUI ParentWindow = parent; InitializeComponent(); - Font = Util.UiFont; + Font = InterfaceUtil.UiFont; AutoTypeString = comboBox_Type.Text = comboBox_Type.Items.OfType().First(); var groupNames = ( @@ -115,22 +116,23 @@ namespace PortProxyGUI if (_updateMode) { - var oldRule = Program.SqliteDbScope.GetRule(_itemRule.Type, _itemRule.ListenOn, _itemRule.ListenPort); - PortProxyUtil.DeleteProxy(oldRule); - Program.SqliteDbScope.Remove(oldRule); + var oldRule = Program.Database.GetRule(_itemRule.Type, _itemRule.ListenOn, _itemRule.ListenPort); + PortPorxyUtil.DeleteProxy(oldRule); + Program.Database.Remove(oldRule); - PortProxyUtil.AddOrUpdateProxy(rule); - Program.SqliteDbScope.Add(rule); + PortPorxyUtil.AddOrUpdateProxy(rule); + Program.Database.Add(rule); ParentWindow.UpdateListViewItem(_listViewItem, rule, 1); } else { - PortProxyUtil.AddOrUpdateProxy(rule); - Program.SqliteDbScope.Add(rule); + PortPorxyUtil.AddOrUpdateProxy(rule); + Program.Database.Add(rule); ParentWindow.RefreshProxyList(); } + PortPorxyUtil.ParamChange(); Close(); } diff --git a/PortProxyGUI/~DS/ListViewColumnSorter.cs b/PortProxyGUI/UI/ListViewColumnSorter.cs similarity index 98% rename from PortProxyGUI/~DS/ListViewColumnSorter.cs rename to PortProxyGUI/UI/ListViewColumnSorter.cs index b7b7610..2167af1 100644 --- a/PortProxyGUI/~DS/ListViewColumnSorter.cs +++ b/PortProxyGUI/UI/ListViewColumnSorter.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Windows.Forms; -namespace PortProxyGUI +namespace PortProxyGUI.UI { public class ListViewColumnSorter : IComparer { @@ -68,7 +68,7 @@ namespace PortProxyGUI else if (OrderOfSort == SortOrder.Descending) { // Descending sort is selected, return negative result of compare operation - return (-compareResult); + return -compareResult; } else { diff --git a/PortProxyGUI/Utils/DnsUtil.cs b/PortProxyGUI/Utils/DnsUtil.cs new file mode 100644 index 0000000..1ade0fc --- /dev/null +++ b/PortProxyGUI/Utils/DnsUtil.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace PortProxyGUI.Utils +{ + internal class DnsUtil + { + [DllImport("dnsapi.dll", EntryPoint = "DnsFlushResolverCache")] + static extern uint DnsFlushResolverCache(); + + public static void FlushCache() + { + var status = DnsFlushResolverCache(); + if (status == 0) throw new InvalidOperationException("Flush DNS Cache failed."); + } + + } +} diff --git a/PortProxyGUI/~DS/Util.cs b/PortProxyGUI/Utils/InterfaceUtil.cs similarity index 86% rename from PortProxyGUI/~DS/Util.cs rename to PortProxyGUI/Utils/InterfaceUtil.cs index 28307d6..f2b0122 100644 --- a/PortProxyGUI/~DS/Util.cs +++ b/PortProxyGUI/Utils/InterfaceUtil.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Drawing; using System.Text; -namespace PortProxyGUI +namespace PortProxyGUI.Utils { - public class Util + public class InterfaceUtil { /// /// Compatibility between .NET Framework and .NET Core. @@ -15,3 +15,4 @@ namespace PortProxyGUI } } + diff --git a/PortProxyGUI/Utils/PortPorxyUtil.cs b/PortProxyGUI/Utils/PortPorxyUtil.cs new file mode 100644 index 0000000..84717bc --- /dev/null +++ b/PortProxyGUI/Utils/PortPorxyUtil.cs @@ -0,0 +1,114 @@ +using Microsoft.Win32; +using PortProxyGUI.Data; +using PortProxyGUI.Native; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace PortProxyGUI.Utils +{ + public static class PortPorxyUtil + { + private static InvalidOperationException InvalidPortProxyType(string type) => new($"Invalid port proxy type ({type})."); + private static readonly string[] ProxyTypes = new[] { "v4tov4", "v4tov6", "v6tov4", "v6tov6" }; + + private static string GetKeyName(string type) + { + return $@"SYSTEM\CurrentControlSet\Services\PortProxy\{type}\tcp"; + } + + public static Rule[] GetProxies() + { + var ruleList = new List(); + foreach (var type in ProxyTypes) + { + var keyName = GetKeyName(type); + var key = Registry.LocalMachine.OpenSubKey(keyName); + + if (key is not null) + { + foreach (var name in key.GetValueNames()) + { + var listenParts = name.Split('/'); + var listenOn = listenParts[0]; + if (!int.TryParse(listenParts[1], out var listenPort)) continue; + + var connectParts = key.GetValue(name).ToString().Split('/'); + var connectTo = connectParts[0]; + if (!int.TryParse(connectParts[1], out var connectPort)) continue; + + ruleList.Add(new Rule + { + Type = type, + ListenOn = listenOn, + ListenPort = listenPort, + ConnectTo = connectTo, + ConnectPort = connectPort, + }); + } + } + } + return ruleList.ToArray(); + } + + public static void AddOrUpdateProxy(Rule rule) + { + // $"netsh interface portproxy add {rule.Type} listenaddress={rule.ListenOn} listenport={rule.ListenPort} connectaddress={rule.ConnectTo} connectport={rule.ConnectPort}" + + if (!ProxyTypes.Contains(rule.Type)) throw InvalidPortProxyType(rule.Type); + + var keyName = GetKeyName(rule.Type); + var key = Registry.LocalMachine.OpenSubKey(keyName, true); + var name = $"{rule.ListenOn}/{rule.ListenPort}"; + var value = $"{rule.ConnectTo}/{rule.ConnectPort}"; + + if (key is null) Registry.LocalMachine.CreateSubKey(keyName); + key = Registry.LocalMachine.OpenSubKey(keyName, true); + key?.SetValue(name, value); + } + + public static void DeleteProxy(Rule rule) + { + // $"netsh interface portproxy delete {rule.Type} listenaddress={rule.ListenOn} listenport={rule.ListenPort}" + + if (!ProxyTypes.Contains(rule.Type)) throw InvalidPortProxyType(rule.Type); + + var keyName = GetKeyName(rule.Type); + var key = Registry.LocalMachine.OpenSubKey(keyName, true); + var name = $"{rule.ListenOn}/{rule.ListenPort}"; + + try + { + key?.DeleteValue(name); + } + catch { } + } + + public static void ParamChange() + { + var hManager = NativeMethods.OpenSCManager(null, null, (uint)GenericRights.GENERIC_READ); + if (hManager == IntPtr.Zero) throw new InvalidOperationException("Open SC Manager failed."); + + var serviceName = "iphlpsvc"; + var hService = NativeMethods.OpenService(hManager, serviceName, ServiceRights.SERVICE_PAUSE_CONTINUE); + if (hService == IntPtr.Zero) + { + NativeMethods.CloseServiceHandle(hManager); + throw new InvalidOperationException($"Open Service ({serviceName}) failed."); + } + + var serviceStatus = new ServiceStatus(); + var success = NativeMethods.ControlService(hService, ServiceControls.SERVICE_CONTROL_PARAMCHANGE, ref serviceStatus); + + NativeMethods.CloseServiceHandle(hService); + NativeMethods.CloseServiceHandle(hManager); + + if (!success) + { + throw new InvalidOperationException($"Control Service ({serviceName}) ParamChange failed."); + } + } + + } +} diff --git a/PortProxyGUI/~DS/CmdRunner.cs b/PortProxyGUI/~DS/CmdRunner.cs deleted file mode 100644 index bb75e0c..0000000 --- a/PortProxyGUI/~DS/CmdRunner.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Diagnostics; - -namespace PortProxyGUI -{ - [Obsolete("The method of creating a new process is no longer used.")] - public static class CmdRunner - { - public static string Execute(string cmd) - { - var proc = Process.Start(new ProcessStartInfo - { - FileName = "cmd", - UseShellExecute = false, - RedirectStandardInput = true, - RedirectStandardOutput = true, - CreateNoWindow = true, - }); - proc.Start(); - - proc.StandardInput.WriteLine($"{cmd} & exit"); - var output = proc.StandardOutput.ReadToEnd(); - - return output; - } - } -} diff --git a/PortProxyGUI/~DS/CmdUtil.cs b/PortProxyGUI/~DS/CmdUtil.cs deleted file mode 100644 index 9c69d8e..0000000 --- a/PortProxyGUI/~DS/CmdUtil.cs +++ /dev/null @@ -1,89 +0,0 @@ -using NStandard; -using PortProxyGUI.Data; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace PortProxyGUI -{ - [Obsolete("The method of creating a new process is no longer used.", true)] - public static class CmdUtil - { - private static Regex GetRegex(string fromType, string toType) - { - return new Regex($@"{fromType}[^:]*:[^\n]+?{toType}[^:]*:\r\n\r\n.+?\r\n--------------- ---------- --------------- ----------\r\n(.+?)\r\n\r\n", RegexOptions.Singleline); - } - - private static readonly Dictionary RegexList = new Dictionary() - { - ["ipv4 to ipv4"] = GetRegex("ipv4", "ipv4"), - ["ipv4 to ipv6"] = GetRegex("ipv4", "ipv6"), - ["ipv6 to ipv4"] = GetRegex("ipv6", "ipv4"), - ["ipv6 to ipv6"] = GetRegex("ipv6", "ipv6"), - }; - - private static readonly Regex LineRegex = new(@"^(.*?)\s{1,}(.*?)\s{1,}(.*?)\s{1,}(.*?)$"); - - public static Rule[] GetProxies() - { - var output = CmdRunner.Execute("netsh interface portproxy show all"); - var types = new[] - { - new ProxyType("ipv4", "ipv4"), - new ProxyType("ipv4", "ipv6"), - new ProxyType("ipv6", "ipv4"), - new ProxyType("ipv6", "ipv6"), - }; - - var list = new List(); - foreach (var type in types) - { - var regex = RegexList[$"{type.From} to {type.To}"]; - var settings = output.ExtractFirst(regex); - var lines = settings?.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - - if (lines is not null) - { - foreach (var line in lines) - { - if (line.TryResolve(LineRegex, out var parts)) - { - var realListenPort = parts[2].First(); - var realConnectPort = parts[4].First(); - - _ = int.TryParse(realListenPort, out var listenPort); - _ = int.TryParse(realConnectPort, out var connectPort); - - list.Add(new Rule - { - Type = type.Type, - ListenOn = parts[1].First(), - ListenPort = listenPort, - ConnectTo = parts[3].First(), - ConnectPort = connectPort, - }); - } - } - } - } - - return list.ToArray(); - } - - public static void AddOrUpdateProxy(Rule rule) - { - CmdRunner.Execute($"netsh interface portproxy add {rule.Type} listenaddress={rule.ListenOn} listenport={rule.ListenPort} connectaddress={rule.ConnectTo} connectport={rule.ConnectPort}"); - } - - public static void DeleteProxy(Rule rule) - { - CmdRunner.Execute($"netsh interface portproxy delete {rule.Type} listenaddress={rule.ListenOn} listenport={rule.ListenPort}"); - } - - public static string FlushDNSCache() - { - return CmdRunner.Execute("ipconfig /flushdns"); - } - } -} diff --git a/PortProxyGUI/~DS/PortProxyUtil.cs b/PortProxyGUI/~DS/PortProxyUtil.cs deleted file mode 100644 index b7fd22a..0000000 --- a/PortProxyGUI/~DS/PortProxyUtil.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.Win32; -using PortProxyGUI.Data; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace PortProxyGUI -{ - public static class PortProxyUtil - { - private static InvalidOperationException InvalidPortProxyType(string type) => new($"Invalid port proxy type ({type})."); - private static readonly string[] ProxyTypes = new[] { "v4tov4", "v4tov6", "v6tov4", "v6tov6" }; - - public static Rule[] GetProxies() - { - var ruleList = new List(); - foreach (var type in ProxyTypes) - { - var keyName = $@"SYSTEM\ControlSet001\Services\PortProxy\{type}\tcp"; - var key = Registry.LocalMachine.OpenSubKey(keyName); - - if (key is not null) - { - foreach (var name in key.GetValueNames()) - { - var listenParts = name.Split('/'); - var listenOn = listenParts[0]; - if (!int.TryParse(listenParts[1], out var listenPort)) continue; - - var connectParts = key.GetValue(name).ToString().Split('/'); - var connectTo = connectParts[0]; - if (!int.TryParse(connectParts[1], out var connectPort)) continue; - - ruleList.Add(new Rule - { - Type = type, - ListenOn = listenOn, - ListenPort = listenPort, - ConnectTo = connectTo, - ConnectPort = connectPort, - }); - } - } - } - return ruleList.ToArray(); - } - - public static void AddOrUpdateProxy(Rule rule) - { - if (!ProxyTypes.Contains(rule.Type)) throw InvalidPortProxyType(rule.Type); - - var keyName = $@"SYSTEM\ControlSet001\Services\PortProxy\{rule.Type}\tcp"; - var key = Registry.LocalMachine.OpenSubKey(keyName, true); - var valueName = $"{rule.ListenOn}/{rule.ListenPort}"; - var value = $"{rule.ConnectTo}/{rule.ConnectPort}"; - - key.SetValue(valueName, value); - } - - public static void DeleteProxy(Rule rule) - { - if (!ProxyTypes.Contains(rule.Type)) throw InvalidPortProxyType(rule.Type); - - var keyName = $@"SYSTEM\ControlSet001\Services\PortProxy\{rule.Type}\tcp"; - var key = Registry.LocalMachine.OpenSubKey(keyName, true); - var valueName = $"{rule.ListenOn}/{rule.ListenPort}"; - - key.DeleteValue(valueName); - } - } -} diff --git a/PortProxyGUI/~DS/ProxyType.cs b/PortProxyGUI/~DS/ProxyType.cs deleted file mode 100644 index b157a51..0000000 --- a/PortProxyGUI/~DS/ProxyType.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace PortProxyGUI -{ - public class ProxyType - { - public ProxyType(string from, string to) - { - From = from; - To = to; - } - - public string From { get; set; } - public string To { get; set; } - public string Type - { - get - { - if (From == "ipv4" && To == "ipv4") return "v4tov4"; - if (From == "ipv4" && To == "ipv6") return "v4tov6"; - if (From == "ipv6" && To == "ipv4") return "v6tov4"; - if (From == "ipv6" && To == "ipv6") return "v6tov6"; - throw new NotSupportedException(); - } - } - - } -} diff --git a/docs/ui.png b/docs/ui.png index 655a28ac25edcc9241566e0a2ab395e58060e871..008666c518a3d4d06f84d3a95c9bdebb7dc3b4cc 100644 GIT binary patch literal 17868 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 N%yUJ_d~t&h{})954Wa-5 literal 18279 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 LymYSQi?{y+4XDB=