191204PX Merge with default: Reworked reloading of files, declared process_window feature done for now.

This commit is contained in:
phantomix 2019-12-04 18:49:42 +01:00
commit d1fa9744d3
17 changed files with 990 additions and 422 deletions

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using dezentrale.core;
using dezentrale.model;
using dezentrale.model.money;
using dezentrale.view;
@ -16,16 +17,24 @@ TODO
- FormMail: Add mail for automatic member type setting to "Foerdermitglied"
- FormMail: Use manual type changing
- FormMail: Cronjob found unassigned MoneyTransfers
- frmEditEntry: Optional (checkbox) request for a comment on data changes, when hitting OK
- frmMain: Indicator that the data was modified after import + messagebox to remind user to export on quitting.
- frmMain: Member List: Column "monthly fee", Column "Last payment", disabled by default
- Configuration window: MoneyTransferRegEx
- Bug: Generating testdata doesn't remove old xml files, thus the memberlist will be mixed after next restart
- Bug: Database import fails on some PCs (seems that writing xml files during decompression is problematic)
- Bug: Member list not reloaded after ProcessCSV (balance display is wrong)
- CronjobConfig
- CustomListView: implement generic filtering
DONE
----
- 2019-12-04: frmProcessWithLog: Implementation for non-immediate GUI actions (process csv, cronjob, import/export) with background thread
*/
namespace dezentrale
{
public class Program
{
public static uint VersionNumber { get; private set; } = 0x19111000;
public static uint VersionNumber { get; private set; } = 0x19120400;
public static string VersionString { get; private set; } = $"{VersionNumber:x}";
public static string AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
@ -59,6 +68,48 @@ namespace dezentrale
public static eMode ProgramMode = eMode.Gui;
private static string csvInput = null;
public static int LoadFiles()
{
moneyTransfers = null;
try { Directory.CreateDirectory(DmDirectory); }
catch (Exception ex) { Console.WriteLine($"Error while creating data directory:\n{ex.Message}"); return 1; }
try
{
config = (Configuration)XmlData.LoadFromFile(ConfigFile, typeof(Configuration));
}
catch (FileNotFoundException)
{
config.DbDirectory = Path.Combine(DmDirectory, Configuration.DefaultDbDirectory);
XmlData.SaveToFile(ConfigFile, config); Console.WriteLine("Created new configuration file.");
}
catch (Exception ex)
{
Console.WriteLine("Error while accessing program data:");
Console.WriteLine(ex.Message);
return 1;
}
try { Directory.CreateDirectory(config.DbDirectory); }
catch (Exception ex) { Console.WriteLine($"Error while creating member data directory:\n{ex.Message}"); return 1; }
try
{
string[] memberFiles = Directory.GetFiles(config.DbDirectory, "member-*.xml");
//foreach (string s in memberFiles) Console.WriteLine(s);
members = MemberList.LoadFromFiles(memberFiles);
Console.WriteLine($"Loaded {members.Entries.Count} member entries");
}
catch (Exception ex)
{
Console.WriteLine("Error while loading member files:");
Console.WriteLine(ex.Message);
return 1;
}
return 0;
}
[STAThread]
static int Main(string[] args)
{
@ -125,41 +176,7 @@ namespace dezentrale
return 1;
}
try { Directory.CreateDirectory(DmDirectory); }
catch (Exception ex) { Console.WriteLine($"Error while creating data directory:\n{ex.Message}"); return 1; }
try
{
config = (Configuration)XmlData.LoadFromFile(ConfigFile, typeof(Configuration));
}
catch (FileNotFoundException)
{
config.DbDirectory = Path.Combine(DmDirectory, Configuration.DefaultDbDirectory);
XmlData.SaveToFile(ConfigFile, config); Console.WriteLine("Created new configuration file.");
}
catch (Exception ex)
{
Console.WriteLine("Error while accessing program data:");
Console.WriteLine(ex.Message);
return 1;
}
try { Directory.CreateDirectory(config.DbDirectory); }
catch (Exception ex) { Console.WriteLine($"Error while creating member data directory:\n{ex.Message}"); return 1; }
try
{
string[] memberFiles = Directory.GetFiles(config.DbDirectory, "member-*.xml");
//foreach (string s in memberFiles) Console.WriteLine(s);
members = MemberList.LoadFromFiles(memberFiles);
Console.WriteLine($"Loaded {members.Entries.Count} member entries");
}
catch (Exception ex)
{
Console.WriteLine("Error while loading member files:");
Console.WriteLine(ex.Message);
return 1;
}
if(LoadFiles() != 0) return 1;
switch (ProgramMode)
{
@ -174,14 +191,42 @@ namespace dezentrale
Cronjob.Run();
break;
case eMode.Export:
if (!config.ImportExport.Export(config.DbDirectory, DmDirectory)) return 1;
if (!config.ImportExport.VerifyExport(config.DbDirectory, DmDirectory)) return 1;
break;
case eMode.Import:
{
ExportProcess export = new ExportProcess()
{
ImportExportSettings = Program.config.ImportExport,
MemberDir = Program.config.DbDirectory,
OutputDir = Program.DmDirectory,
};
ConsoleLogger logger = new ConsoleLogger();
System.Threading.Thread t = logger.StartRunProcess(export);
if (t != null) t.Join();
else return 1;
//if (!config.ImportExport.Export(config.DbDirectory, DmDirectory)) return 1;
//if (!config.ImportExport.VerifyExport(config.DbDirectory, DmDirectory)) return 1;
if(logger.DialogResult != DialogResult.OK) return 1;
} break;
case eMode.Import:
{
ImportProcess import = new ImportProcess()
{
ImportExportSettings = Program.config.ImportExport,
MemberDir = Program.config.DbDirectory,
InputDir = Program.DmDirectory,
};
ConsoleLogger logger = new ConsoleLogger();
System.Threading.Thread t = logger.StartRunProcess(import);
if (t != null) t.Join();
else return 1;
//frmProcessWithLog frmImport = new frmProcessWithLog(import, true);
//if (!config.ImportExport.Import(config.DbDirectory, DmDirectory))
//return 1;
if(logger.DialogResult != DialogResult.OK) return 1;
} break;
if (!config.ImportExport.Import(config.DbDirectory, DmDirectory))
return 1;
break;
case eMode.BankImport:
if (string.IsNullOrEmpty(csvInput))
{

50
core/BackgroundProcess.cs Normal file
View File

@ -0,0 +1,50 @@
using System;
using System.Diagnostics;
using System.Threading;
namespace dezentrale.core
{
public abstract class BackgroundProcess
{
public IProcessController LogTarget { get; set; } = null;
public bool CancelButton { get; protected set; } = false;
public string Caption { get; protected set; } = "BackgroundProcess";
public uint Steps { get; protected set; } = 1;
protected abstract bool Run();
private Thread thread = null;
public Thread StartRun()
{
if (thread != null) return null;
thread = new Thread(RunAbstract) { IsBackground = true };
thread.Start();
return thread;
}
private void RunAbstract()
{
LogTarget.ActionCompleted(Run());
}
}
public class HelloWorldProcess : BackgroundProcess
{
public HelloWorldProcess()
{
Caption = "Hello World";
Steps = 20;
}
protected override bool Run()
{
for (uint i = 0; i < 20; i++)
{
string stepName = ((i & 0x01) == 0) ? "Hello" : "World";
LogTarget.StepStarted(i, stepName);
LogTarget.LogLine(stepName, dezentrale.model.LogEvent.ELogLevel.Info, "HelloWorldProcess");
Thread.Sleep(200);
LogTarget.StepCompleted(i, stepName, (i & 0x01) == 0);
Thread.Sleep(200);
}
return true;
}
}
}

219
core/ExportProcess.cs Normal file
View File

@ -0,0 +1,219 @@
using System;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using dezentrale.model;
namespace dezentrale.core
{
/** \brief Export functionality for the database contents
*/
public class ExportProcess : ImportExportBase
{
public ExportProcess()
{
Caption = "Database export";
Steps = 6;
}
protected override bool Run()
{
LogTarget.StepStarted(0, "Preparing export");
if (ImportExportSettings == null)
{
LogTarget.LogLine("MemberImportExport data class not set.", LogEvent.ELogLevel.Error, "ExportProcess");
return false;
}
if (MemberDir == null)
{
LogTarget.LogLine("Member directory not set.", LogEvent.ELogLevel.Error, "ExportProcess");
return false;
} else if (!Directory.Exists(MemberDir))
{
LogTarget.LogLine($"Cannot find directory '{MemberDir}'", LogEvent.ELogLevel.Error, "ExportProcess");
return false;
}
if (OutputDir == null)
{
LogTarget.LogLine("Input directory not set.", LogEvent.ELogLevel.Error, "ExportProcess");
return false;
} else if (!Directory.Exists(OutputDir))
{
LogTarget.LogLine($"Cannot find directory '{OutputDir}'", LogEvent.ELogLevel.Error, "ExportProcess");
return false;
}
//TBD: Check if newer version of database is online
LogTarget.StepStarted(1, $"Fetching commits from remote location");
string outFile = Path.Combine(OutputDir, ImportExportSettings.ZipFile);
try
{
// Depending on the directory this could be very large and would require more attention
// in a commercial package.
string[] filenames = Directory.GetFiles(MemberDir, "*.xml");
if (File.Exists(outFile))
{
LogTarget.LogLine($"Creating backup of old {outFile}", LogEvent.ELogLevel.Info, "ExportProcess");
XmlData.CreateBackup(outFile, true, "ExportProcess", true);
}
LogTarget.StepStarted(2, $"Packing {ImportExportSettings.ZipFile} ({filenames.Length} files)");
// 'using' statements guarantee the stream is closed properly which is a big source
// of problems otherwise. Its exception safe as well which is great.
using (ZipOutputStream s = new ZipOutputStream(File.Create(outFile)))
{
s.SetLevel(9); // 0 - store only ... 9 - best compression
s.Password = ImportExportSettings.ZipPassword; //null is a desired value for "no encryption"
byte[] buffer = new byte[4096];
foreach (string file in filenames)
{
//LogTarget.LogLine($"Packing {file}", LogEvent.ELogLevel.Trace, "ExportProcess");
// Using GetFileName makes the result compatible with XP
// as the resulting path is not absolute.
var entry = new ZipEntry(Path.GetFileName(file));
// Setup the entry data as required.
// Crc and size are handled by the library for seakable streams
// so no need to do them here.
// Could also use the last write time or similar for the file.
entry.DateTime = DateTime.Now;
s.PutNextEntry(entry);
using (FileStream fs = File.OpenRead(file))
{
// Using a fixed size buffer here makes no noticeable difference for output
// but keeps a lid on memory usage.
int sourceBytes;
do
{
sourceBytes = fs.Read(buffer, 0, buffer.Length);
s.Write(buffer, 0, sourceBytes);
} while (sourceBytes > 0);
}
}
// Finish/Close arent needed strictly as the using statement does this automatically
// Finish is important to ensure trailing information for a Zip file is appended. Without this
// the created file would be invalid.
s.Finish();
// Close is important to wrap things up and unlock the file.
s.Close();
}
}
catch (Exception ex)
{
LogTarget.LogLine($"Exception during packing: {ex.Message}", LogEvent.ELogLevel.Error, "ExportProcess");
// No need to rethrow the exception as for our purposes its handled.
return false;
}
if (ImportExportSettings.GpgEnabled)
{
LogTarget.StepStarted(3, $"Encrypting {ImportExportSettings.GpgFile}");
LogTarget.LogLine($"Using GPG to encrypt {Path.Combine(OutputDir, ImportExportSettings.GpgFile)}", LogEvent.ELogLevel.Info, "ExportProcess");
if (!Gpg($"--output \"{Path.Combine(OutputDir, ImportExportSettings.GpgFile)}\" -c \"{outFile}\"", ImportExportSettings.GpgPassword, LogTarget)) return false;
outFile = Path.Combine(OutputDir, ImportExportSettings.GpgFile);
}
LogTarget.StepStarted(4, $"Committing and pushing");
if (ImportExportSettings.HgEnabled)
{
LogTarget.LogLine($"Using HG to commit / push {outFile}", LogEvent.ELogLevel.Info, "ExportProcess");
//this might fail as repo might be existing / file might be already in repo
RunProcess("hg", "init", OutputDir, LogTarget);
RunProcess("hg", $"add {outFile}", OutputDir, LogTarget);
//now, committing is more interesting
if (!RunProcess("hg",
"commit"
+ $" --message \"dezentrale-members.exe --mode=export\nProgram version={Program.VersionString}\""
+ $" --user \"{ImportExportSettings.HgUserName}\"", OutputDir, LogTarget))
return false;
if (!RunProcess("hg", $"--config auth.rc.prefix={ImportExportSettings.HgURL} --config auth.rc.username={ImportExportSettings.HgUserName} --config auth.rc.password={ImportExportSettings.HgPassword} push {ImportExportSettings.HgURL}", OutputDir, LogTarget))
return false;
}
//TBD: Validate
LogTarget.StepStarted(5, $"Validating the exported data");
//extract all to
/*string verifyDirectory = Path.Combine(memberDirectory, "verify");
try { Directory.CreateDirectory(verifyDirectory); }
catch (Exception ex) { Console.WriteLine($"Error while creating verify directory:\n{ex.Message}"); return false; }
//Move all *.xml to *.bak in verify directory
string[] filenames = Directory.GetFiles(verifyDirectory, "*.xml");
foreach (string f in filenames)
{
string fileName = Path.Combine(verifyDirectory, f);
string backupName = $"{fileName}.bak";
if (File.Exists(backupName)) File.Delete(backupName);
File.Move(fileName, backupName);
File.Delete(fileName);
}
if (!Import(verifyDirectory, outputDir)) return false;
try
{
Console.WriteLine("Verify: Checking files");
filenames = Directory.GetFiles(memberDirectory, "*.xml");
foreach (string fileWithPath in filenames)
{
string fileName = Path.GetFileName(fileWithPath);
Console.WriteLine($"Checking for {fileName}");
string origFile = Path.Combine(memberDirectory, fileName);
string compareFile = Path.Combine(verifyDirectory, fileName);
if (!File.Exists(compareFile))
{
Console.WriteLine($"File doesn't exist: {compareFile}");
return false;
}
if (!FilesCompare(origFile, compareFile))
{
Console.WriteLine($"File comparison failed between: \"{compareFile}\" and \"{compareFile}\"");
return false;
}
}
filenames = Directory.GetFiles(verifyDirectory, "*.xml");
foreach (string fileWithPath in filenames)
{
string fileName = Path.GetFileName(fileWithPath);
string origFile = Path.Combine(memberDirectory, fileName);
string compareFile = Path.Combine(verifyDirectory, fileName);
if (!File.Exists(origFile))
{
Console.WriteLine($"Found extra xml in verify folder: {compareFile}");
return false;
}
}
Console.WriteLine("Verify: Done. All OK");
}
catch (Exception ex)
{
Console.WriteLine($"Exception during verify: {ex.Message}");
// No need to rethrow the exception as for our purposes its handled.
return false;
}*/
return true;
}
}
}

13
core/ILogger.cs Normal file
View File

@ -0,0 +1,13 @@
using System;
using dezentrale.model;
namespace dezentrale.core
{
public interface ILogger
{
void Clear();
void LogRaw(string text);
void LogLine(string text, LogEvent.ELogLevel logLevel = LogEvent.ELogLevel.Info, string module = "");
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace dezentrale.core
{
public interface IProcessController : ILogger
{
void ActionCompleted(bool success);
void StepStarted(uint stepNumber = 0, string stepDescription = "Generic");
void StepCompleted(uint stepNumber = 0, string stepDescription = "Generic", bool success = true);
System.Windows.Forms.DialogResult DialogResult { get; set; }
}
}

96
core/ImportExportBase.cs Normal file
View File

@ -0,0 +1,96 @@
using System;
using System.Diagnostics;
using System.Threading;
namespace dezentrale.core
{
public abstract class ImportExportBase : BackgroundProcess
{
public model.MemberImportExport ImportExportSettings { get; set; } = null;
public string MemberDir { get; set; } = null; //! Directory that holds the application settings
public string InputDir { get; set; } = null; //! Import working directory
public string OutputDir { get; set; } = null; //! Export working directory
public static bool Gpg(string args, string gpgPassword, core.ILogger log = null)
{
string argsFull = $"--batch --yes --passphrase \"{gpgPassword}\" {args}";
return RunProcess("gpg", argsFull, ".", log);
}
public static bool RunProcess(string cmd, string args, string workingDir = ".", core.ILogger log = null)
{
try
{
if (log != null)
{
log.LogLine($"Running {cmd}", model.LogEvent.ELogLevel.Info, "RunProcess");
log.LogRaw($"args: {args}");
log.LogRaw($"dir: {workingDir}");
}
else
{
Console.WriteLine($"cmd: {cmd} {args}");
Console.WriteLine($"dir: {workingDir}");
}
Process p = new Process();
p.StartInfo.FileName = cmd;
p.StartInfo.Arguments = args;
p.StartInfo.WorkingDirectory = workingDir;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
string stdout;
string stderr;
bool toReturn;
//For testing the import/export, the versioning can be omitted by using gpgOnly = true.
bool gpgOnly = false;
if (gpgOnly && !cmd.Equals("gpg"))
{
stdout = "std\r\nout";
stderr = "std\r\nerr";
Thread.Sleep(2000);
toReturn = true;
} else
{
p.Start();
stdout = p.StandardOutput.ReadToEnd();
stderr = p.StandardError.ReadToEnd();
p.WaitForExit();
toReturn = p.ExitCode == 0;
}
if (stdout.Length > 0)
{
if (log != null) log.LogRaw(stdout);
else
{
Console.WriteLine("stdout:");
Console.WriteLine(stdout);
Console.WriteLine();
}
}
if (stderr.Length > 0)
{
if (log != null) log.LogRaw(stderr);
else
{
Console.WriteLine("stderr:");
Console.WriteLine(stderr);
Console.WriteLine();
}
}
return toReturn;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
return false;
}
}
}

147
core/ImportProcess.cs Normal file
View File

@ -0,0 +1,147 @@
using System;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using dezentrale.model;
namespace dezentrale.core
{
public class ImportProcess : ImportExportBase
{
public ImportProcess()
{
Caption = "Database import";
Steps = 5;
}
protected override bool Run()
{
LogTarget.StepStarted(0, "Preparing import");
if (ImportExportSettings == null)
{
LogTarget.LogLine("MemberImportExport data class not set.", LogEvent.ELogLevel.Error, "ImportProcess");
return false;
}
if (MemberDir == null)
{
LogTarget.LogLine("Member directory not set.", LogEvent.ELogLevel.Error, "ImportProcess");
return false;
} else if (!Directory.Exists(MemberDir))
{
LogTarget.LogLine($"Cannot find directory '{MemberDir}'", LogEvent.ELogLevel.Error, "ImportProcess");
return false;
}
if (InputDir == null)
{
LogTarget.LogLine("Input directory not set.", LogEvent.ELogLevel.Error, "ImportProcess");
return false;
}
else if (!Directory.Exists(InputDir))
{
LogTarget.LogLine($"Cannot find directory '{InputDir}'", LogEvent.ELogLevel.Error, "ImportProcess");
return false;
}
LogTarget.StepStarted(1, "Fetching repository");
if (ImportExportSettings.HgEnabled)
{
RunProcess("hg", "init", InputDir, LogTarget);
RunProcess("hg", "update null", InputDir, LogTarget);
if (!RunProcess("hg", $"--config auth.rc.prefix={ImportExportSettings.HgURL} --config auth.rc.username={ImportExportSettings.HgUserName} --config auth.rc.password={ImportExportSettings.HgPassword} pull {ImportExportSettings.HgURL}", InputDir, LogTarget))
return false;
if (!RunProcess("hg", $"update", InputDir, LogTarget))
return false;
}
if (ImportExportSettings.GpgEnabled)
{
LogTarget.StepStarted(2, $"Decrypting {ImportExportSettings.GpgFile}");
LogTarget.LogLine($"Import: Using GPG to decrypt {Path.Combine(InputDir, ImportExportSettings.GpgFile)}", LogEvent.ELogLevel.Info, "ImportProcess");
if (!Gpg($"--output {Path.Combine(InputDir, ImportExportSettings.ZipFile)} --decrypt {Path.Combine(InputDir, ImportExportSettings.GpgFile)}", ImportExportSettings.GpgPassword, LogTarget)) return false;
}
try
{
LogTarget.StepStarted(3, $"Backupping Contents of {MemberDir}");
string[] filenames = Directory.GetFiles(MemberDir, "*.xml");
LogTarget.LogLine($"Backing up all xml in {MemberDir} ({filenames.Length} files)", LogEvent.ELogLevel.Info, "ImportProcess");
foreach (string f in filenames)
{
//LogTarget.LogLine($"Creating backup of {f}", LogEvent.ELogLevel.Trace, "ImportProcess");
XmlData.CreateBackup(f, false, "DbImport", true);
}
foreach (string f in filenames)
{
//LogTarget.LogLine($"Deleting backupped {f}", LogEvent.ELogLevel.Trace, "ImportProcess");
File.Delete(f);
}
System.Threading.Thread.Sleep(10);
LogTarget.StepStarted(4, $"Unpacking {ImportExportSettings.ZipFile}");
LogTarget.LogLine($"Extracting {Path.Combine(InputDir, ImportExportSettings.ZipFile)}", LogEvent.ELogLevel.Info, "ImportProcess");
string inFile = Path.Combine(InputDir, ImportExportSettings.ZipFile);
using (ZipInputStream s = new ZipInputStream(File.OpenRead(inFile)))
{
s.Password = ImportExportSettings.ZipPassword;
ZipEntry theEntry;
while ((theEntry = s.GetNextEntry()) != null)
{
Console.WriteLine(theEntry.Name);
string directoryName = Path.GetDirectoryName(theEntry.Name);
string fileName = Path.GetFileName(theEntry.Name);
// create directory
if (directoryName.Length > 0)
{
Directory.CreateDirectory(directoryName);
}
if (fileName != String.Empty)
{
using (FileStream streamWriter = File.Create(Path.Combine(MemberDir, theEntry.Name)))
{
int size = 2048;
byte[] data = new byte[2048];
while (true)
{
size = s.Read(data, 0, data.Length);
if (size > 0)
{
streamWriter.Write(data, 0, size);
}
else
{
break;
}
}
}
}
}
}
LogTarget.StepCompleted(4, $"Unpacking {ImportExportSettings.ZipFile}", true);
}
catch (Exception ex)
{
LogTarget.LogLine($"Exception during import: {ex.Message}", LogEvent.ELogLevel.Error, "ImportProcess");
return false;
}
return true;
}
}
}

View File

@ -182,6 +182,14 @@
<Compile Include="view\LVMoneyTransfers.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="view\frmProcessWithLog.cs" />
<Compile Include="core\ImportProcess.cs" />
<Compile Include="core\ILogger.cs" />
<Compile Include="core\IProcessController.cs" />
<Compile Include="core\BackgroundProcess.cs" />
<Compile Include="view\ConsoleLogger.cs" />
<Compile Include="core\ExportProcess.cs" />
<Compile Include="core\ImportExportBase.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
@ -195,5 +203,8 @@
</ItemGroup>
<ItemGroup />
<ItemGroup />
<ItemGroup>
<Folder Include="core\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -13,6 +13,8 @@ namespace dezentrale.model
[XmlElement] public MemberImportExport ImportExport{ get; set; } = new MemberImportExport();
[XmlElement] public string DbDirectory { get; set; } = DefaultDbDirectory;
//[XmlElement] public string DbBackupDirectory { get; set; } = DefaultDbBackupDirectory;
//[XmlElement] public string ImportExportDirectory { get; set; } = DefaultImportExportDirectory;
[XmlElement] public uint RegularPaymentAmount { get; set; } = 3200; //cents
[XmlElement] public string RegularPaymentCurrency { get; set; } = "EUR";
[XmlElement] public string LocalUser { get; set; } = "John Doe";
@ -26,6 +28,8 @@ namespace dezentrale.model
[XmlElement] public List<KeyValue> MoneyTransferRegEx { get; set; } = new List<KeyValue>(); //This doesn't belong here! Move to new file within db-data!
[XmlElement] public DateTime LastCronjobRun { get; set; } = DateTime.Now; //This doesn't belong here! Move to new file within db-data!
[XmlIgnore] public static string DefaultDbDirectory{ get; private set; } = "db-data";
[XmlIgnore] public static string DefaultDbDirectory { get; private set; } = "db-data";
//[XmlIgnore] public static string DefaultDbBackupDirectory { get; private set; } = "db-backup";
//[XmlIgnore] public static string DefaultImportExportDirectory { get; private set; } = "import-export";
}
}

View File

@ -27,8 +27,19 @@ namespace dezentrale.model
EMail,
// TODO: [Obsolete("Use ELogLevel.Error instead")]
Error,
}
public enum ELogLevel
{
Trace = -1,
Debug = 0,
Info = 1,
Warning = 2,
Error = 3,
}
[XmlAttribute] public DateTime Timestamp { get; set; } = DateTime.Now;
[XmlAttribute] public string LocalUser { get; set; } = Program.config.LocalUser;
[XmlElement("SubEvent")] public List<LogSubEvent> SubEvents { get; set; } = new List<LogSubEvent>();

View File

@ -8,115 +8,19 @@ namespace dezentrale.model
{
public class MemberImportExport
{
[XmlElement] public string ZipFile { get; set; } = "fnord.zip";
[XmlElement] public string ZipPassword { get; set; } = "";
[XmlElement] public bool GpgEnabled { get; set; } = true;
[XmlElement] public string GpgFile { get; set; } = "fnord.gpg";
[XmlElement] public string GpgPassword { get; set; } = "fnord";
[XmlElement] public bool HgEnabled { get; set; } = false;
[XmlElement] public string HgUserName { get; set; } = "";
[XmlElement] public string HgPassword { get; set; } = "";
[XmlElement] public string HgURL { get; set; } = "";
[XmlElement] public bool GitEnabled { get; set; } = false;
[XmlElement] public string GitUserName { get; set; } = "";
[XmlElement] public string GitPassword { get; set; } = "";
[XmlElement] public string ZipFile { get; set; } = "fnord.zip";
[XmlElement] public string ZipPassword { get; set; } = "";
[XmlElement] public bool GpgEnabled { get; set; } = true;
[XmlElement] public string GpgFile { get; set; } = "fnord.gpg";
[XmlElement] public string GpgPassword { get; set; } = "fnord";
[XmlElement] public bool HgEnabled { get; set; } = false;
[XmlElement] public string HgUserName { get; set; } = "";
[XmlElement] public string HgPassword { get; set; } = "";
[XmlElement] public string HgURL { get; set; } = "";
[XmlElement] public bool GitEnabled { get; set; } = false;
[XmlElement] public string GitUserName { get; set; } = "";
[XmlElement] public string GitPassword { get; set; } = "";
private bool RunProcess(string cmd, string args, string workingDir = ".")
{
try
{
Console.WriteLine($"cmd: {cmd} {args}");
Console.WriteLine($"dir: {workingDir}");
Process p = new Process();
p.StartInfo.FileName = cmd;
p.StartInfo.Arguments = args;
p.StartInfo.WorkingDirectory = workingDir;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.Start();
string stdout = p.StandardOutput.ReadToEnd();
string stderr = p.StandardError.ReadToEnd();
p.WaitForExit();
if (stdout.Length > 0)
{
Console.WriteLine("stdout:");
Console.WriteLine(stdout);
Console.WriteLine();
}
if (stderr.Length > 0)
{
Console.WriteLine("stderr:");
Console.WriteLine(stderr);
Console.WriteLine();
}
return p.ExitCode == 0;
} catch(Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
return false;
}
private bool Gpg(string args)
{
string argsFull = $"--batch --yes --passphrase \"{GpgPassword}\" {args}";
return RunProcess("gpg", argsFull);
}
public bool Export(string memberDirectory, string outputDir)
{
string tmpFile = Path.Combine(outputDir, ZipFile);
Console.WriteLine($"Export: Packing {tmpFile}");
if (ZipExport(memberDirectory, outputDir) != true) return false;
if (GpgEnabled)
{
Console.WriteLine($"Export: Using GPG to encrypt {Path.Combine(outputDir, GpgFile)}");
if (!Gpg($"--output \"{Path.Combine(outputDir, GpgFile)}\" -c \"{tmpFile}\"")) return false;
tmpFile = Path.Combine(outputDir, GpgFile);
}
if (HgEnabled)
{
Console.WriteLine($"Export: Using HG to commit / push {tmpFile}");
//this might fail as repo might be existing / file might be already in repo
RunProcess("hg", "init", outputDir);
RunProcess("hg", $"add {tmpFile}", outputDir);
//now, committing is more interesting
if (!RunProcess("hg",
"commit"
+ $" --message \"dezentrale-members.exe --mode=export\nProgram version={Program.VersionString}\""
+ $" --user \"{HgUserName}\"", outputDir))
return false;
if (!RunProcess("hg", $"--config auth.rc.prefix={HgURL} --config auth.rc.username={HgUserName} --config auth.rc.password={HgPassword} push {HgURL}", outputDir))
return false;
}
return true;
}
public bool Import(string memberDirectory, string inputDir)
{
if(HgEnabled)
{
RunProcess("hg", "init", inputDir);
RunProcess("hg", "update null", inputDir);
if (!RunProcess("hg", $"--config auth.rc.prefix={HgURL} --config auth.rc.username={HgUserName} --config auth.rc.password={HgPassword} pull {HgURL}", inputDir))
return false;
if (!RunProcess("hg", $"update", inputDir))
return false;
}
if (GpgEnabled)
{
Console.WriteLine($"Import: Using GPG to decrypt {Path.Combine(inputDir, GpgFile)}");
if (!Gpg($"--output {Path.Combine(inputDir, ZipFile)} --decrypt {Path.Combine(inputDir, GpgFile)}")) return false;
}
Console.WriteLine($"Import: Extracting {Path.Combine(inputDir, ZipFile)}");
if (ZipImport(memberDirectory, inputDir) != true) return false;
return true;
}
private bool FilesCompare(string file1, string file2)
{
try
@ -136,248 +40,5 @@ namespace dezentrale.model
}
return true;
}
public bool VerifyExport(string memberDirectory, string outputDir)
{
if (!Directory.Exists(memberDirectory))
{
Console.WriteLine($"Cannot find directory '{memberDirectory}'");
return false;
}
if (!Directory.Exists(outputDir))
{
Console.WriteLine($"Cannot find directory '{outputDir}'");
return false;
}
Console.WriteLine("Verifying exported");
//extract all to
string verifyDirectory = Path.Combine(memberDirectory, "verify");
try { Directory.CreateDirectory(verifyDirectory); }
catch (Exception ex) { Console.WriteLine($"Error while creating verify directory:\n{ex.Message}"); return false; }
//Move all *.xml to *.bak in verify directory
string[] filenames = Directory.GetFiles(verifyDirectory, "*.xml");
foreach (string f in filenames)
{
string fileName = Path.Combine(verifyDirectory, f);
string backupName = $"{fileName}.bak";
if (File.Exists(backupName)) File.Delete(backupName);
File.Move(fileName, backupName);
File.Delete(fileName);
}
if (!Import(verifyDirectory, outputDir)) return false;
try
{
Console.WriteLine("Verify: Checking files");
filenames = Directory.GetFiles(memberDirectory, "*.xml");
foreach (string fileWithPath in filenames)
{
string fileName = Path.GetFileName(fileWithPath);
Console.WriteLine($"Checking for {fileName}");
string origFile = Path.Combine(memberDirectory, fileName);
string compareFile = Path.Combine(verifyDirectory, fileName);
if (!File.Exists(compareFile))
{
Console.WriteLine($"File doesn't exist: {compareFile}");
return false;
}
if (!FilesCompare(origFile, compareFile))
{
Console.WriteLine($"File comparison failed between: \"{compareFile}\" and \"{compareFile}\"");
return false;
}
}
filenames = Directory.GetFiles(verifyDirectory, "*.xml");
foreach (string fileWithPath in filenames)
{
string fileName = Path.GetFileName(fileWithPath);
string origFile = Path.Combine(memberDirectory, fileName);
string compareFile = Path.Combine(verifyDirectory, fileName);
if (!File.Exists(origFile))
{
Console.WriteLine($"Found extra xml in verify folder: {compareFile}");
return false;
}
}
Console.WriteLine("Verify: Done. All OK");
}
catch (Exception ex)
{
Console.WriteLine($"Exception during verify: {ex.Message}");
// No need to rethrow the exception as for our purposes its handled.
return false;
}
return true;
}
private bool ZipExport(string memberDirectory, string outputDir)
{
if (!Directory.Exists(memberDirectory))
{
Console.WriteLine($"Cannot find directory '{memberDirectory}'");
return false;
}
if (!Directory.Exists(outputDir))
{
Console.WriteLine($"Cannot find directory '{outputDir}'");
return false;
}
try
{
// Depending on the directory this could be very large and would require more attention
// in a commercial package.
string[] filenames = Directory.GetFiles(memberDirectory, "*.xml");
string outFile = Path.Combine(outputDir, ZipFile);
if (File.Exists(outFile))
{
string backupName = $"{outFile}.bak";
if (File.Exists(backupName)) File.Delete(backupName);
File.Move(outFile, backupName);
}
// 'using' statements guarantee the stream is closed properly which is a big source
// of problems otherwise. Its exception safe as well which is great.
using (ZipOutputStream s = new ZipOutputStream(File.Create(outFile)))
{
s.SetLevel(9); // 0 - store only to 9 - means best compression
s.Password = ZipPassword; //null is a desired value for "no encryption"
byte[] buffer = new byte[4096];
foreach (string file in filenames)
{
Console.WriteLine(file);
// Using GetFileName makes the result compatible with XP
// as the resulting path is not absolute.
var entry = new ZipEntry(Path.GetFileName(file));
// Setup the entry data as required.
// Crc and size are handled by the library for seakable streams
// so no need to do them here.
// Could also use the last write time or similar for the file.
entry.DateTime = DateTime.Now;
s.PutNextEntry(entry);
using (FileStream fs = File.OpenRead(file))
{
// Using a fixed size buffer here makes no noticeable difference for output
// but keeps a lid on memory usage.
int sourceBytes;
do
{
sourceBytes = fs.Read(buffer, 0, buffer.Length);
s.Write(buffer, 0, sourceBytes);
} while (sourceBytes > 0);
}
}
// Finish/Close arent needed strictly as the using statement does this automatically
// Finish is important to ensure trailing information for a Zip file is appended. Without this
// the created file would be invalid.
s.Finish();
// Close is important to wrap things up and unlock the file.
s.Close();
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception during export: {ex.Message}");
// No need to rethrow the exception as for our purposes its handled.
return false;
}
return true;
}
private bool ZipImport(string memberDirectory, string inputDir)
{
if (!Directory.Exists(memberDirectory))
{
Console.WriteLine($"Cannot find directory '{memberDirectory}'");
return false;
}
if (!Directory.Exists(inputDir))
{
Console.WriteLine($"Cannot find directory '{inputDir}'");
return false;
}
try
{
//First, rename all member-*.xml to member-*.xml.bak
string[] filenames = Directory.GetFiles(memberDirectory, "*.xml");
foreach (string f in filenames)
{
string fileName = Path.GetFileName(f);
string backupName = $"{fileName}.bak";
if (File.Exists(backupName)) File.Delete(backupName);
File.Move(fileName, backupName);
File.Delete(fileName);
}
string inFile = Path.Combine(inputDir, ZipFile);
using (ZipInputStream s = new ZipInputStream(File.OpenRead(inFile)))
{
s.Password = ZipPassword;
ZipEntry theEntry;
while ((theEntry = s.GetNextEntry()) != null)
{
Console.WriteLine(theEntry.Name);
string directoryName = Path.GetDirectoryName(theEntry.Name);
string fileName = Path.GetFileName(theEntry.Name);
// create directory
if (directoryName.Length > 0)
{
Directory.CreateDirectory(directoryName);
}
if (fileName != String.Empty)
{
using (FileStream streamWriter = File.Create(Path.Combine(memberDirectory, theEntry.Name)))
{
int size = 2048;
byte[] data = new byte[2048];
while (true)
{
size = s.Read(data, 0, data.Length);
if (size > 0)
{
streamWriter.Write(data, 0, size);
}
else
{
break;
}
}
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception during import: {ex.Message}");
// No need to rethrow the exception as for our purposes its handled.
return false;
}
return true;
}
}
}

View File

@ -10,7 +10,36 @@ namespace dezentrale.model
public class XmlData
{
[XmlAttribute] public DateTime LastChanged { get; set; } = DateTime.Now;
[XmlAttribute] public string ProgramVersion { get; set; } = "";
[XmlAttribute] public string ProgramVersion { get; set; } = "";
/** \brief Move or make a backup copy of the specified file to the same folder. Will overwrite old backups if @ref permanentWithTimestamp is false
* \param name="fileWithPath" Source file to copy. File must exist.
* \param name="backupName" Optional name for a backup. E.g. "PreImport" or "NewProgVersion"
* \param param name="permanentWithTimestamp" Stores the backup including a timestamp (@ref DateTime.Now) and fails if the target file already exists.
* \return the resulting name on success.
* \note this method throws Exceptions when failing.
*/
public static string CreateBackup(string fileWithPath, bool fileMove = true, string backupName = null, bool permanentWithTimestamp = false)
{
if (!File.Exists(fileWithPath)) throw new FileNotFoundException(fileWithPath);
string backupFile = fileWithPath;
if (backupName != null) backupFile += "." + backupName;
if (permanentWithTimestamp) backupFile += "." + DateTime.Now.ToString("yyMMdd_HHmmss");
backupFile += ".bak";
if (File.Exists(backupFile))
{
if (permanentWithTimestamp)
throw new IOException($"File {backupFile} already exists");
File.Delete(backupFile);
}
if (fileMove) File.Move(fileWithPath, backupFile);
else File.Copy(fileWithPath, backupFile);
return backupName;
}
public static XmlData LoadFromFile(string fileName, Type type = null)
{
@ -26,10 +55,14 @@ namespace dezentrale.model
{
Console.WriteLine($"Object of type {ds.GetType()} was stored in Version {ds.ProgramVersion:X8}, Re-Storing with {Program.VersionNumber:X8}");
//backup file
string backupName = $"{fileName}.v{ds.ProgramVersion:X8}.bak";
if (File.Exists(backupName)) File.Delete(backupName);
File.Move(fileName, backupName);
if (!SaveToFile(fileName, ds)) throw new Exception("Saving returned false");
try
{
CreateBackup(fileName, true, $"v{ds.ProgramVersion:X8}", true);
if (!SaveToFile(fileName, ds)) throw new Exception("Saving returned false");
} catch(Exception e2)
{
Console.WriteLine($"Error {e2.GetType()}: {e2.Message}");
}
}
return ds;
}
@ -48,9 +81,10 @@ namespace dezentrale.model
if (File.Exists(fileName))
{
string backupName = $"{fileName}.bak";
if (File.Exists(backupName)) File.Delete(backupName);
File.Move(fileName, backupName);
CreateBackup(fileName);
//string backupName = $"{fileName}.bak";
//if (File.Exists(backupName)) File.Delete(backupName);
//File.Move(fileName, backupName);
}
Console.WriteLine($"XmlData.SaveToFile({ds.GetType()})");
ds.ProgramVersion = Program.VersionString;

54
view/ConsoleLogger.cs Normal file
View File

@ -0,0 +1,54 @@
using System;
using dezentrale.model;
namespace dezentrale.view
{
public class ConsoleLogger : core.IProcessController
{
public System.Windows.Forms.DialogResult DialogResult { get; set; } = System.Windows.Forms.DialogResult.Cancel;
public ConsoleLogger()
{
}
public System.Threading.Thread StartRunProcess(core.BackgroundProcess process)
{
Console.WriteLine($"Process - {process.Caption} (running)");
System.Threading.Thread t = process.StartRun();
if(t == null)
{
Console.WriteLine($"Process - {process.Caption} (starting error)");
}
return t;
}
public void Clear()
{
Console.Clear();
}
public void LogLine(string text, LogEvent.ELogLevel logLevel = LogEvent.ELogLevel.Info, string module = "")
{
Console.WriteLine($"{DateTime.Now} [{module}] {logLevel} {text}");
}
public void LogRaw(string text)
{
Console.WriteLine(text);
}
public void StepStarted(uint stepNumber = 0, string stepDescription = "Generic")
{
Console.WriteLine($"# {stepNumber + 1}: Started {stepDescription}");
}
public void StepCompleted(uint stepNumber = 0, string stepDescription = "Generic", bool success = true)
{
Console.WriteLine($"# {stepNumber + 1}: Completed({success}) {stepDescription}");
}
public void ActionCompleted(bool success)
{
if (success) Console.WriteLine($"Process - done (OK)");
else Console.WriteLine($"Process - done (ERROR)");
}
}
}

View File

@ -29,8 +29,9 @@ namespace dezentrale.view
//This is a workaround for checkboxes keep changing when MultiSelect with Shift
if ((ModifierKeys & (Keys.Shift | Keys.Control)) != 0)
e.NewValue = e.CurrentValue;
};
};
MenuItem filterMenu = cm.MenuItems.Add("Filter by column...");
foreach (ConfigLVDataHandler col in actualColumns)
{
if(col.FilterAvailable)
@ -39,6 +40,12 @@ namespace dezentrale.view
}
if (col.Visible) this.Columns.Add(new ColumnHeader() { Width = col.Width, Text = col.Display, TextAlign = col.TextAlign, Tag = col, });
}
if(filterMenu.MenuItems.Count < 1)
{
MenuItem unavailable = new MenuItem("unavailable");
unavailable.Enabled = false;
filterMenu.MenuItems.Add(unavailable);
}
}
public CustomListView(List<ConfigLVColumn> actualColumns, EventHandler<ColumnsChangedArgs> ColumnsChanged = null, bool checkBoxes = true, bool columnConfiguration = true) : base()
{

View File

@ -139,6 +139,12 @@ namespace dezentrale.view
btnLoadList.Visible = false;
mT.LoadFromList(entries);
}
public void ClearList()
{
btnLoadList.Visible = true;
mT.Items.Clear();
}
private static void LVMoneyTransfers_ColumnsChanged(object sender, ColumnsChangedArgs e)
{
Console.WriteLine("LVMoneyTransfers_ColumnsChanged");

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using dezentrale.core;
using dezentrale.model;
using dezentrale.model.money;
@ -120,28 +121,41 @@ namespace dezentrale.view
}
private void mnuMain_File_Export(object sender, EventArgs e)
{
if (!Program.config.ImportExport.Export(Program.config.DbDirectory, Program.DmDirectory))
{
MessageBox.Show("Export failed!");
return;
}
if (!Program.config.ImportExport.VerifyExport(Program.config.DbDirectory, Program.DmDirectory))
{
MessageBox.Show("Export verify failed!");
return;
}
ExportProcess export = new ExportProcess()
{
ImportExportSettings = Program.config.ImportExport,
MemberDir = Program.config.DbDirectory,
OutputDir = Program.DmDirectory,
};
frmProcessWithLog frmImport = new frmProcessWithLog(export, false);
frmImport.ShowDialog();
}
private void mnuMain_File_Import(object sender, EventArgs e)
{
DialogResult dr = MessageBox.Show("Overwrite database?", "Warning\nImporting database from external source will overwrite all\nlocal changes! Really import?", MessageBoxButtons.YesNo);
DialogResult dr = MessageBox.Show("Warning\nImporting database from external source will overwrite all\nlocal changes! Really import?", "Overwrite local version of the database?", MessageBoxButtons.YesNo);
if(dr == DialogResult.Yes)
{
if (!Program.config.ImportExport.Import(Program.config.DbDirectory, Program.DmDirectory))
{
MessageBox.Show("Import failed!");
}
Application.Restart();
{
ImportProcess import = new ImportProcess()
{
ImportExportSettings = Program.config.ImportExport,
MemberDir = Program.config.DbDirectory,
InputDir = Program.DmDirectory,
};
frmProcessWithLog frmImport = new frmProcessWithLog(import, true);
dr = frmImport.ShowDialog();
MessageBox.Show("DialogResult=" + dr);
if (dr == DialogResult.Yes)
{
if (Program.LoadFiles() != 0)
{
MessageBox.Show("Error while loading imported files.\nProgram will close now.");
this.Close();
}
lstMembers.LoadFromList(Program.members.Entries);
mtv.ClearList();
}
}
}

185
view/frmProcessWithLog.cs Normal file
View File

@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using dezentrale.core;
using dezentrale.model;
namespace dezentrale.view
{
public class frmProcessWithLog : Form, IProcessController
{
private DialogResult toReturn = DialogResult.Cancel;
private TextBox tbLog = null;
private Label lblStepStatus = null;
private PictureBox picStatus = null;
private Button btnStart = null;
private Button btnCancel = null;
private Button btnClose = null;
private BackgroundProcess process;
public void Clear()
{
tbLog.Invoke(new Action(() => tbLog.Clear()));
Console.Clear();
}
private void printline(string line)
{
tbLog.Invoke(new Action(() =>
tbLog.AppendText(line + "\r\n")
))
;
Console.WriteLine(line);
}
public void LogRaw(string text)
{
printline(text);
}
public void LogLine(string text, LogEvent.ELogLevel logLevel, string module)
{
string output = $"{DateTime.Now} [{module}] {logLevel} {text}";
printline(output);
}
public frmProcessWithLog(BackgroundProcess process, bool autoRun = false)
{
this.process = process;
this.process.LogTarget = this;
this.Text = $"Process - {this.process.Caption} (waiting)";
this.Width = 800;
this.Height = 600;
this.StartPosition = FormStartPosition.CenterParent;
this.FormClosing += (sender, e) => { DialogResult = toReturn; };
this.Load += (sender, e) => { if (autoRun) btnStart_Click(null, null); };
// build GUI
//[TextBox Multiline ]
//[Step display label ]
//[SuccessImg] [Start] [Cancel] [Close]
tbLog = new TextBox()
{
Location = new System.Drawing.Point(5, 5),
Size = new Size(this.Width - 15, this.Height - 95),
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Multiline = true,
ScrollBars = ScrollBars.Both,
};
this.Controls.Add(tbLog);
picStatus = new PictureBox()
{
Location = new System.Drawing.Point(5, this.Height - 80),
Size = new Size(48, 48),
Anchor = AnchorStyles.Left | AnchorStyles.Bottom,
BackColor = Color.White,
};
this.Controls.Add(picStatus);
lblStepStatus = new Label()
{
Location = new System.Drawing.Point(60, this.Height - 80),
AutoSize = false,
Size = new Size(this.Width - 10, 20),
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Text = "Ready.",
};
this.Controls.Add(lblStepStatus);
btnStart = new Button()
{
Text = "Start",
Location = new System.Drawing.Point(this.Width - 260, this.Height - 55),
Anchor = AnchorStyles.Right | AnchorStyles.Bottom,
Enabled = !autoRun,
};
btnStart.Height += 2;
btnStart.Click += btnStart_Click;
this.Controls.Add(btnStart);
btnCancel = new Button()
{
Text = "Cancel",
Location = new System.Drawing.Point(this.Width - 175, this.Height - 55),
Anchor = AnchorStyles.Right | AnchorStyles.Bottom,
Enabled = false,
};
btnCancel.Height += 2;
btnCancel.Click += btnCancel_Click;
this.Controls.Add(btnCancel);
btnClose = new Button()
{
Text = "Close",
Location = new System.Drawing.Point(this.Width - 90, this.Height - 55),
Anchor = AnchorStyles.Right | AnchorStyles.Bottom,
Enabled = true,
};
btnClose.Height += 2;
btnClose.Click += btnClose_Click;
this.Controls.Add(btnClose);
}
private void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
btnCancel.Enabled = true;
btnClose.Enabled = false;
this.Text = $"Process - {this.process.Caption} (running)";
process.StartRun();
}
private void btnCancel_Click(object sender, EventArgs e)
{
btnCancel.Enabled = false;
this.Text = $"Process - {this.process.Caption} (cancelled)";
}
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
public void StepStarted(uint stepNumber, string stepDescription)
{
lblStepStatus.Invoke(new Action(() =>
lblStepStatus.Text = $"# {stepNumber + 1}/{this.process.Steps} : {stepDescription}"
))
;
}
public void StepCompleted(uint stepNumber, string stepDescription, bool success)
{
lblStepStatus.Invoke(new Action(() =>
lblStepStatus.Text = $"# {stepNumber + 1}/{this.process.Steps} : Completed({success}) {stepDescription}"
))
;
}
public void ActionCompleted(bool success)
{
toReturn = success? DialogResult.Yes : DialogResult.No;
btnCancel.Invoke(new Action(() =>
btnCancel.Enabled = false
))
;
btnClose.Invoke(new Action(() =>
btnClose.Enabled = true
))
;
picStatus.Invoke(new Action(() =>
picStatus.BackColor = (success ? Color.LightGreen : Color.Red)
))
;
this.Invoke(new Action(() =>
this.Text = $"Process - {this.process.Caption}" + (success ? " (done)" : " (failed)")
))
;
}
}
}