diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..79e1496f
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,65 @@
+# Changelog
+
+Alle nennenswerten Aenderungen an Starface Outlook Sync werden hier dokumentiert.
+
+Format orientiert sich an [Keep a Changelog](https://keepachangelog.com/de/),
+Versionsschema ist `x.x.x.x` (siehe `release.sh`).
+
+## [Unreleased]
+
+### Behoben
+
+- **Dubletten auf beiden Seiten beim Synchronisieren.** Mehrere zusammenhaengende
+ Ursachen wurden beseitigt:
+ - Eine unvollstaendig geladene Starface-Kontaktliste (z.B. durch einen
+ Lade-Fehler oder Timeout) liess die Engine Kontakte faelschlich als
+ "geloescht" ansehen, ihr Mapping verwerfen und sie beim naechsten Lauf neu
+ anlegen. Der Kontakt-Abruf bricht jetzt mit Fehlermeldung ab (inkl.
+ Wiederholversuch), statt still mit einer Teil-Liste weiterzuarbeiten.
+ - Ist ein Starface-Kontakt nicht in der geladenen Liste (z.B. anderes
+ Adressbuch), wird das Mapping jetzt **behalten** statt verworfen und neu
+ angelegt.
+ - Das Wiederzuordnen bestehender Kontakte war zu streng: eine von Starface
+ umformatierte Telefonnummer konnte einen eindeutigen E-Mail- oder
+ Namens-Treffer ueberstimmen und so eine Neuanlage statt Verknuepfung
+ ausloesen. Ein gleicher E-Mail- oder voller Namens-Treffer reicht jetzt.
+
+- **Synchronisation extrem langsam / schrieb bei jedem Lauf alle Kontakte neu.**
+ Die Aenderungserkennung verglich einen einzigen Hash gegen beide Seiten.
+ Outlook und Starface stellen denselben Kontakt aber unterschiedlich dar
+ (nicht uebertragene Felder, abweichendes Telefonformat), wodurch der Hash nie
+ uebereinstimmte und praktisch jeder Kontakt bei jedem Sync neu geschrieben
+ wurde. Jede Seite hat jetzt eine eigene Baseline (`LastOutlookHash` /
+ `LastStarfaceHash`); nur tatsaechlich geaenderte Kontakte werden geschrieben.
+
+### Geaendert
+
+- Starface-Kontaktdetails werden beim Laden parallel abgerufen (8 gleichzeitig)
+ statt einzeln nacheinander – deutlich schneller bei grossen Adressbuechern.
+- `UpdateContact` (Outlook) und `UpdateContactAsync` (Starface) geben jetzt den
+ frisch eingelesenen Stand zurueck, damit die Baseline nach dem Schreiben
+ korrekt gesetzt wird und der Sync sauber konvergiert.
+
+### Migration
+
+- Bestehende Mapping-Dateien werden beim ersten Sync automatisch uebernommen
+ (ein ruhiger Durchlauf ohne Massen-Update).
+- **Bereits vorhandene Dubletten werden nicht automatisch entfernt** – die Fixes
+ verhindern nur neue. Vorhandene Doppel-Kontakte einmalig manuell bereinigen.
+
+## [0.0.0.23]
+
+### Behoben
+
+- Kritischer Loesch-Fehler: Kontakte wurden bei einer geaenderten Outlook-EntryID
+ nicht mehr massenhaft geloescht.
+
+### Geaendert
+
+- Outlook-Sicherheitsschluessel werden fuer Domaenenumgebungen zusaetzlich nach
+ HKLM geschrieben (unterdrueckt die Outlook-Sicherheitsabfrage).
+
+## Aeltere Versionen
+
+Aeltere Releases (v0.0.0.1 – v0.0.0.22) sind ueber die Git-Historie und die
+Git-Tags nachvollziehbar.
diff --git a/src/StarfaceOutlookSync/Models/SyncProfile.cs b/src/StarfaceOutlookSync/Models/SyncProfile.cs
index 57b2a493..ea196cd3 100644
--- a/src/StarfaceOutlookSync/Models/SyncProfile.cs
+++ b/src/StarfaceOutlookSync/Models/SyncProfile.cs
@@ -45,6 +45,15 @@ namespace StarfaceOutlookSync.Models
public string ProfileId { get; set; } = "";
public string OutlookEntryId { get; set; } = "";
public string StarfaceId { get; set; } = "";
+
+ // Getrennte Baselines pro Seite. Outlook und Starface stellen denselben
+ // Kontakt unterschiedlich dar (Felder, Telefonformat), daher MUSS jede
+ // Seite gegen ihre eigene zuletzt-gesehene Repraesentation verglichen
+ // werden - sonst gilt jeder Kontakt bei jedem Sync als geaendert.
+ public string LastOutlookHash { get; set; } = "";
+ public string LastStarfaceHash { get; set; } = "";
+
+ // Alt-Feld (vor v0.0.0.24). Nur noch fuer Migration bestehender Mappings.
public string LastSyncHash { get; set; } = "";
}
diff --git a/src/StarfaceOutlookSync/Services/OutlookContactsService.cs b/src/StarfaceOutlookSync/Services/OutlookContactsService.cs
index 9259869c..e5820b78 100644
--- a/src/StarfaceOutlookSync/Services/OutlookContactsService.cs
+++ b/src/StarfaceOutlookSync/Services/OutlookContactsService.cs
@@ -350,7 +350,13 @@ namespace StarfaceOutlookSync.Services
}
}
- public bool UpdateContact(string entryId, UnifiedContact contact)
+ ///
+ /// Aktualisiert den Outlook-Kontakt und gibt den frisch eingelesenen
+ /// Stand zurueck (null bei Fehler). Der zurueckgegebene Kontakt liefert
+ /// den massgeblichen Hash NACH dem Schreiben - noetig damit die naechste
+ /// Synchronisation den Kontakt nicht erneut als geaendert erkennt.
+ ///
+ public UnifiedContact UpdateContact(string entryId, UnifiedContact contact)
{
try
{
@@ -361,15 +367,17 @@ namespace StarfaceOutlookSync.Services
MapToOutlook(contact, ci);
ci.Save();
+ var updated = MapFromOutlook(ci);
+
Marshal.ReleaseComObject(ci);
Marshal.ReleaseComObject(ns);
- return true;
+ return updated;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error updating contact: {ex.Message}");
- return false;
+ return null;
}
}
diff --git a/src/StarfaceOutlookSync/Services/StarfaceApiClient.cs b/src/StarfaceOutlookSync/Services/StarfaceApiClient.cs
index 21234fa5..19808248 100644
--- a/src/StarfaceOutlookSync/Services/StarfaceApiClient.cs
+++ b/src/StarfaceOutlookSync/Services/StarfaceApiClient.cs
@@ -194,7 +194,16 @@ namespace StarfaceOutlookSync.Services
query += $"&tags={book.TagId}";
var resp = await _http.GetAsync($"{_baseUrl}/contacts?{query}");
- if (!resp.IsSuccessStatusCode) break;
+ if (!resp.IsSuccessStatusCode)
+ {
+ // WICHTIG: nicht still abbrechen. Eine unvollstaendige Liste
+ // laesst die Sync-Engine Kontakte faelschlich als geloescht
+ // ansehen -> Mappings werden verworfen -> Dubletten.
+ throw new Exception(
+ $"Starface-Kontaktliste konnte nicht vollstaendig geladen werden " +
+ $"(Seite {page}: HTTP {(int)resp.StatusCode}). Synchronisation abgebrochen, " +
+ $"um Dubletten zu vermeiden.");
+ }
var body = await resp.Content.ReadAsStringAsync();
JArray array;
@@ -232,31 +241,40 @@ namespace StarfaceOutlookSync.Services
OnDebug?.Invoke($"Seite {page}: {array.Count} Kontakte in Liste");
- // Die Listen-API gibt nur Summary zurueck.
- // Jeden Kontakt einzeln abrufen fuer alle Felder.
- foreach (var item in array)
+ if (firstPage)
{
- var id = item["id"]?.ToString();
- if (string.IsNullOrEmpty(id)) continue;
-
- try
+ var firstId = array[0]?["id"]?.ToString();
+ if (!string.IsNullOrEmpty(firstId))
{
- var detailResp = await _http.GetAsync($"{_baseUrl}/contacts/{id}");
- if (detailResp.IsSuccessStatusCode)
- {
- var detailBody = await detailResp.Content.ReadAsStringAsync();
- var detailObj = JObject.Parse(detailBody);
-
- if (firstPage)
- {
- OnDebug?.Invoke($"Starface Kontakt-Detail (1. Kontakt):\n{detailObj.ToString(Formatting.Indented)}");
- firstPage = false;
- }
-
- contacts.Add(MapFromStarface(detailObj));
- }
+ var sample = await FetchDetailAsync(firstId);
+ if (sample != null)
+ OnDebug?.Invoke($"Starface Kontakt-Detail (1. Kontakt):\n{sample.ToString(Formatting.Indented)}");
}
- catch { }
+ firstPage = false;
+ }
+
+ // Die Listen-API liefert nur eine Zusammenfassung; jeder Kontakt
+ // muss einzeln geladen werden. Das parallelisieren (begrenzt),
+ // sonst dauert es bei vielen Kontakten extrem lange.
+ var ids = array
+ .Select(it => it["id"]?.ToString())
+ .Where(id => !string.IsNullOrEmpty(id))
+ .ToList();
+
+ const int maxParallel = 8;
+ for (int i = 0; i < ids.Count; i += maxParallel)
+ {
+ var batch = ids.Skip(i).Take(maxParallel)
+ .Select(async id =>
+ {
+ var detail = await FetchDetailAsync(id);
+ // null = 404 (zwischenzeitlich geloescht) -> ueberspringen.
+ return detail == null ? null : MapFromStarface(detail);
+ })
+ .ToList();
+
+ var mapped = await Task.WhenAll(batch);
+ contacts.AddRange(mapped.Where(c => c != null));
}
if (array.Count < pageSize) break;
@@ -266,6 +284,36 @@ namespace StarfaceOutlookSync.Services
return contacts;
}
+ ///
+ /// Laedt das Detail-JSON eines Kontakts mit kleiner Wiederholung.
+ /// Gibt null zurueck, wenn der Kontakt zwischen Listen- und Detail-Abruf
+ /// wirklich geloescht wurde (404 - harmlos, wird uebersprungen).
+ /// Wirft bei transienten Fehlern, damit der Aufrufer NICHT mit einer
+ /// unvollstaendigen Liste weiterarbeitet (sonst entstehen Dubletten).
+ ///
+ private async Task FetchDetailAsync(string id)
+ {
+ for (int attempt = 0; attempt < 3; attempt++)
+ {
+ try
+ {
+ var resp = await _http.GetAsync($"{_baseUrl}/contacts/{id}");
+ if (resp.IsSuccessStatusCode)
+ return JObject.Parse(await resp.Content.ReadAsStringAsync());
+
+ // 404 = zwischenzeitlich geloescht; nicht erneut versuchen.
+ if (resp.StatusCode == HttpStatusCode.NotFound) return null;
+ }
+ catch { }
+
+ await Task.Delay(250 * (attempt + 1));
+ }
+
+ throw new Exception(
+ $"Starface-Kontakt {id} konnte nach mehreren Versuchen nicht geladen werden. " +
+ $"Synchronisation abgebrochen, um Dubletten zu vermeiden.");
+ }
+
public async Task CreateContactAsync(UnifiedContact contact, StarfaceAddressBook book)
{
var sfContact = MapToStarface(contact);
@@ -300,7 +348,13 @@ namespace StarfaceOutlookSync.Services
return MapFromStarface(created);
}
- public async Task UpdateContactAsync(string contactId, UnifiedContact contact, StarfaceAddressBook book)
+ ///
+ /// Aktualisiert den Starface-Kontakt und gibt den massgeblichen Stand
+ /// NACH dem Schreiben zurueck (null bei Fehler). Wird fuer die getrennte
+ /// Hash-Baseline benoetigt, damit der Kontakt beim naechsten Sync nicht
+ /// erneut faelschlich als geaendert gilt.
+ ///
+ public async Task UpdateContactAsync(string contactId, UnifiedContact contact, StarfaceAddressBook book)
{
var sfContact = MapToStarface(contact);
sfContact["id"] = contactId;
@@ -323,9 +377,37 @@ namespace StarfaceOutlookSync.Services
{
var respBody = await resp.Content.ReadAsStringAsync();
OnDebug?.Invoke($"PUT /contacts/{contactId} fehlgeschlagen: {(int)resp.StatusCode}\n{respBody}");
+ return null;
}
- return resp.IsSuccessStatusCode;
+ // Frischen Stand zurueckgeben. Manche Versionen liefern den Kontakt
+ // direkt in der PUT-Antwort, sonst per GET nachladen.
+ try
+ {
+ var respBody = await resp.Content.ReadAsStringAsync();
+ if (!string.IsNullOrWhiteSpace(respBody))
+ {
+ var obj = JObject.Parse(respBody);
+ if (obj["blocks"] != null)
+ return MapFromStarface(obj);
+ }
+ }
+ catch { }
+
+ return await GetContactAsync(contactId) ?? contact;
+ }
+
+ /// Laedt einen einzelnen Kontakt mit allen Feldern.
+ public async Task GetContactAsync(string contactId)
+ {
+ try
+ {
+ var resp = await _http.GetAsync($"{_baseUrl}/contacts/{contactId}");
+ if (!resp.IsSuccessStatusCode) return null;
+ var obj = JObject.Parse(await resp.Content.ReadAsStringAsync());
+ return MapFromStarface(obj);
+ }
+ catch { return null; }
}
public async Task DeleteContactAsync(string contactId)
diff --git a/src/StarfaceOutlookSync/Services/SyncEngine.cs b/src/StarfaceOutlookSync/Services/SyncEngine.cs
index adcb5285..b2b87361 100644
--- a/src/StarfaceOutlookSync/Services/SyncEngine.cs
+++ b/src/StarfaceOutlookSync/Services/SyncEngine.cs
@@ -37,72 +37,38 @@ namespace StarfaceOutlookSync.Services
private static bool IsMatch(UnifiedContact a, UnifiedContact b)
{
- // Mindestens ein identifizierendes Feld muss vorhanden sein
- bool hasName = !string.IsNullOrEmpty(a.FirstName) || !string.IsNullOrEmpty(a.LastName);
- bool hasEmail = !string.IsNullOrEmpty(a.Email);
- bool hasPhone = !string.IsNullOrEmpty(a.PhoneWork) || !string.IsNullOrEmpty(a.PhoneMobile);
+ bool hasName = (!string.IsNullOrEmpty(a.FirstName) || !string.IsNullOrEmpty(a.LastName))
+ && (!string.IsNullOrEmpty(b.FirstName) || !string.IsNullOrEmpty(b.LastName));
- if (!hasName && !hasEmail && !hasPhone) return false;
-
- // E-Mail: wenn auf beiden Seiten vorhanden, muss sie gleich sein
- // Wenn nur auf einer Seite vorhanden -> kein Match
- if (!FieldsCompatible(a.Email, b.Email)) return false;
-
- // Name: wenn auf einer Seite vorhanden, muss er gleich sein
- if (!FieldsCompatible(a.FirstName, b.FirstName)) return false;
- if (!FieldsCompatible(a.LastName, b.LastName)) return false;
-
- // Firma: wenn auf einer Seite vorhanden, muss sie gleich sein
- // Leere Firma vs. gefuellte Firma = verschiedene Kontakte
- if (!FieldsCompatible(a.Company, b.Company)) return false;
-
- // Telefon/Fax: wenn auf einer Seite vorhanden, muss es gleich sein
- if (!PhoneFieldsCompatible(a.PhoneWork, b.PhoneWork)) return false;
- if (!PhoneFieldsCompatible(a.PhoneMobile, b.PhoneMobile)) return false;
- if (!PhoneFieldsCompatible(a.PhoneHome, b.PhoneHome)) return false;
- if (!PhoneFieldsCompatible(a.Fax, b.Fax)) return false;
-
- // Mindestens ein starkes Match muss vorhanden sein
+ // Starke Identifikatoren
bool emailMatch = !string.IsNullOrEmpty(a.Email) && !string.IsNullOrEmpty(b.Email)
&& a.Email.Equals(b.Email, StringComparison.OrdinalIgnoreCase);
bool nameMatch = hasName
- && a.FirstName.Equals(b.FirstName, StringComparison.OrdinalIgnoreCase)
- && a.LastName.Equals(b.LastName, StringComparison.OrdinalIgnoreCase)
- && (!string.IsNullOrEmpty(a.FirstName) || !string.IsNullOrEmpty(a.LastName));
+ && (a.FirstName ?? "").Equals(b.FirstName ?? "", StringComparison.OrdinalIgnoreCase)
+ && (a.LastName ?? "").Equals(b.LastName ?? "", StringComparison.OrdinalIgnoreCase);
bool phoneMatch = (!string.IsNullOrEmpty(a.PhoneWork) && !string.IsNullOrEmpty(b.PhoneWork)
&& NormalizePhone(a.PhoneWork) == NormalizePhone(b.PhoneWork))
+ || (!string.IsNullOrEmpty(a.PhoneMobile) && !string.IsNullOrEmpty(b.PhoneMobile)
+ && NormalizePhone(a.PhoneMobile) == NormalizePhone(b.PhoneMobile))
|| (!string.IsNullOrEmpty(a.Fax) && !string.IsNullOrEmpty(b.Fax)
&& NormalizePhone(a.Fax) == NormalizePhone(b.Fax));
bool companyMatch = !string.IsNullOrEmpty(a.Company) && !string.IsNullOrEmpty(b.Company)
&& a.Company.Equals(b.Company, StringComparison.OrdinalIgnoreCase);
- // Email oder Name reicht. Telefon/Fax nur mit Firma zusammen.
- return emailMatch || nameMatch || (phoneMatch && companyMatch) || (companyMatch && phoneMatch);
- }
+ // Widerspruch: beide haben eine E-Mail, aber unterschiedlich -> verschiedene Personen.
+ bool emailContradiction = !string.IsNullOrEmpty(a.Email) && !string.IsNullOrEmpty(b.Email) && !emailMatch;
- ///
- /// Prueft ob zwei Felder kompatibel sind.
- /// Beide leer = kompatibel. Beide gleich = kompatibel.
- /// Eins leer, eins gefuellt = NICHT kompatibel (verschiedene Kontakte).
- ///
- private static bool FieldsCompatible(string a, string b)
- {
- bool aEmpty = string.IsNullOrEmpty(a);
- bool bEmpty = string.IsNullOrEmpty(b);
+ // Gleiche E-Mail ist der staerkste Identifikator und reicht allein.
+ if (emailMatch) return true;
- if (aEmpty && bEmpty) return true;
- if (aEmpty != bEmpty) return false; // Einer leer, anderer nicht
- return a.Equals(b, StringComparison.OrdinalIgnoreCase);
- }
+ // Gleicher voller Name reicht, solange keine widerspruechliche E-Mail vorliegt.
+ // (Telefon-Umformatierung durch Starface darf einen Namens-Treffer NICHT verhindern.)
+ if (nameMatch && !emailContradiction) return true;
- private static bool PhoneFieldsCompatible(string a, string b)
- {
- bool aEmpty = string.IsNullOrEmpty(a);
- bool bEmpty = string.IsNullOrEmpty(b);
+ // Schwacher Pfad: Telefon/Fax nur zusammen mit gleicher Firma und ohne E-Mail-Widerspruch.
+ if (phoneMatch && companyMatch && !emailContradiction) return true;
- if (aEmpty && bEmpty) return true;
- if (aEmpty != bEmpty) return false;
- return NormalizePhone(a) == NormalizePhone(b);
+ return false;
}
private static string NormalizePhone(string phone)
@@ -215,29 +181,50 @@ namespace StarfaceOutlookSync.Services
if (oc != null && sc == null)
{
- // Starface-Kontakt nicht gefunden.
- // Kann passieren wenn der Kontakt einem anderen Adressbuch gehoert.
- // NICHT loeschen, nur Mapping entfernen - wird in Phase 2/3 neu verknuepft
- Log($" Starface-Kontakt nicht in Liste (anderes Adressbuch?): {oc.DisplayName}");
- // Mapping verwerfen, Outlook-Kontakt als unverarbeitet belassen
- // damit er in Phase 2 neu zugeordnet oder erstellt werden kann
+ // Starface-Kontakt nicht in der geladenen Liste.
+ // Da unvollstaendige Ladevorgaenge inzwischen abgebrochen
+ // werden (siehe StarfaceApiClient), liegt das hoechstens an
+ // einem anderen Adressbuch. NICHT loeschen und NICHT neu
+ // anlegen - sonst entstehen Dubletten. Mapping behalten,
+ // beim naechsten Sync wird es erneut abgeglichen.
+ Log($" Starface-Kontakt nicht in Liste (anderes Adressbuch?), behalte Mapping: {oc.DisplayName}");
+ newMappings.Add(mapping);
continue;
}
if (oc != null && sc != null)
{
- // Beide vorhanden -> auf Aenderungen pruefen
+ // 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();
- bool olChanged = olHash != mapping.LastSyncHash;
- bool sfChanged = sfHash != mapping.LastSyncHash;
+
+ // 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))
+ {
+ mapping.LastOutlookHash = olHash;
+ mapping.LastStarfaceHash = sfHash;
+ mapping.LastSyncHash = "";
+ newMappings.Add(mapping);
+ continue;
+ }
+
+ bool olChanged = olHash != mapping.LastOutlookHash;
+ bool sfChanged = sfHash != mapping.LastStarfaceHash;
if (olChanged && !sfChanged && (profile.SyncDirection == SyncDirection.Both || profile.SyncDirection == SyncDirection.OutlookToStarface))
{
// Outlook hat sich geaendert -> Starface updaten
- if (await starface.UpdateContactAsync(mapping.StarfaceId, oc, profile.StarfaceAddressBook))
+ var updated = await starface.UpdateContactAsync(mapping.StarfaceId, oc, profile.StarfaceAddressBook);
+ if (updated != null)
{
- mapping.LastSyncHash = olHash;
+ mapping.LastOutlookHash = olHash;
+ mapping.LastStarfaceHash = updated.GetHash();
result.Updated++;
Log($" Aktualisiert (OL->SF): {oc.DisplayName}");
}
@@ -245,30 +232,36 @@ namespace StarfaceOutlookSync.Services
else if (sfChanged && !olChanged && (profile.SyncDirection == SyncDirection.Both || profile.SyncDirection == SyncDirection.StarfaceToOutlook))
{
// Starface hat sich geaendert -> Outlook updaten
- if (_outlookService.UpdateContact(mapping.OutlookEntryId, sc))
+ var updated = _outlookService.UpdateContact(mapping.OutlookEntryId, sc);
+ if (updated != null)
{
- mapping.LastSyncHash = sfHash;
+ mapping.LastStarfaceHash = sfHash;
+ mapping.LastOutlookHash = updated.GetHash();
result.Updated++;
Log($" Aktualisiert (SF->OL): {sc.DisplayName}");
}
}
else if (olChanged && sfChanged)
{
- // Beide geaendert -> Konflikt, neuere gewinnt (Outlook bevorzugt)
+ // Beide geaendert -> Konflikt, Outlook bevorzugt
if (profile.SyncDirection != SyncDirection.StarfaceToOutlook)
{
- if (await starface.UpdateContactAsync(mapping.StarfaceId, oc, profile.StarfaceAddressBook))
+ var updated = await starface.UpdateContactAsync(mapping.StarfaceId, oc, profile.StarfaceAddressBook);
+ if (updated != null)
{
- mapping.LastSyncHash = olHash;
+ mapping.LastOutlookHash = olHash;
+ mapping.LastStarfaceHash = updated.GetHash();
result.Updated++;
Log($" Konflikt (OL gewinnt): {oc.DisplayName}");
}
}
else
{
- if (_outlookService.UpdateContact(mapping.OutlookEntryId, sc))
+ var updated = _outlookService.UpdateContact(mapping.OutlookEntryId, sc);
+ if (updated != null)
{
- mapping.LastSyncHash = sfHash;
+ mapping.LastStarfaceHash = sfHash;
+ mapping.LastOutlookHash = updated.GetHash();
result.Updated++;
Log($" Konflikt (SF gewinnt): {sc.DisplayName}");
}
@@ -306,14 +299,16 @@ namespace StarfaceOutlookSync.Services
if (match != null)
{
// Existiert schon -> verknuepfen und updaten
- if (await starface.UpdateContactAsync(match.StarfaceId, oc, profile.StarfaceAddressBook))
+ var updated = await starface.UpdateContactAsync(match.StarfaceId, oc, profile.StarfaceAddressBook);
+ if (updated != null)
{
newMappings.Add(new SyncMapping
{
ProfileId = profile.Id,
OutlookEntryId = oc.OutlookEntryId,
StarfaceId = match.StarfaceId,
- LastSyncHash = oc.GetHash()
+ LastOutlookHash = oc.GetHash(),
+ LastStarfaceHash = updated.GetHash()
});
processedStarfaceIds.Add(match.StarfaceId);
unmappedStarface.Remove(match);
@@ -333,7 +328,8 @@ namespace StarfaceOutlookSync.Services
ProfileId = profile.Id,
OutlookEntryId = oc.OutlookEntryId,
StarfaceId = created.StarfaceId,
- LastSyncHash = oc.GetHash()
+ LastOutlookHash = oc.GetHash(),
+ LastStarfaceHash = created.GetHash()
});
result.Created++;
Log($" Erstellt (OL->SF): {oc.DisplayName}");
@@ -379,14 +375,16 @@ namespace StarfaceOutlookSync.Services
if (match != null)
{
// Existiert schon -> verknuepfen und updaten
- if (_outlookService.UpdateContact(match.OutlookEntryId, sc))
+ var updated = _outlookService.UpdateContact(match.OutlookEntryId, sc);
+ if (updated != null)
{
newMappings.Add(new SyncMapping
{
ProfileId = profile.Id,
OutlookEntryId = match.OutlookEntryId,
StarfaceId = sc.StarfaceId,
- LastSyncHash = sc.GetHash()
+ LastStarfaceHash = sc.GetHash(),
+ LastOutlookHash = updated.GetHash()
});
processedOutlookIds.Add(match.OutlookEntryId);
unmappedOutlook.Remove(match);
@@ -405,7 +403,8 @@ namespace StarfaceOutlookSync.Services
ProfileId = profile.Id,
OutlookEntryId = created.OutlookEntryId,
StarfaceId = sc.StarfaceId,
- LastSyncHash = sc.GetHash()
+ LastStarfaceHash = sc.GetHash(),
+ LastOutlookHash = created.GetHash()
});
result.Created++;
Log($" Erstellt (SF->OL): {sc.DisplayName}");