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>
140 lines
4.6 KiB
C#
140 lines
4.6 KiB
C#
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);
|
|
}
|
|
}
|