Neuer Button "Dubletten" im Hauptfenster fuehrt doppelte Kontakte fuer das ausgewaehlte Profil zusammen - je Seite (Telefonanlage + Outlook) getrennt. - Matching in ContactMatcher extrahiert (Sync UND Dedupe nutzen exakt dieselbe Erkennung). - ContactMerger.FillEmptyInto / FilledFieldCount: leere Felder des Gewinners auffuellen (kein Datenverlust), Gewinner-Wahl nach Zuordnung/Vollstaendigkeit. - DedupeService: Analyse (Vorschau) + Ausfuehrung (Merge, Loeschen der Dubletten, Bereinigen verwaister Zuordnungen). - SyncCoordinator.RunExclusiveAsync: Dedupe laeuft unter demselben Guard + Lock-Datei wie ein Sync (nie gleichzeitig; kein Konflikt zwischen Clients). - DedupeForm: Vorschau mit optionaler Detailliste, Bestaetigung, Live-Log; danach Hinweis, einmal zu synchronisieren. - MainForm: Button "Dubletten" (Profil-Auswahl noetig), Fenster verbreitert. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
590 lines
33 KiB
C#
590 lines
33 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using StarfaceOutlookSync.Models;
|
|
|
|
namespace StarfaceOutlookSync.Services
|
|
{
|
|
public class SyncEngine
|
|
{
|
|
private readonly ProfileManager _profileManager = new ProfileManager();
|
|
private readonly OutlookContactsService _outlookService = new OutlookContactsService();
|
|
|
|
public event Action<string> OnProgress;
|
|
|
|
private void Log(string message) => OnProgress?.Invoke(message);
|
|
|
|
/// <summary>Protokolliert eine tatsaechliche Aenderung (fuer Live-Log UND SyncResult.Changes).</summary>
|
|
private void Action(SyncResult result, string message)
|
|
{
|
|
Log(" " + message);
|
|
result?.Changes.Add(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setzt die Baseline eines Mappings auf den uebergebenen Stand beider
|
|
/// Seiten (Snapshot + Hash). Der Snapshot wird fuer das Feld-Merge bei
|
|
/// kuenftigen Konflikten gebraucht.
|
|
/// </summary>
|
|
private static void SetBaseline(SyncMapping m, UnifiedContact outlook, UnifiedContact starface)
|
|
{
|
|
m.LastOutlook = outlook;
|
|
m.LastStarface = starface;
|
|
m.LastOutlookHash = outlook?.GetHash() ?? "";
|
|
m.LastStarfaceHash = starface?.GetHash() ?? "";
|
|
m.LastSyncHash = "";
|
|
}
|
|
|
|
public async Task<SyncResult> SyncProfileAsync(SyncProfile profile)
|
|
{
|
|
var result = new SyncResult
|
|
{
|
|
ProfileName = profile.Name,
|
|
Timestamp = DateTime.Now.ToString("o")
|
|
};
|
|
|
|
try
|
|
{
|
|
Log("Verbinde mit Starface...");
|
|
using (var starface = ContactBackendFactory.Create(profile.Connection))
|
|
{
|
|
starface.OnDebug += (msg) => Log(msg);
|
|
var loginOk = await starface.LoginAsync();
|
|
if (!loginOk)
|
|
{
|
|
result.ErrorMessages.Add("Starface-Login fehlgeschlagen");
|
|
result.Errors++;
|
|
return result;
|
|
}
|
|
|
|
// Kontakte laden
|
|
Log("Lade Outlook-Kontakte...");
|
|
var outlookContacts = _outlookService.GetContacts(profile.OutlookFolderPath);
|
|
Log($"{outlookContacts.Count} Outlook-Kontakte geladen");
|
|
|
|
Log("Lade Starface-Kontakte...");
|
|
var starfaceContacts = await starface.GetContactsAsync(profile.AddressBook);
|
|
Log($"{starfaceContacts.Count} Starface-Kontakte geladen");
|
|
|
|
// Bestehende Mappings laden
|
|
var mappings = _profileManager.GetMappings(profile.Id);
|
|
|
|
// Sets fuer schnellen Lookup
|
|
var mappingByOutlook = new Dictionary<string, SyncMapping>();
|
|
var mappingByStarface = new Dictionary<string, SyncMapping>();
|
|
foreach (var m in mappings)
|
|
{
|
|
if (!string.IsNullOrEmpty(m.OutlookEntryId))
|
|
mappingByOutlook[m.OutlookEntryId] = m;
|
|
if (!string.IsNullOrEmpty(m.StarfaceId))
|
|
mappingByStarface[m.StarfaceId] = m;
|
|
}
|
|
|
|
// Tracking: welche Kontakte wurden bereits verarbeitet
|
|
var processedStarfaceIds = new HashSet<string>();
|
|
var processedOutlookIds = new HashSet<string>();
|
|
var newMappings = new List<SyncMapping>();
|
|
|
|
// ============================================
|
|
// Phase 1: Bestehende Mappings abgleichen
|
|
// ============================================
|
|
Log("Gleiche bestehende Zuordnungen ab...");
|
|
foreach (var mapping in mappings.ToList())
|
|
{
|
|
var oc = outlookContacts.FirstOrDefault(c => c.OutlookEntryId == mapping.OutlookEntryId);
|
|
var sc = starfaceContacts.FirstOrDefault(c => c.StarfaceId == mapping.StarfaceId);
|
|
|
|
if (oc != null) processedOutlookIds.Add(oc.OutlookEntryId);
|
|
if (sc != null) processedStarfaceIds.Add(sc.StarfaceId);
|
|
|
|
if (oc == null && sc == null)
|
|
{
|
|
// Beide Seiten geloescht -> Mapping entfernen
|
|
Log($" Mapping verwaist (beide geloescht), entferne");
|
|
continue;
|
|
}
|
|
|
|
if (oc == null && sc != null)
|
|
{
|
|
// Outlook-Kontakt nicht gefunden.
|
|
// Erst pruefen ob er vielleicht nur eine neue EntryID hat
|
|
var reMatch = ContactMatcher.FindMatch(sc, outlookContacts.Where(c =>
|
|
!processedOutlookIds.Contains(c.OutlookEntryId)).ToList());
|
|
if (reMatch != null)
|
|
{
|
|
// Kontakt existiert noch in Outlook, nur EntryID geaendert
|
|
Log($" EntryID geaendert, verknuepfe neu: {sc.DisplayName}");
|
|
mapping.OutlookEntryId = reMatch.OutlookEntryId;
|
|
processedOutlookIds.Add(reMatch.OutlookEntryId);
|
|
newMappings.Add(mapping);
|
|
continue;
|
|
}
|
|
|
|
// Wirklich in Outlook geloescht.
|
|
if (profile.SyncDirection == SyncDirection.OutlookToStarface)
|
|
{
|
|
// Outlook ist fuehrend -> Loeschung nach Starface spiegeln.
|
|
if (await starface.DeleteContactAsync(mapping.StarfaceId))
|
|
{
|
|
result.Updated++;
|
|
Action(result, $"Geloescht (OL->SF): {sc.DisplayName}");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (profile.SyncDirection == SyncDirection.Both)
|
|
{
|
|
// Bidirektional: anhand der Baseline pruefen, ob die
|
|
// Starface-Seite seit dem letzten Sync unveraendert ist.
|
|
bool sfUnchanged = !string.IsNullOrEmpty(mapping.LastStarfaceHash)
|
|
&& sc.GetHash() == mapping.LastStarfaceHash;
|
|
if (sfUnchanged)
|
|
{
|
|
// Unveraendert + in Outlook geloescht -> Loeschung gilt
|
|
// -> auch aus Starface entfernen.
|
|
if (await starface.DeleteContactAsync(mapping.StarfaceId))
|
|
{
|
|
result.Updated++;
|
|
Action(result, $"Geloescht (OL->SF): {sc.DisplayName}");
|
|
}
|
|
continue;
|
|
}
|
|
// In Outlook geloescht, aber in Starface geaendert ->
|
|
// Bearbeitung gewinnt, in Outlook neu anlegen (Phase 3).
|
|
Log($" In Outlook geloescht, in Starface geaendert -> neu anlegen: {sc.DisplayName}");
|
|
processedStarfaceIds.Remove(sc.StarfaceId);
|
|
continue;
|
|
}
|
|
|
|
// StarfaceToOutlook: Starface ist alleinige Quelle -> in Outlook
|
|
// neu anlegen (Loeschung im Ziel zaehlt nicht).
|
|
Log($" Outlook-Kontakt geloescht, wird neu angelegt: {sc.DisplayName}");
|
|
processedStarfaceIds.Remove(sc.StarfaceId);
|
|
continue;
|
|
}
|
|
|
|
if (oc != null && sc == null)
|
|
{
|
|
// Starface-Kontakt nicht in der geladenen Liste. Zwei Faelle
|
|
// unterscheiden, indem wir ihn per ID direkt abfragen:
|
|
// (a) per ID noch vorhanden -> liegt in einem ANDEREN
|
|
// Adressbuch -> Mapping behalten, NICHT neu anlegen
|
|
// (sonst Dublette).
|
|
// (b) per ID 404 -> in Starface WIRKLICH geloescht.
|
|
bool stillExists = !string.IsNullOrEmpty(mapping.StarfaceId)
|
|
&& await starface.GetContactAsync(mapping.StarfaceId, profile.AddressBook) != null;
|
|
|
|
if (stillExists)
|
|
{
|
|
Log($" Starface-Kontakt in anderem Adressbuch, behalte Mapping: {oc.DisplayName}");
|
|
newMappings.Add(mapping);
|
|
continue;
|
|
}
|
|
|
|
// Wirklich in Starface geloescht.
|
|
if (profile.SyncDirection == SyncDirection.StarfaceToOutlook)
|
|
{
|
|
// Starface ist fuehrend -> Loeschung nach Outlook spiegeln.
|
|
if (_outlookService.DeleteContact(oc.OutlookEntryId))
|
|
{
|
|
result.Updated++;
|
|
Action(result, $"Geloescht (SF->OL): {oc.DisplayName}");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (profile.SyncDirection == SyncDirection.Both)
|
|
{
|
|
// Bidirektional: anhand der Baseline entscheiden, ob der
|
|
// Outlook-Kontakt seit dem letzten Sync unveraendert ist.
|
|
bool olUnchanged = !string.IsNullOrEmpty(mapping.LastOutlookHash)
|
|
&& oc.GetHash() == mapping.LastOutlookHash;
|
|
if (olUnchanged)
|
|
{
|
|
// Unveraendert + in Starface geloescht -> Loeschung gilt
|
|
// -> aus Outlook entfernen.
|
|
if (_outlookService.DeleteContact(oc.OutlookEntryId))
|
|
{
|
|
result.Updated++;
|
|
Action(result, $"Geloescht (SF->OL): {oc.DisplayName}");
|
|
}
|
|
continue;
|
|
}
|
|
// In Starface geloescht, aber in Outlook geaendert ->
|
|
// Bearbeitung gewinnt, in Starface neu anlegen.
|
|
Log($" In Starface geloescht, in Outlook geaendert -> neu anlegen: {oc.DisplayName}");
|
|
processedOutlookIds.Remove(oc.OutlookEntryId);
|
|
continue;
|
|
}
|
|
|
|
// OutlookToStarface: Outlook ist alleinige Quelle -> Kontakt
|
|
// in Starface neu anlegen (Loeschung im Ziel zaehlt nicht).
|
|
Log($" Starface-Kontakt geloescht, wird neu angelegt: {oc.DisplayName}");
|
|
processedOutlookIds.Remove(oc.OutlookEntryId);
|
|
continue;
|
|
}
|
|
|
|
if (oc != null && sc != null)
|
|
{
|
|
// Beide vorhanden -> auf Aenderungen pruefen.
|
|
// WICHTIG: jede Seite gegen ihre EIGENE Baseline pruefen.
|
|
// Outlook und Starface stellen denselben Kontakt
|
|
// unterschiedlich dar, ein gemeinsamer Hash schlaegt nie an.
|
|
var olHash = oc.GetHash();
|
|
var sfHash = sc.GetHash();
|
|
|
|
// Migration alter Mappings (nur LastSyncHash vorhanden):
|
|
// aktuellen Stand als Baseline uebernehmen und als synchron
|
|
// annehmen, damit kein Massen-Update ausgeloest wird.
|
|
if (string.IsNullOrEmpty(mapping.LastOutlookHash) &&
|
|
string.IsNullOrEmpty(mapping.LastStarfaceHash))
|
|
{
|
|
SetBaseline(mapping, oc, sc);
|
|
newMappings.Add(mapping);
|
|
continue;
|
|
}
|
|
|
|
bool olChanged = olHash != mapping.LastOutlookHash;
|
|
bool sfChanged = sfHash != mapping.LastStarfaceHash;
|
|
|
|
if (!olChanged && !sfChanged)
|
|
{
|
|
// Unveraendert. Snapshots aelterer Mappings (nur Hash)
|
|
// nachtragen, damit kuenftige Konflikte gemergt werden koennen.
|
|
if (mapping.LastOutlook == null || mapping.LastStarface == null)
|
|
SetBaseline(mapping, oc, sc);
|
|
newMappings.Add(mapping);
|
|
continue;
|
|
}
|
|
|
|
if (olChanged && !sfChanged && (profile.SyncDirection == SyncDirection.Both || profile.SyncDirection == SyncDirection.OutlookToStarface))
|
|
{
|
|
// Outlook hat sich geaendert -> Starface updaten
|
|
var updated = await starface.UpdateContactAsync(mapping.StarfaceId, oc, profile.AddressBook);
|
|
if (updated != null)
|
|
{
|
|
SetBaseline(mapping, oc, updated);
|
|
result.Updated++;
|
|
Action(result, $"Aktualisiert (OL->SF): {oc.DisplayName}");
|
|
}
|
|
}
|
|
else if (sfChanged && !olChanged && (profile.SyncDirection == SyncDirection.Both || profile.SyncDirection == SyncDirection.StarfaceToOutlook))
|
|
{
|
|
// Starface hat sich geaendert -> Outlook updaten
|
|
var updated = _outlookService.UpdateContact(mapping.OutlookEntryId, sc);
|
|
if (updated != null)
|
|
{
|
|
SetBaseline(mapping, updated, sc);
|
|
result.Updated++;
|
|
Action(result, $"Aktualisiert (SF->OL): {sc.DisplayName}");
|
|
}
|
|
}
|
|
else if (olChanged && sfChanged)
|
|
{
|
|
// Beide Seiten geaendert.
|
|
if (profile.SyncDirection == SyncDirection.Both
|
|
&& mapping.LastOutlook != null && mapping.LastStarface != null)
|
|
{
|
|
// Feldweises 3-Wege-Merge: unterschiedliche Felder
|
|
// bleiben beide erhalten; nur bei gleichem Feld auf
|
|
// beiden Seiten gewinnt Outlook.
|
|
var (merged, conflicts) = ContactMerger.Merge(
|
|
mapping.LastOutlook, mapping.LastStarface, oc, sc, outlookWins: true);
|
|
|
|
var updatedSf = await starface.UpdateContactAsync(mapping.StarfaceId, merged, profile.AddressBook);
|
|
var updatedOl = _outlookService.UpdateContact(mapping.OutlookEntryId, merged);
|
|
|
|
if (updatedSf != null || updatedOl != null)
|
|
{
|
|
if (updatedOl != null)
|
|
{
|
|
mapping.LastOutlook = updatedOl;
|
|
mapping.LastOutlookHash = updatedOl.GetHash();
|
|
}
|
|
if (updatedSf != null)
|
|
{
|
|
mapping.LastStarface = updatedSf;
|
|
mapping.LastStarfaceHash = updatedSf.GetHash();
|
|
}
|
|
mapping.LastSyncHash = "";
|
|
result.Updated++;
|
|
foreach (var cf in conflicts) result.Conflicts.Add(cf);
|
|
Action(result, conflicts.Count > 0
|
|
? $"Beidseitig geaendert, zusammengefuehrt ({conflicts.Count} Feld-Konflikt(e), Outlook gewinnt): {oc.DisplayName}"
|
|
: $"Beidseitig geaendert, zusammengefuehrt: {oc.DisplayName}");
|
|
}
|
|
}
|
|
else if (profile.SyncDirection != SyncDirection.StarfaceToOutlook)
|
|
{
|
|
// Fallback ohne Snapshot bzw. OutlookToStarface:
|
|
// Outlook gewinnt komplett.
|
|
var updated = await starface.UpdateContactAsync(mapping.StarfaceId, oc, profile.AddressBook);
|
|
if (updated != null)
|
|
{
|
|
SetBaseline(mapping, oc, updated);
|
|
result.Updated++;
|
|
Action(result, $"Konflikt (OL gewinnt): {oc.DisplayName}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var updated = _outlookService.UpdateContact(mapping.OutlookEntryId, sc);
|
|
if (updated != null)
|
|
{
|
|
SetBaseline(mapping, updated, sc);
|
|
result.Updated++;
|
|
Action(result, $"Konflikt (SF gewinnt): {sc.DisplayName}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
newMappings.Add(mapping);
|
|
}
|
|
|
|
// ============================================
|
|
// Phase 2: Neue Outlook-Kontakte (ohne Mapping)
|
|
// ============================================
|
|
if (profile.SyncDirection == SyncDirection.Both || profile.SyncDirection == SyncDirection.OutlookToStarface)
|
|
{
|
|
var unmappedOutlook = outlookContacts
|
|
.Where(c => !string.IsNullOrEmpty(c.OutlookEntryId) && !processedOutlookIds.Contains(c.OutlookEntryId))
|
|
.ToList();
|
|
|
|
if (unmappedOutlook.Count > 0)
|
|
Log($"Neue Outlook-Kontakte: {unmappedOutlook.Count}");
|
|
|
|
// Starface-Kontakte die noch kein Mapping haben (fuer Duplikat-Check)
|
|
var unmappedStarface = starfaceContacts
|
|
.Where(c => !string.IsNullOrEmpty(c.StarfaceId) && !processedStarfaceIds.Contains(c.StarfaceId))
|
|
.ToList();
|
|
|
|
foreach (var oc in unmappedOutlook)
|
|
{
|
|
try
|
|
{
|
|
// Duplikat-Check: existiert der Kontakt schon in der Starface?
|
|
var match = ContactMatcher.FindMatch(oc, unmappedStarface);
|
|
if (match != null)
|
|
{
|
|
// Existiert schon -> verknuepfen und updaten
|
|
var updated = await starface.UpdateContactAsync(match.StarfaceId, oc, profile.AddressBook);
|
|
if (updated != null)
|
|
{
|
|
newMappings.Add(new SyncMapping
|
|
{
|
|
ProfileId = profile.Id,
|
|
OutlookEntryId = oc.OutlookEntryId,
|
|
StarfaceId = match.StarfaceId,
|
|
LastOutlook = oc,
|
|
LastStarface = updated,
|
|
LastOutlookHash = oc.GetHash(),
|
|
LastStarfaceHash = updated.GetHash()
|
|
});
|
|
processedStarfaceIds.Add(match.StarfaceId);
|
|
unmappedStarface.Remove(match);
|
|
result.Updated++;
|
|
Action(result, $"Verknuepft (OL->SF): {oc.DisplayName}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Neu -> in Starface erstellen
|
|
Log($" Erstelle in Starface: {oc.DisplayName}");
|
|
var created = await starface.CreateContactAsync(oc, profile.AddressBook);
|
|
if (created != null && !string.IsNullOrEmpty(created.StarfaceId))
|
|
{
|
|
newMappings.Add(new SyncMapping
|
|
{
|
|
ProfileId = profile.Id,
|
|
OutlookEntryId = oc.OutlookEntryId,
|
|
StarfaceId = created.StarfaceId,
|
|
LastOutlook = oc,
|
|
LastStarface = created,
|
|
LastOutlookHash = oc.GetHash(),
|
|
LastStarfaceHash = created.GetHash()
|
|
});
|
|
result.Created++;
|
|
Action(result, $"Erstellt (OL->SF): {oc.DisplayName}");
|
|
}
|
|
else
|
|
{
|
|
Log($" FEHLER: Kontakt konnte nicht erstellt werden: {oc.DisplayName}");
|
|
result.Errors++;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Errors++;
|
|
result.ErrorMessages.Add($"OL->SF {oc.DisplayName}: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Phase 3: Neue Starface-Kontakte (ohne Mapping)
|
|
// ============================================
|
|
if (profile.SyncDirection == SyncDirection.Both || profile.SyncDirection == SyncDirection.StarfaceToOutlook)
|
|
{
|
|
var unmappedStarface = starfaceContacts
|
|
.Where(c => !string.IsNullOrEmpty(c.StarfaceId) && !processedStarfaceIds.Contains(c.StarfaceId))
|
|
.ToList();
|
|
|
|
if (unmappedStarface.Count > 0)
|
|
Log($"Neue Starface-Kontakte: {unmappedStarface.Count}");
|
|
|
|
// Outlook-Kontakte die noch kein Mapping haben (fuer Duplikat-Check)
|
|
var unmappedOutlook = outlookContacts
|
|
.Where(c => !string.IsNullOrEmpty(c.OutlookEntryId) && !processedOutlookIds.Contains(c.OutlookEntryId))
|
|
.ToList();
|
|
|
|
foreach (var sc in unmappedStarface)
|
|
{
|
|
try
|
|
{
|
|
// Duplikat-Check: existiert der Kontakt schon in Outlook?
|
|
var match = ContactMatcher.FindMatch(sc, unmappedOutlook);
|
|
if (match != null)
|
|
{
|
|
// Existiert schon -> verknuepfen und updaten
|
|
var updated = _outlookService.UpdateContact(match.OutlookEntryId, sc);
|
|
if (updated != null)
|
|
{
|
|
newMappings.Add(new SyncMapping
|
|
{
|
|
ProfileId = profile.Id,
|
|
OutlookEntryId = match.OutlookEntryId,
|
|
StarfaceId = sc.StarfaceId,
|
|
LastStarface = sc,
|
|
LastOutlook = updated,
|
|
LastStarfaceHash = sc.GetHash(),
|
|
LastOutlookHash = updated.GetHash()
|
|
});
|
|
processedOutlookIds.Add(match.OutlookEntryId);
|
|
unmappedOutlook.Remove(match);
|
|
result.Updated++;
|
|
Action(result, $"Verknuepft (SF->OL): {sc.DisplayName}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Neu -> in Outlook erstellen
|
|
var created = _outlookService.CreateContact(sc, profile.OutlookFolderPath);
|
|
if (created != null && !string.IsNullOrEmpty(created.OutlookEntryId))
|
|
{
|
|
newMappings.Add(new SyncMapping
|
|
{
|
|
ProfileId = profile.Id,
|
|
OutlookEntryId = created.OutlookEntryId,
|
|
StarfaceId = sc.StarfaceId,
|
|
LastStarface = sc,
|
|
LastOutlook = created,
|
|
LastStarfaceHash = sc.GetHash(),
|
|
LastOutlookHash = created.GetHash()
|
|
});
|
|
result.Created++;
|
|
Action(result, $"Erstellt (SF->OL): {sc.DisplayName}");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Errors++;
|
|
result.ErrorMessages.Add($"SF->OL {sc.DisplayName}: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 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++;
|
|
Action(result, $"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++;
|
|
Action(result, $"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);
|
|
|
|
await starface.LogoutAsync();
|
|
|
|
Log($"Fertig: {result.Created} erstellt, {result.Updated} aktualisiert, {result.Errors} Fehler");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Errors++;
|
|
result.ErrorMessages.Add($"Allgemeiner Fehler: {ex.Message}");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|