Dirty flag for saving when closing
4011be4675566b2ce908272e53f6a59c11fbc2fc
7 files changed
src/WireGuardConfigGenerator/DataModel/Root.cssrc/WireGuardConfigGenerator/Form1.Designer.cssrc/WireGuardConfigGenerator/Form1.cssrc/WireGuardConfigGenerator/FormPassword.Designer.cssrc/WireGuardConfigGenerator/FormPassword.cssrc/WireGuardConfigGenerator/Helpers/CryptoUtils.cssrc/WireGuardConfigGenerator/UserControlTree.cs
diff --git a/src/WireGuardConfigGenerator/DataModel/Root.cs b/src/WireGuardConfigGenerator/DataModel/Root.cs
index cf38336..66c94a9 100644
--- a/src/WireGuardConfigGenerator/DataModel/Root.cs
+++ b/src/WireGuardConfigGenerator/DataModel/Root.cs
@@ -8,6 +8,12 @@ public class Root
{
public List<Group> Groups { get; set; } = [];
+ [JsonIgnore]
+ private string HashLoaded { get; set; } = "f192562ea2091ca8e3a7f541548ab1a7"; // initial empty
+
+ [JsonIgnore]
+ public bool IsDirty => HashLoaded != CryptoUtils.GeHash(this);
+
public async Task SaveAsync(string path, string password) =>
await CryptoUtils.CompressAndEncryptToFileAsync(this, path, password);
@@ -19,9 +25,12 @@ public class Root
if (root != null)
{
Groups = root.Groups;
+
+ HashLoaded = CryptoUtils.GeHash(root);
}
}
}
+
}
public class Group
diff --git a/src/WireGuardConfigGenerator/Form1.Designer.cs b/src/WireGuardConfigGenerator/Form1.Designer.cs
index ff07756..1bbf344 100644
--- a/src/WireGuardConfigGenerator/Form1.Designer.cs
+++ b/src/WireGuardConfigGenerator/Form1.Designer.cs
@@ -36,6 +36,7 @@
statusStrip1 = new StatusStrip();
splitContainer1 = new SplitContainer();
userControlTree1 = new UserControlTree();
+ toolStripMenuItem2 = new ToolStripMenuItem();
menuStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
splitContainer1.Panel1.SuspendLayout();
@@ -53,7 +54,7 @@
//
// fileToolStripMenuItem
//
- fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem1, toolStripSeparator1, exitToolStripMenuItem });
+ fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem1, toolStripMenuItem2, toolStripSeparator1, exitToolStripMenuItem });
fileToolStripMenuItem.Name = "fileToolStripMenuItem";
fileToolStripMenuItem.Size = new Size(37, 20);
fileToolStripMenuItem.Text = "File";
@@ -61,19 +62,19 @@
// toolStripMenuItem1
//
toolStripMenuItem1.Name = "toolStripMenuItem1";
- toolStripMenuItem1.Size = new Size(115, 22);
+ toolStripMenuItem1.Size = new Size(180, 22);
toolStripMenuItem1.Text = "Open ...";
toolStripMenuItem1.Click += OpenFile_Click;
//
// toolStripSeparator1
//
toolStripSeparator1.Name = "toolStripSeparator1";
- toolStripSeparator1.Size = new Size(112, 6);
+ toolStripSeparator1.Size = new Size(177, 6);
//
// exitToolStripMenuItem
//
exitToolStripMenuItem.Name = "exitToolStripMenuItem";
- exitToolStripMenuItem.Size = new Size(115, 22);
+ exitToolStripMenuItem.Size = new Size(180, 22);
exitToolStripMenuItem.Text = "Exit";
exitToolStripMenuItem.Click += Exit_Click;
//
@@ -107,6 +108,13 @@
userControlTree1.Size = new Size(339, 515);
userControlTree1.TabIndex = 0;
//
+ // toolStripMenuItem2
+ //
+ toolStripMenuItem2.Name = "toolStripMenuItem2";
+ toolStripMenuItem2.Size = new Size(180, 22);
+ toolStripMenuItem2.Text = "Save ...";
+ toolStripMenuItem2.Click += Save_Click;
+ //
// Form1
//
AutoScaleDimensions = new SizeF(7F, 15F);
@@ -118,6 +126,7 @@
MainMenuStrip = menuStrip1;
Name = "Form1";
Text = "WireGuardConfigGenerator";
+ FormClosing += Form_FormClosing;
menuStrip1.ResumeLayout(false);
menuStrip1.PerformLayout();
splitContainer1.Panel1.ResumeLayout(false);
@@ -137,5 +146,6 @@
private UserControlTree userControlTree1;
private ToolStripMenuItem toolStripMenuItem1;
private ToolStripSeparator toolStripSeparator1;
+ private ToolStripMenuItem toolStripMenuItem2;
}
}
diff --git a/src/WireGuardConfigGenerator/Form1.cs b/src/WireGuardConfigGenerator/Form1.cs
index b04ea24..8661634 100644
--- a/src/WireGuardConfigGenerator/Form1.cs
+++ b/src/WireGuardConfigGenerator/Form1.cs
@@ -18,7 +18,26 @@ public partial class Form1 : Form
{
var f = new FormPassword();
- if( f.ShowDialog(this) == DialogResult.OK)
+ if (f.ShowDialog(this) == DialogResult.OK)
await this.userControlTree1.LoadAsync(f.Path, f.Password);
}
+
+ private async void Form_FormClosing(object sender, FormClosingEventArgs e)
+ {
+ if (!this.userControlTree1.IsDirty)
+ return;
+
+ var f = new FormPassword();
+
+ if (f.ShowDialog(this) == DialogResult.OK)
+ await this.userControlTree1.SaveAsync(f.Path, f.Password);
+ }
+
+ private async void Save_Click(object sender, EventArgs e)
+ {
+ var f = new FormPassword();
+
+ if (f.ShowDialog(this) == DialogResult.OK)
+ await this.userControlTree1.SaveAsync(f.Path, f.Password);
+ }
}
diff --git a/src/WireGuardConfigGenerator/FormPassword.Designer.cs b/src/WireGuardConfigGenerator/FormPassword.Designer.cs
index ef4ef6a..b6872ad 100644
--- a/src/WireGuardConfigGenerator/FormPassword.Designer.cs
+++ b/src/WireGuardConfigGenerator/FormPassword.Designer.cs
@@ -35,12 +35,14 @@
label2 = new Label();
button1 = new Button();
openFileDialog1 = new OpenFileDialog();
+ btnCancel = new Button();
+ lblError = new Label();
SuspendLayout();
//
// label1
//
label1.AutoSize = true;
- label1.Location = new Point(12, 57);
+ label1.Location = new Point(12, 64);
label1.Name = "label1";
label1.Size = new Size(57, 15);
label1.TabIndex = 0;
@@ -48,15 +50,16 @@
//
// txtPassword
//
- txtPassword.Location = new Point(12, 75);
+ txtPassword.Location = new Point(12, 82);
txtPassword.Name = "txtPassword";
txtPassword.PasswordChar = '*';
txtPassword.Size = new Size(142, 23);
txtPassword.TabIndex = 1;
+ txtPassword.TextChanged += Password_TextChanged;
//
// btnOk
//
- btnOk.Location = new Point(160, 74);
+ btnOk.Location = new Point(320, 118);
btnOk.Name = "btnOk";
btnOk.Size = new Size(75, 23);
btnOk.TabIndex = 2;
@@ -95,12 +98,32 @@
//
openFileDialog1.FileName = "openFileDialog1";
//
+ // btnCancel
+ //
+ btnCancel.Location = new Point(401, 118);
+ btnCancel.Name = "btnCancel";
+ btnCancel.Size = new Size(75, 23);
+ btnCancel.TabIndex = 7;
+ btnCancel.Text = "Cancel";
+ btnCancel.UseVisualStyleBackColor = true;
+ btnCancel.Click += Cancel_Click;
+ //
+ // lblError
+ //
+ lblError.AutoSize = true;
+ lblError.ForeColor = Color.Red;
+ lblError.Location = new Point(174, 87);
+ lblError.Name = "lblError";
+ lblError.Size = new Size(0, 15);
+ lblError.TabIndex = 8;
+ //
// FormPassword
//
- AcceptButton = btnOk;
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
- ClientSize = new Size(488, 106);
+ ClientSize = new Size(488, 152);
+ Controls.Add(lblError);
+ Controls.Add(btnCancel);
Controls.Add(button1);
Controls.Add(txtPath);
Controls.Add(label2);
@@ -127,5 +150,7 @@
private Label label2;
private Button button1;
private OpenFileDialog openFileDialog1;
+ private Button btnCancel;
+ private Label lblError;
}
}
\ No newline at end of file
diff --git a/src/WireGuardConfigGenerator/FormPassword.cs b/src/WireGuardConfigGenerator/FormPassword.cs
index 1c92a74..43ffd27 100644
--- a/src/WireGuardConfigGenerator/FormPassword.cs
+++ b/src/WireGuardConfigGenerator/FormPassword.cs
@@ -8,17 +8,24 @@ public partial class FormPassword : Form
}
public string Password => this.txtPassword.Text.Trim();
-
public string Path => this.txtPath.Text.Trim();
private void Ok_Click(object sender, EventArgs e)
{
- this.DialogResult = DialogResult.OK;
- this.Close();
+ if (this.txtPassword.Text.Length < 8)
+ {
+ this.lblError.Text = "Min. password length 8 chars";
+ }
+ else
+ {
+ this.DialogResult = DialogResult.OK;
+ this.Close();
+ }
}
private void Cancel_Click(object sender, EventArgs e)
{
+ this.DialogResult = DialogResult.Cancel;
this.Close();
}
@@ -35,4 +42,9 @@ public partial class FormPassword : Form
this.txtPath.Text = this.openFileDialog1.FileName.Replace(AppContext.BaseDirectory, string.Empty);
}
}
+
+ private void Password_TextChanged(object sender, EventArgs e)
+ {
+ this.lblError.Text = string.Empty;
+ }
}
diff --git a/src/WireGuardConfigGenerator/Helpers/CryptoUtils.cs b/src/WireGuardConfigGenerator/Helpers/CryptoUtils.cs
index ee1e784..6e71b8d 100644
--- a/src/WireGuardConfigGenerator/Helpers/CryptoUtils.cs
+++ b/src/WireGuardConfigGenerator/Helpers/CryptoUtils.cs
@@ -1,5 +1,6 @@
using System.IO.Compression;
using System.Security.Cryptography;
+using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -12,17 +13,43 @@ public static class CryptoUtils
private const int IVSize = 16; // 128 bits AES IV
private const int Iterations = 100000; // PBKDF2 iterations
- public static async Task<bool> CompressAndEncryptToFileAsync<T>(T model, string filePath, string password, JsonSerializerOptions? options = null, CancellationToken ct = default)
+
+ private static string GetJson<T>(T model, JsonSerializerOptions? options = null)
{
options ??= new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};
-
- // 1. to JSON
string json = JsonSerializer.Serialize(model, options);
+ return json;
+ }
+
+ private static string GetMd5Hash(string input)
+ {
+ byte[] inputBytes = Encoding.UTF8.GetBytes(input);
+
+ string md5hash = BitConverter.ToString(MD5.HashData(inputBytes)).Replace("-", "").ToLower();
+
+ return md5hash;
+ }
+
+
+ public static string GeHash<T>(T model)
+ {
+ string json = GetJson<T>(model);
+
+ string hash = GetMd5Hash(json);
+
+ return hash;
+ }
+
+ public static async Task<bool> CompressAndEncryptToFileAsync<T>(T model, string filePath, string password, JsonSerializerOptions? options = null, CancellationToken ct = default)
+ {
+ // 1. Get Json from model
+ var json = GetJson<T>(model, options);
+
// 2. Compressing using GZIP
byte[] compressed;
using (var outputStream = new MemoryStream())
diff --git a/src/WireGuardConfigGenerator/UserControlTree.cs b/src/WireGuardConfigGenerator/UserControlTree.cs
index a874e7d..8cf0013 100644
--- a/src/WireGuardConfigGenerator/UserControlTree.cs
+++ b/src/WireGuardConfigGenerator/UserControlTree.cs
@@ -8,6 +8,9 @@ public partial class UserControlTree : UserControl
private readonly AppSettings Settings;
private readonly Root root = new();
+
+ public bool IsDirty => this.root.IsDirty;
+
public UserControlTree()
{
InitializeComponent();
@@ -21,12 +24,13 @@ public partial class UserControlTree : UserControl
public async Task LoadAsync(string path, string password)
{
- if (this.ParentForm is Form1 form)
- form.FormClosing += async (s, e) => await root.SaveAsync(path, password);
-
await TreeHelpers.LoadTreeAsync(this.treeView1, root, path, password);
}
+ public async Task SaveAsync(string path, string password)
+ {
+ await root.SaveAsync(path, password);
+ }
private void ShowContextMenu(Point location)
{