Die SyncEngine kennt jetzt nur noch IContactBackend. Was dahinter steckt, entscheidet das Profil — der Ablauf mit getrennten Baselines, Wiederzuordnung und Konfliktaufloesung bleibt unangetastet. In ihm steckt die Erfahrung, die man nicht zweimal machen will. FonariaApiClient spricht deren REST-API. Der Kern ist das Feld `verwaltet`, das jeder Schreibvorgang mitschickt: Fonaria haelt den Kontakt als vollstaendige vCard, dieses Sync-Modell bildet davon nur einen Ausschnitt ab. Ohne diese Liste haette ein aus Outlook gespiegelter Kontakt nach dem ersten Abgleich kein Bild, keine Kategorien und keinen Jahrestag mehr — nicht weil jemand sie geloescht haette, sondern weil wir sie nicht kannten. Zwei Verluste lagen darunter und sind gefunden worden, weil der Client gegen eine echte Anlage lief: - Die Abteilung steckt im zweiten Teil von ORG. Wer nur die Firma schickt, loescht sie mit. - Neben der geschaeftlichen Anschrift kann eine private stehen. Das Sync-Modell traegt nur eine. Beides wird jetzt aus dem gelesenen Stand unveraendert zurueckgeschrieben. Umbenannt: StarfaceConnection -> SystemConnection, StarfaceAddressBook -> RemoteAddressBook. Die JSON-Namen bleiben ueber JsonProperty erhalten, sonst verloeren bestehende Profile ihre Verbindungsdaten. SyncMapping.StarfaceId behaelt seinen Namen aus demselben Grund. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
131 lines
5.5 KiB
C#
131 lines
5.5 KiB
C#
using Newtonsoft.Json;
|
|
|
|
namespace StarfaceOutlookSync.Models
|
|
{
|
|
/// <summary>Welche Telefonanlage auf der anderen Seite steht.</summary>
|
|
public enum PhoneSystem
|
|
{
|
|
Starface,
|
|
Fonaria
|
|
}
|
|
|
|
public enum SyncDirection
|
|
{
|
|
Both,
|
|
OutlookToStarface,
|
|
StarfaceToOutlook
|
|
}
|
|
|
|
public class SystemConnection
|
|
{
|
|
// Voreinstellung Starface, damit Profile aus der Zeit vor Fonaria
|
|
// unveraendert weiterlaufen: Was im JSON fehlt, ist eine Starface.
|
|
public PhoneSystem System { get; set; } = PhoneSystem.Starface;
|
|
|
|
public string Host { get; set; } = "";
|
|
public int Port { get; set; } = 443;
|
|
public bool UseSsl { get; set; } = true;
|
|
public string LoginId { get; set; } = "";
|
|
public string Password { get; set; } = "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ein Adressbuch auf der Gegenseite.
|
|
///
|
|
/// Die Felder heissen noch nach der Starface, weil dort ihre Bedeutung
|
|
/// herkommt: <c>Type</c> unterscheidet zentrales, persoenliches und
|
|
/// Tag-Adressbuch, <c>TagId</c> traegt die Kennung. Bei Fonaria steht in
|
|
/// <c>TagId</c> schlicht die Nummer des Adressbuchs und in <c>Type</c>
|
|
/// dessen Art (private, shared, system).
|
|
/// </summary>
|
|
public class RemoteAddressBook
|
|
{
|
|
public string Type { get; set; } = "central"; // central, user, tag
|
|
public string UserId { get; set; } = "";
|
|
public string TagId { get; set; } = "";
|
|
public string Name { get; set; } = "";
|
|
|
|
public override string ToString() => Name;
|
|
}
|
|
|
|
public class SyncProfile
|
|
{
|
|
public string Id { get; set; } = "";
|
|
public string Name { get; set; } = "";
|
|
// Die JSON-Namen bleiben, wie sie waren — sonst verloeren alle
|
|
// bestehenden Profile beim ersten Start ihre Verbindungsdaten.
|
|
[JsonProperty("StarfaceConnection")]
|
|
public SystemConnection Connection { get; set; } = new SystemConnection();
|
|
|
|
[JsonProperty("StarfaceAddressBook")]
|
|
public RemoteAddressBook AddressBook { get; set; } = new RemoteAddressBook();
|
|
public string OutlookFolderPath { get; set; } = "";
|
|
public string OutlookFolderName { get; set; } = "Kontakte";
|
|
public SyncDirection SyncDirection { get; set; } = SyncDirection.Both;
|
|
public string LastSync { get; set; } = "";
|
|
public bool Enabled { get; set; } = true;
|
|
public int AutoSyncIntervalMinutes { get; set; } = 0; // 0 = manuell
|
|
}
|
|
|
|
public class SyncMapping
|
|
{
|
|
public string ProfileId { get; set; } = "";
|
|
public string OutlookEntryId { get; set; } = "";
|
|
|
|
// Die Kennung auf der Gegenseite — bei Fonaria die Kontakt-ID. Der
|
|
// Name bleibt: Er steht in jeder bereits gespeicherten Zuordnung, und
|
|
// ein Umbenennen wuerde sie alle entwerten.
|
|
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; } = "";
|
|
|
|
// 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.
|
|
public string LastSyncHash { get; set; } = "";
|
|
}
|
|
|
|
public class SyncResult
|
|
{
|
|
public string ProfileName { get; set; } = "";
|
|
public string Timestamp { get; set; } = "";
|
|
public int Created { get; set; }
|
|
public int Updated { get; set; }
|
|
public int Errors { get; set; }
|
|
public System.Collections.Generic.List<string> ErrorMessages { get; set; } = new System.Collections.Generic.List<string>();
|
|
|
|
// Was konkret veraendert wurde (erstellt/aktualisiert/geloescht je Kontakt),
|
|
// fuer das Protokoll.
|
|
public System.Collections.Generic.List<string> Changes { 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; } = ""; // Anzeige-Label, z.B. "E-Mail"
|
|
public string FieldKey { get; set; } = ""; // stabiler Schluessel, z.B. "Email"
|
|
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";
|
|
}
|
|
}
|