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>
211 lines
9.0 KiB
Markdown
211 lines
9.0 KiB
Markdown
# Project Context: Engineering-to-Simulation Sync Tool ("EngineeringSync")
|
||
|
||
You are an expert C# / .NET 10 developer. Build a middleware tool that bridges mechanical engineering (CAD) and simulation (Process Simulate).
|
||
|
||
**Problem:** Engineers save files to a shared folder. Immediate overwrites in the simulation folder cause data corruption for the simulator. This tool watches the engineering folder, records changes, and notifies the simulation engineer via a WPF System Tray App. The simulation engineer reviews changes, selects what to sync, and triggers a controlled copy.
|
||
|
||
---
|
||
|
||
## Tech Stack
|
||
|
||
- **Framework:** .NET 10
|
||
- **Service:** Worker Service (Windows Service) + ASP.NET Core Minimal API + SignalR
|
||
- **Client:** WPF (.NET 10-Windows) with `H.NotifyIcon.Wpf`
|
||
- **Database:** SQLite via EF Core 10 (WAL mode for concurrency)
|
||
- **File Watching:** `System.IO.FileSystemWatcher` + `System.Threading.Channels` (debouncing)
|
||
- **UI Pattern:** MVVM with `CommunityToolkit.Mvvm`
|
||
|
||
---
|
||
|
||
## Phase 1: Solution Initialization
|
||
|
||
Create solution structure via .NET CLI:
|
||
|
||
```
|
||
EngineeringSync.sln
|
||
├── EngineeringSync.Domain (Class Library, net10.0) – Entities, Enums, Interfaces
|
||
├── EngineeringSync.Infrastructure (Class Library, net10.0) – EF Core, AppDbContext, Migrations
|
||
├── EngineeringSync.Service (Worker Service, net10.0) – Kestrel :5050, SignalR, Watcher
|
||
└── EngineeringSync.TrayApp (WPF App, net10.0-windows) – System tray + all UI windows
|
||
```
|
||
|
||
**Dependency direction (strict one-way):**
|
||
- `Infrastructure` → `Domain`
|
||
- `Service` → `Domain` + `Infrastructure`
|
||
- `TrayApp` → `Domain` (for shared DTOs/Enums only)
|
||
|
||
**NuGet Packages:**
|
||
|
||
| Project | Packages |
|
||
|---|---|
|
||
| Domain | *(none – pure POCOs)* |
|
||
| Infrastructure | `Microsoft.EntityFrameworkCore.Sqlite`, `Microsoft.EntityFrameworkCore.Design` |
|
||
| Service | `Microsoft.Extensions.Hosting.WindowsServices`, `Microsoft.AspNetCore.SignalR` |
|
||
| TrayApp | `H.NotifyIcon.Wpf`, `Microsoft.AspNetCore.SignalR.Client`, `CommunityToolkit.Mvvm` |
|
||
|
||
---
|
||
|
||
## Phase 2: Domain & Database Modeling
|
||
|
||
### Domain Entities (`EngineeringSync.Domain`)
|
||
|
||
**`ProjectConfig`** – A watched project (persisted in DB, managed via UI):
|
||
- `Id` (Guid, PK)
|
||
- `Name` (string) – Display name, e.g. "Hauptprojekt Karosserie"
|
||
- `EngineeringPath` (string) – Source folder the watcher monitors
|
||
- `SimulationPath` (string) – Target folder for controlled sync
|
||
- `FileExtensions` (string) – Comma-separated, e.g. ".jt,.cojt,.xml"
|
||
- `IsActive` (bool) – Enable/disable watching without deleting
|
||
- `CreatedAt` (DateTime)
|
||
|
||
**`FileRevision`** – Tracks known file state for change detection:
|
||
- `Id` (Guid, PK)
|
||
- `ProjectId` (Guid, FK → ProjectConfig)
|
||
- `RelativePath` (string)
|
||
- `FileHash` (string) – SHA-256
|
||
- `Size` (long)
|
||
- `LastModified` (DateTime)
|
||
|
||
**`PendingChange`** – A detected change awaiting user action:
|
||
- `Id` (Guid, PK)
|
||
- `ProjectId` (Guid, FK → ProjectConfig)
|
||
- `RelativePath` (string)
|
||
- `ChangeType` (Enum: Created, Modified, Renamed, Deleted)
|
||
- `OldRelativePath` (string?) – Only for Renamed
|
||
- `Status` (Enum: Pending, Synced, Ignored)
|
||
- `CreatedAt` (DateTime)
|
||
- `SyncedAt` (DateTime?)
|
||
|
||
### Infrastructure (`EngineeringSync.Infrastructure`)
|
||
|
||
- `AppDbContext` with `DbSet<ProjectConfig>`, `DbSet<FileRevision>`, `DbSet<PendingChange>`
|
||
- SQLite connection string with `Mode=ReadWriteCreate` and WAL pragma
|
||
- Auto-migration at service startup via `context.Database.Migrate()`
|
||
- Unique index on `(ProjectId, RelativePath)` for `FileRevision`
|
||
|
||
---
|
||
|
||
## Phase 3: The Windows Service (Core Logic)
|
||
|
||
### 3.1 Project Management API (CRUD)
|
||
|
||
New endpoints for managing projects from the TrayApp UI:
|
||
|
||
- `GET /api/projects` – List all ProjectConfig
|
||
- `POST /api/projects` – Create new ProjectConfig (validates paths exist)
|
||
- `PUT /api/projects/{id}` – Update ProjectConfig (restarts watcher for that project)
|
||
- `DELETE /api/projects/{id}` – Delete ProjectConfig + associated changes
|
||
|
||
When a ProjectConfig is created or updated with `IsActive = true`, the WatcherService must dynamically start/stop the corresponding `FileSystemWatcher`.
|
||
|
||
### 3.2 FileSystemWatcher & Debouncing (`WatcherService`)
|
||
|
||
- `BackgroundService` that manages one `FileSystemWatcher` per active `ProjectConfig`
|
||
- Listen to: `Created`, `Changed`, `Renamed`, `Deleted`
|
||
- Filter by `ProjectConfig.FileExtensions`
|
||
- **Debouncing pipeline:**
|
||
1. Push raw `FileSystemEventArgs` into a `Channel<FileEvent>` (unbounded)
|
||
2. Consumer reads from channel, groups events by `(ProjectId, RelativePath)` within a 2000ms sliding window
|
||
3. After window closes, compute SHA-256 hash of file
|
||
4. Compare hash against latest `FileRevision` for that path
|
||
5. If hash differs (or file is new): write `FileRevision` + `PendingChange` to DB
|
||
6. Broadcast via SignalR
|
||
- **Renamed:** Record old + new path, update `FileRevision.RelativePath`
|
||
- **Deleted:** Record as `ChangeType.Deleted`, remove `FileRevision`
|
||
|
||
### 3.3 Minimal API & SignalR Hub
|
||
|
||
Host Kestrel on `http://localhost:5050`:
|
||
|
||
| Method | Endpoint | Description |
|
||
|---|---|---|
|
||
| GET | `/api/projects` | List all projects |
|
||
| POST | `/api/projects` | Create project |
|
||
| PUT | `/api/projects/{id}` | Update project |
|
||
| DELETE | `/api/projects/{id}` | Delete project |
|
||
| GET | `/api/changes/{projectId}` | Pending changes for project |
|
||
| GET | `/api/changes/{projectId}/history` | Synced/ignored changes (last 100) |
|
||
| POST | `/api/sync` | Sync selected PendingChange IDs |
|
||
| POST | `/api/ignore` | Ignore selected PendingChange IDs |
|
||
|
||
**SignalR Hub** (`NotificationHub` at `/notifications`):
|
||
- `ReceiveChangeNotification(projectId, projectName, count)` – Fired when new PendingChanges are persisted
|
||
- `ProjectConfigChanged()` – Fired when a project is created/updated/deleted
|
||
|
||
### 3.4 Sync Logic (`SyncManager`)
|
||
|
||
When `POST /api/sync` is called with a list of PendingChange IDs:
|
||
|
||
1. Load PendingChange + ProjectConfig from DB
|
||
2. Verify source file exists at `EngineeringPath / RelativePath`
|
||
3. If target already exists in `SimulationPath`:
|
||
- Rename to `{filename}_{timestamp:yyyyMMdd_HHmmss}.bak` (timestamped to prevent overwriting previous backups)
|
||
4. Copy source → target (create subdirectories as needed)
|
||
5. Mark PendingChange as `Status = Synced`, set `SyncedAt`
|
||
6. For `ChangeType.Deleted`: Delete target file (after backup), mark Synced
|
||
7. Return result summary (success count, errors)
|
||
|
||
---
|
||
|
||
## Phase 4: The WPF Tray App
|
||
|
||
### 4.1 System Tray Setup
|
||
|
||
- `H.NotifyIcon.Wpf`: headless startup (no main window)
|
||
- Tray icon context menu:
|
||
- **"Änderungen anzeigen"** → Opens PendingChangesWindow
|
||
- **"Projekte verwalten"** → Opens ProjectManagementWindow
|
||
- **"Beenden"** → Graceful shutdown
|
||
|
||
### 4.2 SignalR Client
|
||
|
||
- Connect to `http://localhost:5050/notifications` on startup
|
||
- Auto-reconnect with exponential backoff
|
||
- On `ReceiveChangeNotification`: Show balloon tip "Neue Engineering-Daten für Projekt [Name] verfügbar. Klicken zum Überprüfen."
|
||
- On `ProjectConfigChanged`: Refresh project list in any open window
|
||
|
||
### 4.3 Project Management Window (NEW)
|
||
|
||
A WPF Window (MVVM) for managing watched projects:
|
||
|
||
- **ListView** showing all ProjectConfig entries (Name, EngineeringPath, SimulationPath, Active-Status)
|
||
- **"Neues Projekt"** button → Opens inline form or dialog:
|
||
- Name (TextBox)
|
||
- Engineering-Pfad (TextBox + FolderBrowserDialog via Button)
|
||
- Simulations-Pfad (TextBox + FolderBrowserDialog via Button)
|
||
- Dateiendungen (TextBox, comma-separated, default: ".jt,.cojt,.xml")
|
||
- Aktiv (CheckBox)
|
||
- **"Bearbeiten"** / **"Löschen"** buttons per row
|
||
- All CRUD calls go to `POST/PUT/DELETE /api/projects`
|
||
- Path validation: Check that folders exist before saving
|
||
|
||
### 4.4 Pending Changes Window
|
||
|
||
- MVVM with `DataGrid`
|
||
- **Project selector** (ComboBox) at top → fetches from `GET /api/projects`
|
||
- Columns: Datei, Änderungstyp, Zeitstempel, Auswahl (CheckBox)
|
||
- **"Ausgewählte synchronisieren"** → `POST /api/sync`
|
||
- **"Ausgewählte ignorieren"** → `POST /api/ignore`
|
||
- **"Alle synchronisieren"** / **"Alle ignorieren"** convenience buttons
|
||
- Auto-refresh when SignalR notification arrives for the selected project
|
||
|
||
---
|
||
|
||
## Phase 5: Build Order & Execution
|
||
|
||
Build step-by-step, verify each phase compiles before proceeding:
|
||
|
||
1. **Phase 1** – Solution scaffolding via `dotnet` CLI. Verify `dotnet build` succeeds.
|
||
2. **Phase 2** – Domain entities + EF Core DbContext + initial migration. Verify migration applies.
|
||
3. **Phase 3.1** – Project CRUD API endpoints. Test with manual HTTP requests.
|
||
4. **Phase 3.2** – WatcherService with Channel debouncing. Test with file drops.
|
||
5. **Phase 3.3** – SignalR hub + remaining API endpoints.
|
||
6. **Phase 3.4** – SyncManager copy logic.
|
||
7. **Phase 4.1+4.2** – TrayApp shell with SignalR client.
|
||
8. **Phase 4.3** – Project Management Window (path configuration UI).
|
||
9. **Phase 4.4** – Pending Changes Window.
|
||
|
||
Use `CommunityToolkit.Mvvm` for `[ObservableProperty]`, `[RelayCommand]` source generators in all ViewModels.
|
||
|
||
Ensure all code compiles under .NET 10. Use clean architecture and dependency injection throughout.
|