diff --git a/CHANGELOG.md b/CHANGELOG.md index b5e96fe9..e4620b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,13 @@ Versionsschema ist `x.x.x.x` (siehe `release.sh`). ### Geaendert +- **Ein-Richtungs-Modi sind jetzt echtes "Ersetzen".** Outlook->Starface macht + das Starface-Adressbuch zu einer exakten Kopie von Outlook: Kontakte, die nur + in Starface existieren (kein Pendant in Outlook), werden geloescht. + Starface->Outlook entsprechend umgekehrt. Schutz: Ist die Quelle komplett + leer (z.B. falscher Ordner gewaehlt), wird die Loeschphase uebersprungen + statt die Zielseite zu leeren. Mass-Loeschungen finden nur bei vollstaendig + geladener Liste statt (unvollstaendige Ladevorgaenge brechen vorher ab). - **Bidirektionale Loeschungen** werden jetzt erkannt. Wird ein Kontakt auf einer Seite geloescht und ist die andere Seite seit dem letzten Sync unveraendert (Abgleich ueber die gespeicherte Baseline), wird die Loeschung diff --git a/README.md b/README.md index 92be273d..a7e8f6e8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,10 @@ Windows-Anwendung zur bidirektionalen Synchronisation von Kontakten zwischen Mic - **Profil-System** zum Verwalten mehrerer Sync-Konfigurationen (verschiedene Adressbuecher oder Anlagen) - **Starface-Adressbuecher**: Zentrales Adressbuch, persoenliches Adressbuch und Tag-basierte Adressbuecher - **Outlook-Kontaktordner**: Frei waehlbarer Kontaktordner als Sync-Ziel -- **Sync-Richtung** konfigurierbar: Outlook -> Starface, Starface -> Outlook oder bidirektional +- **Sync-Richtung** konfigurierbar: + - *Bidirektional*: Aenderungen werden in beide Richtungen abgeglichen (inkl. Loeschungen) + - *Outlook -> Starface*: Das Starface-Adressbuch wird zur exakten Kopie von Outlook (nur in Starface vorhandene Kontakte werden geloescht) + - *Starface -> Outlook*: Der Outlook-Ordner wird zur exakten Kopie von Starface (nur in Outlook vorhandene Kontakte werden geloescht) - **Intelligentes Matching**: Kontakte werden anhand von E-Mail-Adresse oder Name abgeglichen - **Aenderungserkennung**: Nur geaenderte Kontakte werden uebertragen (Hash-basiert) - **Auto-Sync**: Optionaler automatischer Sync in konfigurierbarem Intervall diff --git a/src/StarfaceOutlookSync/Services/SyncEngine.cs b/src/StarfaceOutlookSync/Services/SyncEngine.cs index 1bd7682b..7f51b21b 100644 --- a/src/StarfaceOutlookSync/Services/SyncEngine.cs +++ b/src/StarfaceOutlookSync/Services/SyncEngine.cs @@ -494,6 +494,77 @@ namespace StarfaceOutlookSync.Services } } + // ============================================ + // Phase 4: Ersetzen-Modus - Zielseite an Quelle angleichen + // ============================================ + // In den Ein-Richtungs-Modi soll die Zielseite eine exakte Kopie + // der Quelle werden. Kontakte, die nur auf der Zielseite existieren + // (kein Mapping, kein Treffer in der Quelle), werden geloescht. + // Sicher, weil unvollstaendige Ladevorgaenge vorher abbrechen. + if (profile.SyncDirection == SyncDirection.OutlookToStarface) + { + // Schutz gegen versehentliches Leerraeumen (z.B. falscher Ordner). + if (outlookContacts.Count == 0) + { + Log("Ersetzen-Modus uebersprungen: Outlook-Ordner ist leer (Schutz vor versehentlichem Leeren)."); + } + else + { + var leftover = starfaceContacts + .Where(c => !string.IsNullOrEmpty(c.StarfaceId) && !processedStarfaceIds.Contains(c.StarfaceId)) + .ToList(); + if (leftover.Count > 0) + Log($"Ersetzen-Modus: entferne {leftover.Count} Kontakt(e) aus Starface, die nicht in Outlook existieren"); + foreach (var sc in leftover) + { + try + { + if (await starface.DeleteContactAsync(sc.StarfaceId)) + { + result.Updated++; + Log($" Geloescht (nur in Starface): {sc.DisplayName}"); + } + } + catch (Exception ex) + { + result.Errors++; + result.ErrorMessages.Add($"Loeschen SF {sc.DisplayName}: {ex.Message}"); + } + } + } + } + else if (profile.SyncDirection == SyncDirection.StarfaceToOutlook) + { + if (starfaceContacts.Count == 0) + { + Log("Ersetzen-Modus uebersprungen: Starface-Adressbuch ist leer (Schutz vor versehentlichem Leeren)."); + } + else + { + var leftover = outlookContacts + .Where(c => !string.IsNullOrEmpty(c.OutlookEntryId) && !processedOutlookIds.Contains(c.OutlookEntryId)) + .ToList(); + if (leftover.Count > 0) + Log($"Ersetzen-Modus: entferne {leftover.Count} Kontakt(e) aus Outlook, die nicht in Starface existieren"); + foreach (var oc in leftover) + { + try + { + if (_outlookService.DeleteContact(oc.OutlookEntryId)) + { + result.Updated++; + Log($" Geloescht (nur in Outlook): {oc.DisplayName}"); + } + } + catch (Exception ex) + { + result.Errors++; + result.ErrorMessages.Add($"Loeschen OL {oc.DisplayName}: {ex.Message}"); + } + } + } + } + // Mappings speichern _profileManager.SaveMappings(profile.Id, newMappings); _profileManager.UpdateLastSync(profile.Id); diff --git a/src/StarfaceOutlookSync/UI/ProfileEditorForm.cs b/src/StarfaceOutlookSync/UI/ProfileEditorForm.cs index 07c89136..0a4832ac 100644 --- a/src/StarfaceOutlookSync/UI/ProfileEditorForm.cs +++ b/src/StarfaceOutlookSync/UI/ProfileEditorForm.cs @@ -22,6 +22,7 @@ namespace StarfaceOutlookSync.UI private NumericUpDown _numAutoSync; private Button _btnTest, _btnLoadBooks, _btnSave, _btnCancel; private Label _lblTestResult; + private Label _lblDirectionHint; private List _addressBooks = new List(); private List _outlookFolderPaths = new List(); @@ -101,7 +102,18 @@ namespace StarfaceOutlookSync.UI _cmbDirection = new ComboBox { Left = 12, Top = y, Width = 250, DropDownStyle = ComboBoxStyle.DropDownList }; _cmbDirection.Items.AddRange(new object[] { "Bidirektional", "Outlook -> Starface", "Starface -> Outlook" }); _cmbDirection.SelectedIndex = 0; - panel.Controls.Add(_cmbDirection); y += 32; + panel.Controls.Add(_cmbDirection); y += 28; + + _lblDirectionHint = new Label + { + Left = 12, Top = y, Width = 360, Height = 32, + ForeColor = Color.FromArgb(150, 80, 0), + Font = new Font("Segoe UI", 8.25f) + }; + _cmbDirection.SelectedIndexChanged += (s, e) => UpdateDirectionHint(); + panel.Controls.Add(_lblDirectionHint); + UpdateDirectionHint(); + y += 36; panel.Controls.Add(MakeLabel("Auto-Sync Intervall (Minuten, 0 = manuell):", 12, y)); y += 22; _numAutoSync = new NumericUpDown { Left = 12, Top = y, Width = 80, Minimum = 0, Maximum = 1440, Value = 0 }; @@ -122,6 +134,22 @@ namespace StarfaceOutlookSync.UI CancelButton = _btnCancel; } + private void UpdateDirectionHint() + { + switch (_cmbDirection.SelectedIndex) + { + case 1: // Outlook -> Starface + _lblDirectionHint.Text = "Achtung: Das Starface-Adressbuch wird zur exakten Kopie von Outlook.\nKontakte, die nur in Starface existieren, werden geloescht."; + break; + case 2: // Starface -> Outlook + _lblDirectionHint.Text = "Achtung: Der Outlook-Ordner wird zur exakten Kopie von Starface.\nKontakte, die nur in Outlook existieren, werden geloescht."; + break; + default: // Bidirektional + _lblDirectionHint.Text = "Aenderungen werden in beide Richtungen abgeglichen."; + break; + } + } + private Label MakeLabel(string text, int x, int y) { return new Label { Text = text, Left = x, Top = y, AutoSize = true };