191204PX Merge with default: Reworked reloading of files, declared process_window feature done for now.
This commit is contained in:
commit
d1fa9744d3
131
Program.cs
131
Program.cs
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = "");
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)")
|
||||
))
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue