Add field-level 3-way merge for bidirectional conflicts
Bisher wurde bei einem Konflikt (beide Seiten geaendert) der ganze Datensatz ueberschrieben - eine gleichzeitige Aenderung an einem anderen Feld ging verloren (z.B. A aendert Telefon in Outlook, B aendert Mail in Starface -> eine Aenderung weg). Jetzt: - Mapping speichert je Seite einen Snapshot des letzten Sync-Stands (LastOutlook/LastStarface), zusaetzlich zu den Hashes. - Bei beidseitiger Aenderung im Both-Modus wird feldweise gemergt (ContactMerger): unterschiedliche Felder bleiben beide erhalten, nur bei echtem Konflikt am selben Feld gewinnt Outlook. - Echte Feld-Konflikte landen in SyncResult.Conflicts und werden im MainForm per Tray-Meldung angezeigt. - Snapshots werden in allen Baseline-Punkten gesetzt (Phase 1-3) und fuer aeltere Mappings beim naechsten unveraenderten Sync nachgetragen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,17 @@ Versionsschema ist `x.x.x.x` (siehe `release.sh`).
|
|||||||
wurde. Jede Seite hat jetzt eine eigene Baseline (`LastOutlookHash` /
|
wurde. Jede Seite hat jetzt eine eigene Baseline (`LastOutlookHash` /
|
||||||
`LastStarfaceHash`); nur tatsaechlich geaenderte Kontakte werden geschrieben.
|
`LastStarfaceHash`); nur tatsaechlich geaenderte Kontakte werden geschrieben.
|
||||||
|
|
||||||
|
### Hinzugefuegt
|
||||||
|
|
||||||
|
- **Feldweises 3-Wege-Merge bei Konflikten (bidirektional).** Wenn derselbe
|
||||||
|
Kontakt zwischen zwei Syncs auf beiden Seiten geaendert wurde, bleiben jetzt
|
||||||
|
Aenderungen an *unterschiedlichen* Feldern beide erhalten (z.B. einer aendert
|
||||||
|
die Telefonnummer in Outlook, ein anderer die E-Mail in Starface). Nur wenn
|
||||||
|
DASSELBE Feld auf beiden Seiten unterschiedlich geaendert wurde, greift die
|
||||||
|
Vorrang-Regel (Outlook gewinnt). Dafuer wird im Mapping zusaetzlich ein
|
||||||
|
Snapshot des letzten Sync-Stands je Seite gespeichert. Solche echten
|
||||||
|
Feld-Konflikte werden dem Benutzer per Tray-Meldung angezeigt.
|
||||||
|
|
||||||
### Geaendert
|
### Geaendert
|
||||||
|
|
||||||
- **Doppelte Syncs verhindert (lokal).** Der Schutz gegen gleichzeitig laufende
|
- **Doppelte Syncs verhindert (lokal).** Der Schutz gegen gleichzeitig laufende
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ namespace StarfaceOutlookSync.Models
|
|||||||
public string LastOutlookHash { get; set; } = "";
|
public string LastOutlookHash { get; set; } = "";
|
||||||
public string LastStarfaceHash { get; set; } = "";
|
public string LastStarfaceHash { get; set; } = "";
|
||||||
|
|
||||||
|
// Snapshot des zuletzt synchronisierten Stands je Seite. Ermoeglicht ein
|
||||||
|
// feldweises 3-Wege-Merge bei Konflikten (welche Seite hat welches Feld
|
||||||
|
// geaendert), statt den ganzen Datensatz zu ueberschreiben. Null bei
|
||||||
|
// Alt-Mappings -> dann Fallback auf ganz-ueberschreiben.
|
||||||
|
public UnifiedContact LastOutlook { get; set; }
|
||||||
|
public UnifiedContact LastStarface { get; set; }
|
||||||
|
|
||||||
// Alt-Feld (vor v0.0.0.24). Nur noch fuer Migration bestehender Mappings.
|
// Alt-Feld (vor v0.0.0.24). Nur noch fuer Migration bestehender Mappings.
|
||||||
public string LastSyncHash { get; set; } = "";
|
public string LastSyncHash { get; set; } = "";
|
||||||
}
|
}
|
||||||
@@ -65,5 +72,23 @@ namespace StarfaceOutlookSync.Models
|
|||||||
public int Updated { get; set; }
|
public int Updated { get; set; }
|
||||||
public int Errors { get; set; }
|
public int Errors { get; set; }
|
||||||
public System.Collections.Generic.List<string> ErrorMessages { get; set; } = new System.Collections.Generic.List<string>();
|
public System.Collections.Generic.List<string> ErrorMessages { get; set; } = new System.Collections.Generic.List<string>();
|
||||||
|
|
||||||
|
// Echte Feld-Konflikte (dasselbe Feld auf beiden Seiten geaendert), die
|
||||||
|
// ueber die Vorrang-Regel aufgeloest wurden. Fuer Benutzer-Hinweise.
|
||||||
|
public System.Collections.Generic.List<FieldConflict> Conflicts { get; set; } = new System.Collections.Generic.List<FieldConflict>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FieldConflict
|
||||||
|
{
|
||||||
|
public string StarfaceId { get; set; } = "";
|
||||||
|
public string ContactName { get; set; } = "";
|
||||||
|
public string Field { get; set; } = "";
|
||||||
|
public string OutlookValue { get; set; } = "";
|
||||||
|
public string StarfaceValue { get; set; } = "";
|
||||||
|
public string Winner { get; set; } = ""; // "Outlook" oder "Starface"
|
||||||
|
|
||||||
|
public override string ToString() =>
|
||||||
|
$"{ContactName}: Feld '{Field}' auf beiden Seiten geaendert " +
|
||||||
|
$"(Outlook: '{OutlookValue}' / Starface: '{StarfaceValue}') -> {Winner} uebernommen";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using StarfaceOutlookSync.Models;
|
||||||
|
|
||||||
|
namespace StarfaceOutlookSync.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Feldweises 3-Wege-Merge zweier Kontaktstaende gegen ihre Baseline.
|
||||||
|
/// Erlaubt es, dass zwei Stellen unterschiedliche Felder desselben Kontakts
|
||||||
|
/// aendern, ohne dass eine Aenderung verloren geht. Nur wenn DASSELBE Feld
|
||||||
|
/// auf beiden Seiten geaendert wurde, greift die Vorrang-Regel.
|
||||||
|
/// </summary>
|
||||||
|
public static class ContactMerger
|
||||||
|
{
|
||||||
|
private class FieldDef
|
||||||
|
{
|
||||||
|
public string Label;
|
||||||
|
public Func<UnifiedContact, string> Get;
|
||||||
|
public Action<UnifiedContact, string> Set;
|
||||||
|
public bool IsPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly FieldDef[] Fields = new[]
|
||||||
|
{
|
||||||
|
new FieldDef { Label = "Vorname", Get = c => c.FirstName, Set = (c, v) => c.FirstName = v },
|
||||||
|
new FieldDef { Label = "Nachname", Get = c => c.LastName, Set = (c, v) => c.LastName = v },
|
||||||
|
new FieldDef { Label = "Firma", Get = c => c.Company, Set = (c, v) => c.Company = v },
|
||||||
|
new FieldDef { Label = "Position", Get = c => c.JobTitle, Set = (c, v) => c.JobTitle = v },
|
||||||
|
new FieldDef { Label = "E-Mail", Get = c => c.Email, Set = (c, v) => c.Email = v },
|
||||||
|
new FieldDef { Label = "E-Mail 2", Get = c => c.EmailSecondary, Set = (c, v) => c.EmailSecondary = v },
|
||||||
|
new FieldDef { Label = "Telefon", Get = c => c.PhoneWork, Set = (c, v) => c.PhoneWork = v, IsPhone = true },
|
||||||
|
new FieldDef { Label = "Mobil", Get = c => c.PhoneMobile, Set = (c, v) => c.PhoneMobile = v, IsPhone = true },
|
||||||
|
new FieldDef { Label = "Telefon privat", Get = c => c.PhoneHome, Set = (c, v) => c.PhoneHome = v, IsPhone = true },
|
||||||
|
new FieldDef { Label = "Fax", Get = c => c.Fax, Set = (c, v) => c.Fax = v, IsPhone = true },
|
||||||
|
new FieldDef { Label = "Strasse", Get = c => c.Street, Set = (c, v) => c.Street = v },
|
||||||
|
new FieldDef { Label = "Ort", Get = c => c.City, Set = (c, v) => c.City = v },
|
||||||
|
new FieldDef { Label = "PLZ", Get = c => c.PostalCode, Set = (c, v) => c.PostalCode = v },
|
||||||
|
new FieldDef { Label = "Bundesland", Get = c => c.State, Set = (c, v) => c.State = v },
|
||||||
|
new FieldDef { Label = "Land", Get = c => c.Country, Set = (c, v) => c.Country = v },
|
||||||
|
new FieldDef { Label = "Webseite", Get = c => c.Website, Set = (c, v) => c.Website = v },
|
||||||
|
new FieldDef { Label = "Notizen", Get = c => c.Notes, Set = (c, v) => c.Notes = v },
|
||||||
|
new FieldDef { Label = "Anrede", Get = c => c.Salutation, Set = (c, v) => c.Salutation = v },
|
||||||
|
new FieldDef { Label = "Titel", Get = c => c.Title, Set = (c, v) => c.Title = v },
|
||||||
|
new FieldDef { Label = "Geburtstag", Get = c => c.Birthday, Set = (c, v) => c.Birthday = v },
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fuehrt den Outlook- und Starface-Stand gegen ihre jeweilige Baseline
|
||||||
|
/// zusammen. outlookWins entscheidet bei echten Feld-Konflikten.
|
||||||
|
/// </summary>
|
||||||
|
public static (UnifiedContact merged, List<FieldConflict> conflicts) Merge(
|
||||||
|
UnifiedContact baseOutlook, UnifiedContact baseStarface,
|
||||||
|
UnifiedContact outlook, UnifiedContact starface,
|
||||||
|
bool outlookWins)
|
||||||
|
{
|
||||||
|
var merged = new UnifiedContact
|
||||||
|
{
|
||||||
|
OutlookEntryId = outlook.OutlookEntryId,
|
||||||
|
StarfaceId = starface.StarfaceId
|
||||||
|
};
|
||||||
|
var conflicts = new List<FieldConflict>();
|
||||||
|
|
||||||
|
foreach (var f in Fields)
|
||||||
|
{
|
||||||
|
string olv = f.Get(outlook) ?? "";
|
||||||
|
string sfv = f.Get(starface) ?? "";
|
||||||
|
string bol = f.Get(baseOutlook) ?? "";
|
||||||
|
string bsf = f.Get(baseStarface) ?? "";
|
||||||
|
|
||||||
|
bool olChanged = !Equal(olv, bol, f.IsPhone);
|
||||||
|
bool sfChanged = !Equal(sfv, bsf, f.IsPhone);
|
||||||
|
|
||||||
|
string chosen;
|
||||||
|
if (olChanged && !sfChanged)
|
||||||
|
{
|
||||||
|
chosen = olv;
|
||||||
|
}
|
||||||
|
else if (sfChanged && !olChanged)
|
||||||
|
{
|
||||||
|
chosen = sfv;
|
||||||
|
}
|
||||||
|
else if (olChanged && sfChanged)
|
||||||
|
{
|
||||||
|
if (Equal(olv, sfv, f.IsPhone))
|
||||||
|
{
|
||||||
|
// Beide auf denselben Wert geaendert -> kein echter Konflikt.
|
||||||
|
chosen = olv;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chosen = outlookWins ? olv : sfv;
|
||||||
|
conflicts.Add(new FieldConflict
|
||||||
|
{
|
||||||
|
StarfaceId = starface.StarfaceId,
|
||||||
|
ContactName = outlook.DisplayName,
|
||||||
|
Field = f.Label,
|
||||||
|
OutlookValue = olv,
|
||||||
|
StarfaceValue = sfv,
|
||||||
|
Winner = outlookWins ? "Outlook" : "Starface"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Keine Seite hat dieses Feld geaendert -> Outlook-Wert behalten.
|
||||||
|
chosen = olv;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Set(merged, chosen);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (merged, conflicts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Equal(string a, string b, bool isPhone)
|
||||||
|
{
|
||||||
|
if (isPhone)
|
||||||
|
return NormalizePhone(a) == NormalizePhone(b);
|
||||||
|
return string.Equals(a ?? "", b ?? "", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizePhone(string phone)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(phone)) return "";
|
||||||
|
return new string(phone.Where(c => char.IsDigit(c) || c == '+').ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,20 @@ namespace StarfaceOutlookSync.Services
|
|||||||
|
|
||||||
private void Log(string message) => OnProgress?.Invoke(message);
|
private void Log(string message) => OnProgress?.Invoke(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 = "";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Findet einen passenden Kontakt in der Kandidatenliste.
|
/// Findet einen passenden Kontakt in der Kandidatenliste.
|
||||||
/// Strenges Matching: Felder die auf einer Seite gefuellt sind muessen
|
/// Strenges Matching: Felder die auf einer Seite gefuellt sind muessen
|
||||||
@@ -282,9 +296,7 @@ namespace StarfaceOutlookSync.Services
|
|||||||
if (string.IsNullOrEmpty(mapping.LastOutlookHash) &&
|
if (string.IsNullOrEmpty(mapping.LastOutlookHash) &&
|
||||||
string.IsNullOrEmpty(mapping.LastStarfaceHash))
|
string.IsNullOrEmpty(mapping.LastStarfaceHash))
|
||||||
{
|
{
|
||||||
mapping.LastOutlookHash = olHash;
|
SetBaseline(mapping, oc, sc);
|
||||||
mapping.LastStarfaceHash = sfHash;
|
|
||||||
mapping.LastSyncHash = "";
|
|
||||||
newMappings.Add(mapping);
|
newMappings.Add(mapping);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -292,14 +304,23 @@ namespace StarfaceOutlookSync.Services
|
|||||||
bool olChanged = olHash != mapping.LastOutlookHash;
|
bool olChanged = olHash != mapping.LastOutlookHash;
|
||||||
bool sfChanged = sfHash != mapping.LastStarfaceHash;
|
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))
|
if (olChanged && !sfChanged && (profile.SyncDirection == SyncDirection.Both || profile.SyncDirection == SyncDirection.OutlookToStarface))
|
||||||
{
|
{
|
||||||
// Outlook hat sich geaendert -> Starface updaten
|
// Outlook hat sich geaendert -> Starface updaten
|
||||||
var updated = await starface.UpdateContactAsync(mapping.StarfaceId, oc, profile.StarfaceAddressBook);
|
var updated = await starface.UpdateContactAsync(mapping.StarfaceId, oc, profile.StarfaceAddressBook);
|
||||||
if (updated != null)
|
if (updated != null)
|
||||||
{
|
{
|
||||||
mapping.LastOutlookHash = olHash;
|
SetBaseline(mapping, oc, updated);
|
||||||
mapping.LastStarfaceHash = updated.GetHash();
|
|
||||||
result.Updated++;
|
result.Updated++;
|
||||||
Log($" Aktualisiert (OL->SF): {oc.DisplayName}");
|
Log($" Aktualisiert (OL->SF): {oc.DisplayName}");
|
||||||
}
|
}
|
||||||
@@ -310,22 +331,54 @@ namespace StarfaceOutlookSync.Services
|
|||||||
var updated = _outlookService.UpdateContact(mapping.OutlookEntryId, sc);
|
var updated = _outlookService.UpdateContact(mapping.OutlookEntryId, sc);
|
||||||
if (updated != null)
|
if (updated != null)
|
||||||
{
|
{
|
||||||
mapping.LastStarfaceHash = sfHash;
|
SetBaseline(mapping, updated, sc);
|
||||||
mapping.LastOutlookHash = updated.GetHash();
|
|
||||||
result.Updated++;
|
result.Updated++;
|
||||||
Log($" Aktualisiert (SF->OL): {sc.DisplayName}");
|
Log($" Aktualisiert (SF->OL): {sc.DisplayName}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (olChanged && sfChanged)
|
else if (olChanged && sfChanged)
|
||||||
{
|
{
|
||||||
// Beide geaendert -> Konflikt, Outlook bevorzugt
|
// Beide Seiten geaendert.
|
||||||
if (profile.SyncDirection != SyncDirection.StarfaceToOutlook)
|
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.StarfaceAddressBook);
|
||||||
|
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);
|
||||||
|
Log(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.StarfaceAddressBook);
|
var updated = await starface.UpdateContactAsync(mapping.StarfaceId, oc, profile.StarfaceAddressBook);
|
||||||
if (updated != null)
|
if (updated != null)
|
||||||
{
|
{
|
||||||
mapping.LastOutlookHash = olHash;
|
SetBaseline(mapping, oc, updated);
|
||||||
mapping.LastStarfaceHash = updated.GetHash();
|
|
||||||
result.Updated++;
|
result.Updated++;
|
||||||
Log($" Konflikt (OL gewinnt): {oc.DisplayName}");
|
Log($" Konflikt (OL gewinnt): {oc.DisplayName}");
|
||||||
}
|
}
|
||||||
@@ -335,14 +388,12 @@ namespace StarfaceOutlookSync.Services
|
|||||||
var updated = _outlookService.UpdateContact(mapping.OutlookEntryId, sc);
|
var updated = _outlookService.UpdateContact(mapping.OutlookEntryId, sc);
|
||||||
if (updated != null)
|
if (updated != null)
|
||||||
{
|
{
|
||||||
mapping.LastStarfaceHash = sfHash;
|
SetBaseline(mapping, updated, sc);
|
||||||
mapping.LastOutlookHash = updated.GetHash();
|
|
||||||
result.Updated++;
|
result.Updated++;
|
||||||
Log($" Konflikt (SF gewinnt): {sc.DisplayName}");
|
Log($" Konflikt (SF gewinnt): {sc.DisplayName}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Beide unveraendert -> nichts tun
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newMappings.Add(mapping);
|
newMappings.Add(mapping);
|
||||||
@@ -382,6 +433,8 @@ namespace StarfaceOutlookSync.Services
|
|||||||
ProfileId = profile.Id,
|
ProfileId = profile.Id,
|
||||||
OutlookEntryId = oc.OutlookEntryId,
|
OutlookEntryId = oc.OutlookEntryId,
|
||||||
StarfaceId = match.StarfaceId,
|
StarfaceId = match.StarfaceId,
|
||||||
|
LastOutlook = oc,
|
||||||
|
LastStarface = updated,
|
||||||
LastOutlookHash = oc.GetHash(),
|
LastOutlookHash = oc.GetHash(),
|
||||||
LastStarfaceHash = updated.GetHash()
|
LastStarfaceHash = updated.GetHash()
|
||||||
});
|
});
|
||||||
@@ -403,6 +456,8 @@ namespace StarfaceOutlookSync.Services
|
|||||||
ProfileId = profile.Id,
|
ProfileId = profile.Id,
|
||||||
OutlookEntryId = oc.OutlookEntryId,
|
OutlookEntryId = oc.OutlookEntryId,
|
||||||
StarfaceId = created.StarfaceId,
|
StarfaceId = created.StarfaceId,
|
||||||
|
LastOutlook = oc,
|
||||||
|
LastStarface = created,
|
||||||
LastOutlookHash = oc.GetHash(),
|
LastOutlookHash = oc.GetHash(),
|
||||||
LastStarfaceHash = created.GetHash()
|
LastStarfaceHash = created.GetHash()
|
||||||
});
|
});
|
||||||
@@ -458,6 +513,8 @@ namespace StarfaceOutlookSync.Services
|
|||||||
ProfileId = profile.Id,
|
ProfileId = profile.Id,
|
||||||
OutlookEntryId = match.OutlookEntryId,
|
OutlookEntryId = match.OutlookEntryId,
|
||||||
StarfaceId = sc.StarfaceId,
|
StarfaceId = sc.StarfaceId,
|
||||||
|
LastStarface = sc,
|
||||||
|
LastOutlook = updated,
|
||||||
LastStarfaceHash = sc.GetHash(),
|
LastStarfaceHash = sc.GetHash(),
|
||||||
LastOutlookHash = updated.GetHash()
|
LastOutlookHash = updated.GetHash()
|
||||||
});
|
});
|
||||||
@@ -478,6 +535,8 @@ namespace StarfaceOutlookSync.Services
|
|||||||
ProfileId = profile.Id,
|
ProfileId = profile.Id,
|
||||||
OutlookEntryId = created.OutlookEntryId,
|
OutlookEntryId = created.OutlookEntryId,
|
||||||
StarfaceId = sc.StarfaceId,
|
StarfaceId = sc.StarfaceId,
|
||||||
|
LastStarface = sc,
|
||||||
|
LastOutlook = created,
|
||||||
LastStarfaceHash = sc.GetHash(),
|
LastStarfaceHash = sc.GetHash(),
|
||||||
LastOutlookHash = created.GetHash()
|
LastOutlookHash = created.GetHash()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -358,10 +358,22 @@ namespace StarfaceOutlookSync.UI
|
|||||||
|
|
||||||
var msg = $"{profile.Name}: {result.Created} erstellt, {result.Updated} aktualisiert";
|
var msg = $"{profile.Name}: {result.Created} erstellt, {result.Updated} aktualisiert";
|
||||||
if (result.Errors > 0) msg += $", {result.Errors} Fehler";
|
if (result.Errors > 0) msg += $", {result.Errors} Fehler";
|
||||||
|
if (result.Conflicts.Count > 0) msg += $", {result.Conflicts.Count} Konflikt(e)";
|
||||||
|
|
||||||
_trayIcon.ShowBalloonTip(3000, "Starface Sync", msg,
|
_trayIcon.ShowBalloonTip(3000, "Starface Sync", msg,
|
||||||
result.Errors > 0 ? ToolTipIcon.Warning : ToolTipIcon.Info);
|
result.Errors > 0 ? ToolTipIcon.Warning : ToolTipIcon.Info);
|
||||||
|
|
||||||
|
// Echte Feld-Konflikte gesondert melden, damit der Benutzer weiss,
|
||||||
|
// dass ein Wert ueberschrieben wurde.
|
||||||
|
if (result.Conflicts.Count > 0)
|
||||||
|
{
|
||||||
|
var detail = string.Join("\n", result.Conflicts.Take(5).Select(c => c.ToString()));
|
||||||
|
if (result.Conflicts.Count > 5)
|
||||||
|
detail += $"\n... und {result.Conflicts.Count - 5} weitere";
|
||||||
|
_trayIcon.ShowBalloonTip(10000,
|
||||||
|
$"Konflikt bei {result.Conflicts.Count} Kontakt(en)", detail, ToolTipIcon.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
SetStatus(msg);
|
SetStatus(msg);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
Reference in New Issue
Block a user