From e7bfc69842e091dc493619f5121366b6373a65d8 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Mon, 15 Apr 2024 15:22:04 +0200 Subject: [PATCH] [#3] Elwig: Add feature to sync deliveries --- Elwig/Helpers/Config.cs | 6 + Elwig/Helpers/Export/ElwigData.cs | 251 ++++++++++++++++++++++ Elwig/Helpers/ExportMode.cs | 2 +- Elwig/Helpers/Utils.cs | 43 +++- Elwig/Windows/DeliveryAdminWindow.xaml | 11 + Elwig/Windows/DeliveryAdminWindow.xaml.cs | 63 ++++++ Elwig/Windows/MainWindow.xaml | 4 + Elwig/Windows/MainWindow.xaml.cs | 57 ++++- Installer/Files/config.ini | 5 + 9 files changed, 431 insertions(+), 11 deletions(-) create mode 100644 Elwig/Helpers/Export/ElwigData.cs diff --git a/Elwig/Helpers/Config.cs b/Elwig/Helpers/Config.cs index 172ff7f..51f3004 100644 --- a/Elwig/Helpers/Config.cs +++ b/Elwig/Helpers/Config.cs @@ -41,6 +41,9 @@ namespace Elwig.Helpers { public string? Branch = null; public string? UpdateUrl = null; public bool UpdateAuto = false; + public string? SyncUrl = null; + public string SyncUsername = ""; + public string SyncPassword = ""; public string? SmtpHost = null; public int? SmtpPort = null; @@ -71,6 +74,9 @@ namespace Elwig.Helpers { Debug = TrueValues.Contains(config["general:debug"]?.ToLower()); UpdateUrl = config["update:url"]; UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower()); + SyncUrl = config["sync:url"]; + SyncUsername = config["sync:username"] ?? ""; + SyncPassword = config["sync:password"] ?? ""; SmtpHost = config["smtp:host"]; SmtpPort = config["smtp:port"]?.All(char.IsAsciiDigit) == true && config["smtp:port"]?.Length > 0 ? int.Parse(config["smtp:port"]!) : null; diff --git a/Elwig/Helpers/Export/ElwigData.cs b/Elwig/Helpers/Export/ElwigData.cs new file mode 100644 index 0000000..5944370 --- /dev/null +++ b/Elwig/Helpers/Export/ElwigData.cs @@ -0,0 +1,251 @@ +using System.IO.Compression; +using System.IO; +using System.Threading.Tasks; +using Elwig.Models.Entities; +using System.Collections.Generic; +using System; +using System.Text.Json.Nodes; +using System.Linq; +using System.Windows; +using Microsoft.EntityFrameworkCore; + +namespace Elwig.Helpers.Export { + public static class ElwigData { + + public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt"); + + public static async Task GetImportedFiles() { + try { + return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8); + } catch { + return []; + } + } + + public static async Task AddImportedFiles(IEnumerable filenames) { + await File.AppendAllLinesAsync(ImportedTxt, filenames, Utils.UTF8); + } + + public static Task Import(string filename, bool interactive) => Import([filename], interactive); + + public static async Task Import(IEnumerable filenames, bool interactive) { + try { + using var ctx = new AppDbContext(); + var currentDids = await ctx.Deliveries + .GroupBy(d => d.Year) + .ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId)); + + var deliveries = new List(); + var deliveryParts = new List(); + var modifiers = new List(); + + var metaData = new List<(string Name, int DeliveryNum, string Filters)>(); + + foreach (var filename in filenames) { + using var zip = ZipFile.Open(filename, ZipArchiveMode.Read); + + var version = zip.GetEntry("version"); + using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) { + if (await reader.ReadToEndAsync() != "elwig:1") + throw new FileFormatException("Ungültige Export-Datei"); + } + + var metaJson = zip.GetEntry("meta.json"); + var meta = await JsonNode.ParseAsync(metaJson!.Open()); + var deliveryCount = meta!["deliveries"]?["count"]!.AsValue().GetValue(); + var deliveryFilters = meta!["deliveries"]?["filters"]!.AsArray().Select(f => f!.AsValue().GetValue()).ToArray(); + if (deliveryCount != null && deliveryFilters != null) + metaData.Add((Path.GetFileName(filename), (int)deliveryCount, string.Join(" / ", deliveryFilters))); + + var membersJson = zip.GetEntry("members.json"); + if (membersJson != null) { + using var reader = new StreamReader(membersJson.Open(), Utils.UTF8); + string? line; + while ((line = await reader.ReadLineAsync()) != null) { + var obj = JsonNode.Parse(line)!.AsObject(); + // TODO import members.json + } + } + + var areaComsJson = zip.GetEntry("area_commitments.json"); + if (areaComsJson != null) { + using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8); + string? line; + while ((line = await reader.ReadLineAsync()) != null) { + var obj = JsonNode.Parse(line)!.AsObject(); + // TODO import area_commitments.json + } + } + + var deliveriesJson = zip.GetEntry("deliveries.json"); + if (deliveriesJson != null) { + using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8); + string? line; + while ((line = await reader.ReadLineAsync()) != null) { + var obj = JsonNode.Parse(line)!.AsObject(); + var (d, parts, mods) = JsonToDelivery(obj, currentDids); + deliveries.Add(d); + deliveryParts.AddRange(parts); + modifiers.AddRange(mods); + } + } + } + + var lsnrs = deliveries.Select(d => d.LsNr).ToList(); + var duplicateLsNrs = await ctx.Deliveries + .Where(d => lsnrs.Contains(d.LsNr)) + .Select(d => d.LsNr) + .ToListAsync(); + var duplicateDIds = deliveries + .Where(d => duplicateLsNrs.Contains(d.LsNr)) + .Select(d => (d.Year, d.DId)) + .ToList(); + bool overwriteDelivieries = false; + if (duplicateLsNrs.Count > 0) { + var res = MessageBox.Show($"Sollen {duplicateLsNrs.Count} Lieferungen überschreiben werden?", "Lieferungen überschreiben", + MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No); + overwriteDelivieries = res == MessageBoxResult.Yes; + } + if (overwriteDelivieries) { + ctx.RemoveRange(ctx.Deliveries.Where(d => duplicateLsNrs.Contains(d.LsNr))); + ctx.AddRange(deliveries); + ctx.AddRange(deliveryParts); + ctx.AddRange(modifiers); + } else { + ctx.AddRange(deliveries.Where(d => !duplicateDIds.Contains((d.Year, d.DId)))); + ctx.AddRange(deliveryParts.Where(p => !duplicateDIds.Contains((p.Year, p.DId)))); + ctx.AddRange(modifiers.Where(m => !duplicateDIds.Contains((m.Year, m.DId)))); + } + await ctx.SaveChangesAsync(); + await AddImportedFiles(filenames.Select(f => Path.GetFileName(f))); + await App.HintContextChange(); + + MessageBox.Show( + $"Das importieren der Daten war erfolgreich!\n" + + $"Folgendes wurde importiert:\n" + + $" Lieferungen: {deliveries.Count}\n" + + string.Join("\n", metaData.Select(d => $" {d.Name} ({d.DeliveryNum})\n {d.Filters}")) + + "\n", "Importieren erfolgreich", + MessageBoxButton.OK, MessageBoxImage.Information); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + public static async Task ExportDeliveries(string filename, IEnumerable deliveries, IEnumerable filters) { + File.Delete(filename); + using var zip = ZipFile.Open(filename, ZipArchiveMode.Create); + + var version = zip.CreateEntry("version", CompressionLevel.NoCompression); + using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) { + await writer.WriteAsync("elwig:1"); + } + + var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression); + using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) { + await writer.WriteAsync( + $"{{\"timestamp\": \"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}\", " + + $"\"zwstid\": \"{App.ZwstId}\", \"device\": \"{Environment.MachineName}\", " + + $"\"deliveries\": {{" + + $"\"count\": {deliveries.Count()}, " + + $"\"parts\": {deliveries.Sum(d => d.Parts.Count)}, " + + $"\"filters\": [{string.Join(", ", filters.Select(f => '"' + f + '"'))}]" + + $"}}}}"); + } + + var json = zip.CreateEntry("deliveries.json"); + using (var writer = new StreamWriter(json.Open(), Utils.UTF8)) { + foreach (var d in deliveries) { + await writer.WriteLineAsync(DeliveryToJson(d).ToJsonString()); + } + } + } + + public static JsonObject DeliveryToJson(Delivery d) { + return new JsonObject { + ["lsnr"] = d.LsNr, + ["year"] = d.Year, + ["date"] = $"{d.Date:yyyy-MM-dd}", + ["zwstid"] = d.ZwstId, + ["lnr"] = d.LNr, + ["time"] = d.Time != null ? $"{d.Time:HH:mm:ss}" : null, + ["mgnr"] = d.MgNr, + ["parts"] = new JsonArray(d.Parts.OrderBy(p => p.DPNr).Select(p => { + var obj = new JsonObject { + ["dpnr"] = p.DPNr, + ["sortid"] = p.SortId, + ["attrid"] = p.AttrId, + ["cultid"] = p.CultId, + ["weight"] = p.Weight, + ["kmw"] = p.Kmw, + ["qualid"] = p.QualId, + ["hkid"] = p.HkId, + ["kgnr"] = p.KgNr, + ["rdnr"] = p.RdNr, + ["net_weight"] = p.IsNetWeight, + ["manual_weighing"] = p.IsManualWeighing, + ["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()), + ["comment"] = p.Comment, + }; + if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck; + if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked; + if (p.IsLesewagen != null) obj["lesewagen"] = p.IsLesewagen; + if (p.IsGebunden != null) obj["gebunden"] = p.IsGebunden; + if (p.Temperature != null) obj["temperature"] = p.Temperature; + if (p.Acid != null) obj["acid"] = p.Acid; + if (p.ScaleId != null) obj["scale_id"] = p.ScaleId; + if (p.WeighingId != null) obj["weighing_id"] = p.WeighingId; + if (p.WeighingReason != null) obj["weighing_reason"] = p.WeighingReason; + return obj; + }).ToArray()), + ["comment"] = d.Comment, + }; + } + + public static (Delivery, List, List) JsonToDelivery(JsonNode json, Dictionary currentDids) { + var year = json["year"]!.AsValue().GetValue(); + var did = ++currentDids[year]; + return (new Delivery { + Year = year, + DId = did, + DateString = json["date"]!.AsValue().GetValue(), + TimeString = json["time"]?.AsValue().GetValue(), + ZwstId = json["zwstid"]!.AsValue().GetValue(), + LNr = json["lnr"]!.AsValue().GetValue(), + LsNr = json["lsnr"]!.AsValue().GetValue(), + MgNr = json["mgnr"]!.AsValue().GetValue(), + Comment = json["comment"]?.AsValue().GetValue(), + }, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => new DeliveryPart { + Year = year, + DId = did, + DPNr = p["dpnr"]!.AsValue().GetValue(), + SortId = p["sortid"]!.AsValue().GetValue(), + AttrId = p["attrid"]?.AsValue().GetValue(), + CultId = p["cultid"]?.AsValue().GetValue(), + Weight = p["weight"]!.AsValue().GetValue(), + Kmw = p["kmw"]!.AsValue().GetValue(), + QualId = p["qualid"]!.AsValue().GetValue(), + HkId = p["hkid"]!.AsValue().GetValue(), + KgNr = p["kgnr"]?.AsValue().GetValue(), + RdNr = p["rdnr"]?.AsValue().GetValue(), + IsNetWeight = p["net_weight"]!.AsValue().GetValue(), + IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue(), + Comment = p["comment"]?.AsValue().GetValue(), + IsSplCheck = p["spl_check"]?.AsValue().GetValue() ?? false, + IsHandPicked = p["hand_picked"]?.AsValue().GetValue(), + IsLesewagen = p["lesewagen"]?.AsValue().GetValue(), + IsGebunden = p["gebunden"]?.AsValue().GetValue(), + Temperature = p["temperature"]?.AsValue().GetValue(), + Acid = p["acid"]?.AsValue().GetValue(), + ScaleId = p["scale_id"]?.AsValue().GetValue(), + WeighingId = p["weighing_id"]?.AsValue().GetValue(), + WeighingReason = p["weighing_reason"]?.AsValue().GetValue(), + }).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier { + Year = year, + DId = did, + DPNr = p["dpnr"]!.AsValue().GetValue(), + ModId = m!.AsValue().GetValue(), + })).ToList()); + } + } +} diff --git a/Elwig/Helpers/ExportMode.cs b/Elwig/Helpers/ExportMode.cs index f949bb9..c49bf87 100644 --- a/Elwig/Helpers/ExportMode.cs +++ b/Elwig/Helpers/ExportMode.cs @@ -1,5 +1,5 @@ namespace Elwig.Helpers { public enum ExportMode { - Show, SaveList, SavePdf, Print, Email + Show, SaveList, SavePdf, Print, Email, Export, Upload } } diff --git a/Elwig/Helpers/Utils.cs b/Elwig/Helpers/Utils.cs index ebf2194..df69a17 100644 --- a/Elwig/Helpers/Utils.cs +++ b/Elwig/Helpers/Utils.cs @@ -81,7 +81,7 @@ namespace Elwig.Helpers { return PhoneNrTypes.Where(t => t.Key == type).Select(t => t.Value).FirstOrDefault(type); } - private static readonly string[] TempWildcards = ["*.html", "*.pdf", "*.exe"]; + private static readonly string[] TempWildcards = ["*.html", "*.pdf", "*.exe", "*.zip"]; private static readonly ushort[] Crc16ModbusTable = [ 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, @@ -409,21 +409,48 @@ namespace Elwig.Helpers { return InternetGetConnectedState(out var _, 0); } + public static HttpClient GetHttpClient(string? username = null, string? password = null, string? accept = null) { + var client = new HttpClient() { + Timeout = TimeSpan.FromSeconds(5), + }; + client.DefaultRequestHeaders.Accept.Clear(); + if (accept != null) + client.DefaultRequestHeaders.Accept.Add(new(accept)); + if (username != null || password != null) + client.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String( + Utils.UTF8.GetBytes($"{username}:{password}"))); + return client; + } + public static async Task<(string Version, string Url, long Size)?> GetLatestInstallerUrl(string url) { try { - using var client = new HttpClient() { - Timeout = TimeSpan.FromSeconds(5), - }; - client.DefaultRequestHeaders.Accept.Clear(); - client.DefaultRequestHeaders.Accept.Add(new("application/json")); - var res = JsonNode.Parse(await client.GetStringAsync(url)); - var data = res!["data"]![0]!; + using var client = GetHttpClient(accept: "application/json"); + var resJson = JsonNode.Parse(await client.GetStringAsync(url)); + var data = resJson!["data"]![0]!; return ((string)data["version"]!, (string)data["url"]!, (long)data["size"]!); } catch { return null; } } + public static async Task UploadExportData(string zip, string url, string username, string password) { + if (!url.EndsWith('/')) url += "/"; + using var client = GetHttpClient(username, password, accept: "application/json"); + var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read)); + content.Headers.ContentType = new("application/zip"); + using var res = await client.PutAsync(url + Path.GetFileName(zip), content); + res.EnsureSuccessStatusCode(); + } + + public static async Task GetExportMetaData(string url, string username, string password) { + using var client = GetHttpClient(username, password, accept: "application/json"); + using var res = await client.GetAsync(url); + res.EnsureSuccessStatusCode(); + var resJson = JsonNode.Parse(await res.Content.ReadAsStringAsync()); + var data = resJson!["data"]!; + return data.AsArray(); + } + public static void CleanupTempFiles() { var dir = new DirectoryInfo(App.TempPath); foreach (var file in TempWildcards.SelectMany(dir.EnumerateFiles)) { diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml b/Elwig/Windows/DeliveryAdminWindow.xaml index 775aa8b..375a6e0 100644 --- a/Elwig/Windows/DeliveryAdminWindow.xaml +++ b/Elwig/Windows/DeliveryAdminWindow.xaml @@ -110,6 +110,17 @@ + + + + + + + diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml.cs b/Elwig/Windows/DeliveryAdminWindow.xaml.cs index 4debf93..e05c28a 100644 --- a/Elwig/Windows/DeliveryAdminWindow.xaml.cs +++ b/Elwig/Windows/DeliveryAdminWindow.xaml.cs @@ -11,6 +11,7 @@ using Microsoft.Win32; using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; @@ -37,6 +38,7 @@ namespace Elwig.Windows { private readonly RoutedCommand CtrlO = new("CtrlO", typeof(DeliveryAdminWindow), [new KeyGesture(Key.O, ModifierKeys.Control)]); private readonly RoutedCommand CtrlJ = new("CtrlJ", typeof(DeliveryAdminWindow), [new KeyGesture(Key.J, ModifierKeys.Control)]); private readonly RoutedCommand CtrlQ = new("CtrlQ", typeof(DeliveryAdminWindow), [new KeyGesture(Key.Q, ModifierKeys.Control)]); + private readonly RoutedCommand CtrlH = new("CtrlH", typeof(DeliveryAdminWindow), [new KeyGesture(Key.H, ModifierKeys.Control)]); private readonly RoutedCommand CtrlShiftP = new("CtrlShiftP", typeof(DeliveryAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control | ModifierKeys.Shift)]); private readonly RoutedCommand CtrlShiftO = new("CtrlShiftO", typeof(DeliveryAdminWindow), [new KeyGesture(Key.O, ModifierKeys.Control | ModifierKeys.Shift)]); @@ -54,6 +56,7 @@ namespace Elwig.Windows { CommandBindings.Add(new CommandBinding(CtrlO, Menu_DeliveryJournal_ShowFilters_Click)); CommandBindings.Add(new CommandBinding(CtrlJ, Menu_DeliveryJournal_PrintToday_Click)); CommandBindings.Add(new CommandBinding(CtrlQ, Menu_WineQualityStatistics_PrintToday_Click)); + CommandBindings.Add(new CommandBinding(CtrlH, Menu_Export_UploadToday_Click)); CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_DeliveryNote_Print_Click)); CommandBindings.Add(new CommandBinding(CtrlShiftO, Menu_DeliveryJournal_PrintFilters_Click)); RequiredInputs = [ @@ -121,6 +124,9 @@ namespace Elwig.Windows { case 3: Menu_WineQualityStatistics_ModeKmw5.IsChecked = true; break; case 4: Menu_WineQualityStatistics_ModeKmw10.IsChecked = true; break; } + + Menu_Export_UploadFilters.IsEnabled = App.Config.SyncUrl != null; + Menu_Export_UploadToday.IsEnabled = App.Config.SyncUrl != null; } public DeliveryAdminWindow(int mgnr) : this() { @@ -222,6 +228,15 @@ namespace Elwig.Windows { await GenerateDeliveryJournal(1, ExportMode.Show); } + private async void Menu_Export_ExportToday_Click(object sender, RoutedEventArgs evt) { + await GenerateDeliveryJournal(1, ExportMode.Export); + } + + private async void Menu_Export_UploadToday_Click(object sender, RoutedEventArgs evt) { + if (App.Config.SyncUrl == null) return; + await GenerateDeliveryJournal(1, ExportMode.Upload); + } + private async void Menu_DeliveryJournal_PrintToday_Click(object sender, RoutedEventArgs evt) { await GenerateDeliveryJournal(1, ExportMode.Print); } @@ -242,6 +257,15 @@ namespace Elwig.Windows { await GenerateDeliveryJournal(0, ExportMode.Print); } + private async void Menu_Export_ExportFilters_Click(object sender, RoutedEventArgs evt) { + await GenerateDeliveryJournal(0, ExportMode.Export); + } + + private async void Menu_Export_UploadFilters_Click(object sender, RoutedEventArgs evt) { + if (App.Config.SyncUrl == null) return; + await GenerateDeliveryJournal(0, ExportMode.Upload); + } + private async Task GenerateDeliveryJournal(int modeWho, ExportMode exportMode) { using var ctx = new AppDbContext(); IQueryable query; @@ -256,6 +280,10 @@ namespace Elwig.Windows { .Where(p => p.Delivery.DateString == date); filterNames.Add($"{Utils.Today:dd.MM.yyyy}"); } + if (exportMode == ExportMode.Upload && !filterNames.Contains($"Zweigstelle {App.BranchName}")) { + query = query.Where(p => p.Delivery.ZwstId == App.ZwstId); + filterNames.Add($"Zweigstelle {App.BranchName}"); + } query = query .OrderBy(p => p.Delivery.DateString) @@ -281,6 +309,41 @@ namespace Elwig.Windows { } Mouse.OverrideCursor = null; } + } else if (exportMode == ExportMode.Export) { + var d = new SaveFileDialog() { + FileName = $"Lieferungen.zip", + DefaultExt = "zip", + Filter = "ZIP-Datei (*.zip)|*.zip", + Title = $"{DeliveryJournal.Name} speichern unter - Elwig" + }; + if (d.ShowDialog() == true) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + await ElwigData.ExportDeliveries(d.FileName, await query.Select(p => p.Delivery).Distinct().ToListAsync(), filterNames); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + } else if (exportMode == ExportMode.Upload && App.Config.SyncUrl != null) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.zip"; + var path = Path.Combine(App.TempPath, filename); + var list = await query.Select(p => p.Delivery).Distinct().ToListAsync(); + if (list.Count == 0) { + MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Fehler", + MessageBoxButton.OK, MessageBoxImage.Error); + } else { + await ElwigData.ExportDeliveries(path, list, filterNames); + await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); + MessageBox.Show($"Lieferungen erfolgreich hochgeladen!", "Lieferungen hochgeladen", + MessageBoxButton.OK, MessageBoxImage.Information); + } + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; } else { Mouse.OverrideCursor = Cursors.AppStarting; try { diff --git a/Elwig/Windows/MainWindow.xaml b/Elwig/Windows/MainWindow.xaml index 2e8ebee..5a61d74 100644 --- a/Elwig/Windows/MainWindow.xaml +++ b/Elwig/Windows/MainWindow.xaml @@ -19,6 +19,10 @@ + + + + diff --git a/Elwig/Windows/MainWindow.xaml.cs b/Elwig/Windows/MainWindow.xaml.cs index 2a30b8f..9cbc865 100644 --- a/Elwig/Windows/MainWindow.xaml.cs +++ b/Elwig/Windows/MainWindow.xaml.cs @@ -4,9 +4,12 @@ using Elwig.Helpers.Export; using Elwig.Models.Dtos; using Microsoft.Win32; using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows; @@ -20,8 +23,9 @@ namespace Elwig.Windows { var v = Assembly.GetExecutingAssembly().GetName().Version; VersionField.Text = "Version: " + (v == null ? "?" : $"{v.Major}.{v.Minor}.{v.Build}") + $" – {App.BranchName}"; if (App.Client.Client == null) VersionField.Text += " (Unbekannt)"; - if (App.Config.UpdateUrl == null) Menu_Help_Update.IsEnabled = false; - if (App.Config.Smtp == null) Menu_Help_Smtp.IsEnabled = false; + Menu_Help_Update.IsEnabled = App.Config.UpdateUrl != null; + Menu_Help_Smtp.IsEnabled = App.Config.Smtp != null; + Menu_Database_Sync.IsEnabled = App.Config.SyncUrl != null; } private void Window_Loaded(object sender, RoutedEventArgs evt) { @@ -70,6 +74,55 @@ namespace Elwig.Windows { Process.Start("explorer.exe", path); } + private void Menu_Database_Export_Click(object sender, RoutedEventArgs evt) { + // TODO Menu_Database_Export_Click + } + + private void Menu_Database_Import_Click(object sender, RoutedEventArgs evt) { + // TODO Menu_Database_Import_Click + } + + private async void Menu_Database_Sync_Click(object sender, RoutedEventArgs evt) { + if (App.Config.SyncUrl == null) + return; + Mouse.OverrideCursor = Cursors.AppStarting; + try { + var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); + var files = data + .Select(f => new { + Name = f!["name"]!.AsValue().GetValue(), + Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null, + ZwstId = f!["zwstid"]?.AsValue().GetValue(), + DeliveryNum = f!["meta"]?["deliveries"]?["count"]!.AsValue().GetValue(), + DeliveryPartNum = f!["meta"]?["deliveries"]?["parts"]!.AsValue().GetValue(), + Filters = f!["meta"]?["deliveries"]?["filters"]!.AsArray().Select(i => i!.AsValue().GetValue()).ToArray(), + Device = f!["meta"]?["device"]!.AsValue().GetValue(), + Url = f!["url"]!.AsValue().GetValue(), + Size = f!["size"]!.AsValue().GetValue(), + }) + .Where(f => f.DeliveryNum > 0 && f.Timestamp >= new DateTime(Utils.CurrentLastSeason, 7, 1)) + .ToList(); + + var imported = await ElwigData.GetImportedFiles(); + var import = files + .Where(f => f.Filters != null && f.Filters.Length > 0 && f.Device != Environment.MachineName && !imported.Contains(f.Name)) + .ToList(); + var paths = new List(); + using (var client = Utils.GetHttpClient(App.Config.SyncUsername, App.Config.SyncPassword)) { + foreach (var f in import) { + var filename = Path.Combine(App.TempPath, f.Name); + using var stream = new FileStream(filename, FileMode.Create); + await client.DownloadAsync(f.Url, stream); + paths.Add(filename); + } + } + await ElwigData.Import(paths, false); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + private void MemberAdminButton_Click(object sender, RoutedEventArgs evt) { var w = new MemberAdminWindow(); w.Show(); diff --git a/Installer/Files/config.ini b/Installer/Files/config.ini index 8215a6e..f126141 100644 --- a/Installer/Files/config.ini +++ b/Installer/Files/config.ini @@ -14,6 +14,11 @@ file = database.sqlite3 url = https://www.necronda.net/elwig/files/elwig/latest auto = true +[sync] +;url = https://www.necronda.net/elwig/clients/WGX/ +;username = "" +;password = "" + [smtp] ;host = ;port =