Initial commit: EngineeringSync v1.0.0
Vollständige Implementierung des EngineeringSync-Middleware-Tools: - Windows Service (Kestrel :5050) mit FileSystemWatcher + SignalR - WPF Tray-App mit PendingChanges- und Projektverwaltungs-Fenster - Setup-Wizard (8-Schritte-Installer) - SQLite/EF Core Datenschicht (WAL-Modus) - SHA-256-basiertes Debouncing (2s Fenster) - Backup-System mit konfigurierbarer Aufbewahrung Bugfixes & Verbesserungen: - BUG-1: AppDbContext OnConfiguring invertierte Bedingung behoben - BUG-2: Event-Handler-Leak in TrayApp (Fenster-Singleton-Pattern) - BUG-3: ProjectConfigChanged SignalR-Signal in allen CRUD-Endpoints - BUG-5: Rename-Sync löscht alte Datei im Simulations-Ordner - BUG-6: Doppeltes Dispose von SignalR verhindert - BUG-7: Registry-Deinstallation nur EngineeringSync-Eintrag entfernt - S1: Path-Traversal-Schutz via SafeCombine() im SyncManager - E1: FSW Buffer 64KB + automatischer Re-Scan bei Overflow - E2: Retry-Logik (3x) für gesperrte Dateien mit exponentiellem Backoff - E4: Channel.Writer.TryComplete() beim Shutdown - C2: HubMethodNames-Konstanten statt Magic Strings - E3: Pagination in Changes-API (page/pageSize Query-Parameter) - A1: Fire-and-Forget mit try/catch + Logging Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
139
EngineeringSync.TrayApp/App.xaml.cs
Normal file
139
EngineeringSync.TrayApp/App.xaml.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using EngineeringSync.TrayApp.Services;
|
||||
using EngineeringSync.TrayApp.ViewModels;
|
||||
using EngineeringSync.TrayApp.Views;
|
||||
using H.NotifyIcon;
|
||||
|
||||
namespace EngineeringSync.TrayApp;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private TaskbarIcon? _trayIcon;
|
||||
private SignalRService? _signalR;
|
||||
private ApiClient? _apiClient;
|
||||
private PendingChangesWindow? _pendingChangesWindow;
|
||||
private PendingChangesViewModel? _pendingChangesViewModel;
|
||||
|
||||
protected override async void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
_apiClient = new ApiClient(new System.Net.Http.HttpClient());
|
||||
_signalR = new SignalRService();
|
||||
|
||||
// TaskbarIcon aus XAML-Resource laden (wird dadurch korrekt im Shell registriert)
|
||||
_trayIcon = (TaskbarIcon)FindResource("TrayIcon");
|
||||
_trayIcon.ContextMenu = BuildContextMenu();
|
||||
|
||||
// ForceCreate() aufrufen, um sicherzustellen dass das Icon im System Tray erscheint
|
||||
_trayIcon.ForceCreate(enablesEfficiencyMode: false);
|
||||
|
||||
_signalR.ChangeNotificationReceived += OnChangeNotificationReceived;
|
||||
_signalR.ChangeNotificationReceived += OnPendingChangesWindowNotification;
|
||||
|
||||
try { await _signalR.ConnectAsync(); }
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[TrayApp] SignalR Connect Fehler: {ex}"); }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Debug-Fehlerbehandlung für Icon-Initialisierung
|
||||
MessageBox.Show(
|
||||
$"Fehler bei der Initialisierung des Tray Icons:\n\n{ex.Message}\n\n{ex.StackTrace}",
|
||||
"EngineeringSync Startup Error",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private ContextMenu BuildContextMenu()
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
|
||||
var showChanges = new MenuItem { Header = "Änderungen anzeigen" };
|
||||
showChanges.Click += (_, _) => OpenChangesWindow();
|
||||
menu.Items.Add(showChanges);
|
||||
|
||||
var showProjects = new MenuItem { Header = "Projekte verwalten" };
|
||||
showProjects.Click += (_, _) => OpenProjectsWindow();
|
||||
menu.Items.Add(showProjects);
|
||||
|
||||
menu.Items.Add(new Separator());
|
||||
|
||||
var exit = new MenuItem { Header = "Beenden" };
|
||||
exit.Click += (_, _) => ExitApp();
|
||||
menu.Items.Add(exit);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void OpenChangesWindow()
|
||||
{
|
||||
// Fenster beim ersten Mal erstellen
|
||||
if (_pendingChangesWindow is null)
|
||||
{
|
||||
_pendingChangesViewModel = new PendingChangesViewModel(_apiClient!);
|
||||
_pendingChangesWindow = new PendingChangesWindow(_pendingChangesViewModel);
|
||||
}
|
||||
|
||||
// Fenster anzeigen/aktivieren
|
||||
_pendingChangesWindow.Show();
|
||||
_pendingChangesWindow.Activate();
|
||||
_ = _pendingChangesViewModel!.LoadProjectsCommand.ExecuteAsync(null);
|
||||
}
|
||||
|
||||
private void OpenProjectsWindow()
|
||||
{
|
||||
var existing = Windows.OfType<ProjectManagementWindow>().FirstOrDefault();
|
||||
if (existing is not null) { existing.Activate(); return; }
|
||||
|
||||
var vm = new ProjectManagementViewModel(_apiClient!);
|
||||
var window = new ProjectManagementWindow(vm);
|
||||
window.Show();
|
||||
_ = vm.LoadCommand.ExecuteAsync(null);
|
||||
}
|
||||
|
||||
private void OnChangeNotificationReceived(Guid projectId, string projectName, int count)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
_trayIcon?.ShowNotification(
|
||||
title: "Neue Engineering-Daten",
|
||||
message: $"Projekt \"{projectName}\": {count} ausstehende Änderung(en).");
|
||||
});
|
||||
}
|
||||
|
||||
private void OnPendingChangesWindowNotification(Guid projectId, string projectName, int count)
|
||||
{
|
||||
// Handler für PendingChangesWindow: nur aktualisieren wenn Fenster sichtbar ist
|
||||
if (_pendingChangesWindow?.IsVisible == true && _pendingChangesViewModel is not null)
|
||||
{
|
||||
Dispatcher.Invoke(() => _ = _pendingChangesViewModel.LoadChangesCommand.ExecuteAsync(null));
|
||||
}
|
||||
}
|
||||
|
||||
private async void ExitApp()
|
||||
{
|
||||
if (_signalR is not null)
|
||||
{
|
||||
await _signalR.DisposeAsync();
|
||||
_signalR = null;
|
||||
}
|
||||
_trayIcon?.Dispose();
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
protected override async void OnExit(ExitEventArgs e)
|
||||
{
|
||||
if (_signalR is not null)
|
||||
{
|
||||
await _signalR.DisposeAsync();
|
||||
_signalR = null;
|
||||
}
|
||||
_trayIcon?.Dispose();
|
||||
base.OnExit(e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user