Make dedupe robust: independent sides, wait for sync, report failures
Nach der Vorschau blieben Dubletten teils stehen. Ursachen abgesichert: - Outlook- und Anlagen-Seite laufen jetzt in getrennten try-Bloecken. Vorher brach ein Fehler beim Laden/Verarbeiten der Anlage die ganze Methode ab, bevor die Outlook-Seite ueberhaupt bereinigt wurde. - RunExclusiveAsync wartet bis zu 60s auf einen laufenden Sync, statt sofort abzubrechen (Auto-Sync feuert jede Minute -> haeufige Kollision). - Fehlgeschlagene Loeschungen werden jetzt als Fehler gemeldet (vorher still verschluckt), plus Gruppen-Zaehler ins Log. - DedupeForm zeigt einen deutlichen Hinweis (MessageBox), wenn wegen eines laufenden Syncs nicht ausgefuehrt wurde, und laesst einen erneuten Versuch zu. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+5
-2
@@ -16,8 +16,11 @@ Versionsschema ist `x.x.x.x` (siehe `release.sh`).
|
||||
Felder aus den Dubletten aufgefuellt werden (kein Datenverlust), die uebrigen
|
||||
werden geloescht. Verwaiste Zuordnungen werden bereinigt, sodass der naechste
|
||||
bidirektionale Sync die Gewinner sauber 1:1 verbindet. Laeuft unter derselben
|
||||
Sperre wie ein Sync (kein gleichzeitiger Lauf), nutzt dieselbe Kontakt-
|
||||
Erkennung wie der Sync und wird vollstaendig protokolliert.
|
||||
Sperre wie ein Sync (kein gleichzeitiger Lauf - wartet bei laufendem Sync bis
|
||||
zu 60s statt abzubrechen), nutzt dieselbe Kontakt-Erkennung wie der Sync und
|
||||
wird vollstaendig protokolliert. Outlook- und Anlagen-Seite werden unabhaengig
|
||||
verarbeitet (eine streikende Anlage verhindert nicht die Outlook-Bereinigung);
|
||||
fehlgeschlagene Loeschungen werden gemeldet.
|
||||
|
||||
### Behoben
|
||||
|
||||
|
||||
@@ -90,8 +90,55 @@ namespace StarfaceOutlookSync.Services
|
||||
var deletedStarfaceIds = new HashSet<string>();
|
||||
var deletedOutlookIds = new HashSet<string>();
|
||||
|
||||
// --- Outlook (lokal, unabhaengig - laeuft auch wenn die Anlage streikt) ---
|
||||
try
|
||||
{
|
||||
var outlookContacts = _outlookService.GetContacts(profile.OutlookFolderPath);
|
||||
var groups = GroupDuplicates(outlookContacts, c => mappedOutlook.Contains(c.OutlookEntryId));
|
||||
Log($"Outlook: {groups.Count} Gruppe(n) mit Dubletten.");
|
||||
foreach (var g in groups)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool changed = ContactMerger.FillEmptyInto(g.Survivor, g.Duplicates);
|
||||
if (changed && _outlookService.UpdateContact(g.Survivor.OutlookEntryId, g.Survivor) == null)
|
||||
{
|
||||
report.Errors++;
|
||||
report.Messages.Add($"Outlook: '{g.Survivor.DisplayName}' konnte nicht ergaenzt werden - uebersprungen.");
|
||||
continue;
|
||||
}
|
||||
foreach (var dup in g.Duplicates)
|
||||
{
|
||||
if (_outlookService.DeleteContact(dup.OutlookEntryId))
|
||||
{
|
||||
deletedOutlookIds.Add(dup.OutlookEntryId);
|
||||
report.OutlookDeleted++;
|
||||
Log($" Outlook: Dublette geloescht -> '{g.Survivor.DisplayName}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
report.Errors++;
|
||||
report.Messages.Add($"Outlook: Dublette von '{g.Survivor.DisplayName}' konnte nicht geloescht werden.");
|
||||
}
|
||||
}
|
||||
report.OutlookMerged++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
report.Errors++;
|
||||
report.Messages.Add($"Outlook '{g.Survivor.DisplayName}': {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
report.Errors++;
|
||||
report.Messages.Add("Outlook gesamt: " + ex.Message);
|
||||
}
|
||||
|
||||
// --- Telefonanlage (unabhaengig) ---
|
||||
try
|
||||
{
|
||||
using (var backend = ContactBackendFactory.Create(profile.Connection))
|
||||
{
|
||||
backend.OnDebug += Log;
|
||||
@@ -99,33 +146,31 @@ namespace StarfaceOutlookSync.Services
|
||||
throw new Exception("Login an der Telefonanlage fehlgeschlagen.");
|
||||
|
||||
var remoteContacts = await backend.GetContactsAsync(profile.AddressBook);
|
||||
|
||||
// --- Telefonanlage ---
|
||||
foreach (var g in GroupDuplicates(remoteContacts, c => mappedStarface.Contains(c.StarfaceId)))
|
||||
var groups = GroupDuplicates(remoteContacts, c => mappedStarface.Contains(c.StarfaceId));
|
||||
Log($"Telefonanlage: {groups.Count} Gruppe(n) mit Dubletten.");
|
||||
foreach (var g in groups)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool changed = ContactMerger.FillEmptyInto(g.Survivor, g.Duplicates);
|
||||
bool survivorOk = true;
|
||||
if (changed)
|
||||
survivorOk = await backend.UpdateContactAsync(g.Survivor.StarfaceId, g.Survivor, profile.AddressBook) != null;
|
||||
|
||||
if (!survivorOk)
|
||||
if (changed && await backend.UpdateContactAsync(g.Survivor.StarfaceId, g.Survivor, profile.AddressBook) == null)
|
||||
{
|
||||
// Gewinner konnte nicht ergaenzt werden -> Dubletten NICHT loeschen
|
||||
// (sonst gingen die nur dort vorhandenen Felder verloren).
|
||||
report.Errors++;
|
||||
report.Messages.Add($"Anlage: '{g.Survivor.DisplayName}' konnte nicht ergaenzt werden - Gruppe uebersprungen.");
|
||||
report.Messages.Add($"Anlage: '{g.Survivor.DisplayName}' konnte nicht ergaenzt werden - uebersprungen.");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var dup in g.Duplicates)
|
||||
{
|
||||
if (await backend.DeleteContactAsync(dup.StarfaceId))
|
||||
{
|
||||
deletedStarfaceIds.Add(dup.StarfaceId);
|
||||
report.RemoteDeleted++;
|
||||
Log($" Anlage: Dublette geloescht -> zusammengefuehrt in '{g.Survivor.DisplayName}'");
|
||||
Log($" Anlage: Dublette geloescht -> '{g.Survivor.DisplayName}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
report.Errors++;
|
||||
report.Messages.Add($"Anlage: Dublette von '{g.Survivor.DisplayName}' konnte nicht geloescht werden.");
|
||||
}
|
||||
}
|
||||
report.RemoteMerged++;
|
||||
@@ -139,40 +184,11 @@ namespace StarfaceOutlookSync.Services
|
||||
|
||||
await backend.LogoutAsync();
|
||||
}
|
||||
|
||||
// --- Outlook ---
|
||||
foreach (var g in GroupDuplicates(outlookContacts, c => mappedOutlook.Contains(c.OutlookEntryId)))
|
||||
{
|
||||
try
|
||||
{
|
||||
bool changed = ContactMerger.FillEmptyInto(g.Survivor, g.Duplicates);
|
||||
bool survivorOk = true;
|
||||
if (changed)
|
||||
survivorOk = _outlookService.UpdateContact(g.Survivor.OutlookEntryId, g.Survivor) != null;
|
||||
|
||||
if (!survivorOk)
|
||||
{
|
||||
report.Errors++;
|
||||
report.Messages.Add($"Outlook: '{g.Survivor.DisplayName}' konnte nicht ergaenzt werden - Gruppe uebersprungen.");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var dup in g.Duplicates)
|
||||
{
|
||||
if (_outlookService.DeleteContact(dup.OutlookEntryId))
|
||||
{
|
||||
deletedOutlookIds.Add(dup.OutlookEntryId);
|
||||
report.OutlookDeleted++;
|
||||
Log($" Outlook: Dublette geloescht -> zusammengefuehrt in '{g.Survivor.DisplayName}'");
|
||||
}
|
||||
}
|
||||
report.OutlookMerged++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
report.Errors++;
|
||||
report.Messages.Add($"Outlook '{g.Survivor.DisplayName}': {ex.Message}");
|
||||
}
|
||||
report.Messages.Add("Telefonanlage gesamt: " + ex.Message);
|
||||
}
|
||||
|
||||
// Verwaiste Zuordnungen (zeigen auf geloeschte Dubletten) entfernen.
|
||||
|
||||
@@ -123,11 +123,19 @@ namespace StarfaceOutlookSync.Services
|
||||
/// </summary>
|
||||
public async Task<bool> RunExclusiveAsync(Func<Task> action, Action<string> status)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _running, 1, 0) != 0)
|
||||
// Auf einen gerade laufenden Sync warten (bis 60s), statt sofort
|
||||
// abzubrechen - der Auto-Sync feuert ja jede Minute.
|
||||
var guardDeadline = DateTime.UtcNow.AddSeconds(60);
|
||||
while (Interlocked.CompareExchange(ref _running, 1, 0) != 0)
|
||||
{
|
||||
status?.Invoke("Es laeuft bereits ein Sync / eine Aktion - bitte warten.");
|
||||
if (DateTime.UtcNow >= guardDeadline)
|
||||
{
|
||||
status?.Invoke("Es laeuft bereits ein Sync - bitte spaeter erneut versuchen.");
|
||||
return false;
|
||||
}
|
||||
status?.Invoke("Warte, bis der laufende Sync fertig ist...");
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
SyncLock crossLock = null;
|
||||
var sharedDir = UserSettings.Load().SharedDirectory;
|
||||
|
||||
@@ -160,7 +160,15 @@ namespace StarfaceOutlookSync.UI
|
||||
|
||||
if (!ran)
|
||||
{
|
||||
AppendLog("Abgebrochen (Sync/Aktion laeuft oder anderer Arbeitsplatz aktiv).");
|
||||
AppendLog("Abgebrochen: gerade laeuft ein Sync (oder ein anderer Arbeitsplatz ist aktiv).");
|
||||
_lblSummary.Text = "Nicht ausgefuehrt - es laeuft gerade ein Sync. Bitte kurz warten und erneut versuchen.";
|
||||
_lblSummary.ForeColor = Color.OrangeRed;
|
||||
MessageBox.Show(this,
|
||||
"Die Zusammenfuehrung wurde NICHT ausgefuehrt, weil gerade ein Sync laeuft " +
|
||||
"(oder ein anderer Arbeitsplatz aktiv ist).\n\nBitte kurz warten und erneut auf " +
|
||||
"'Zusammenfuehren starten' klicken.",
|
||||
"Dubletten", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
_btnStart.Enabled = true; // erneuter Versuch moeglich
|
||||
}
|
||||
else if (report != null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user