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>
Beim Tray-/Auto-Sync lief die Engine auf dem UI-Thread -> Fenster und
Tray-Kontextmenue froren ein (Menuepunkte reagierten nicht).
- MainForm.RunSync fuehrt die Engine jetzt via Task.Run im Hintergrund aus
(wie schon der manuelle Fenster-Sync).
- Balloon() marshalled Tray-Meldungen thread-sicher per BeginInvoke auf den
UI-Thread (die Fortsetzung kann nach Task.Run aus einem Pool-Thread kommen).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Firmen-/Service-Eintraege (nur Nummer + Firma, kein Name/E-Mail) wurden bei
jedem Sync neu angelegt statt verknuepft, weil das Matching Rufnummern
feldgenau und formatabhaengig verglich. Bei unterschiedlich formatierten
Nummern (+49 vs 0) und wechselnden Feldern (geschaeftlich/privat) schlug der
Abgleich fehl -> Vervielfachung.
- NormalizePhone vereinheitlicht jetzt +49/0049 -> 0 (SyncEngine + ContactMerger).
- Telefon-Abgleich feldUEBERGREIFEND (jede Nummer gegen jede) via SharedPhone.
- "Nummer + Firma"-Treffer nur noch fuer Eintraege OHNE Personennamen, damit
Kollegen mit gemeinsamer Zentrale-Nummer nicht verschmolzen werden.
- Zusaetzlich: reine Nummern-Eintraege (kein Name/Firma/E-Mail) matchen ueber
eine gemeinsame Rufnummer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Setup setzt den Autostart weiterhin fuer alle Benutzer (HKLM); die
Task-Beschreibung im Installer stellt das jetzt klar.
In den Einstellungen neuer Abschnitt "Autostart":
- "Nur fuer diesen Benutzer" -> HKCU\...\Run
- "Fuer alle Benutzer" -> HKLM\...\Run, nur mit Admin-Rechten aenderbar
(so laesst sich der vom Setup gesetzte All-User-Autostart auch entfernen).
Neuer AutostartManager kapselt Lesen/Setzen/Entfernen beider Run-Eintraege
und die Admin-Pruefung. Single-Instance-Mutex verhindert weiterhin einen
Doppelstart, falls beide Eintraege gesetzt sind.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Neue Einstellung "Protokoll auto-leeren - Eintraege aelter als (Tage)".
0 = aus (alle Eintraege bleiben), >0 entfernt aeltere Eintraege.
- UserSettings.LogRetentionDays (Standard 0).
- Logger.PruneOlderThan(days): parst den Zeitstempel-Prefix je Zeile und
entfernt zu alte; Zeilen ohne Zeitstempel bleiben erhalten.
- Ausgefuehrt beim Start, vor jedem Sync (Coordinator), beim Oeffnen des
Protokolls und beim Speichern der Einstellungen.
- SettingsForm: NumericUpDown (0-3650).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Der manuelle "Synchronisieren"-Button (SyncProgressForm) lief ueber eine eigene
Engine und umging die clientuebergreifende Lock-Datei, den lokalen Guard und
einen Teil des Protokolls. Jetzt teilen sich beide Pfade einen SyncCoordinator.
- Neuer SyncCoordinator kapselt: prozessweiten Re-Entrancy-Guard (statisch, so
koennen manueller + automatischer Sync nicht mehr gleichzeitig laufen),
Lock-Datei (mit Warte-Status), Konflikt-Notizen anderer Arbeitsplaetze,
Protokoll (Start/Ergebnis/Aenderungen/Konflikte/Fehler) und das Verteilen
eigener Konflikt-Notizen. runEngine wird vom Aufrufer uebergeben, damit das
Threading pro Pfad erhalten bleibt (UI-Thread vs. Task.Run).
- MainForm.RunSync und SyncProgressForm.RunSync nutzen den Coordinator; UI
(Tray-Meldung vs. Fenster) bleibt jeweils beim Aufrufer.
- Lock/Notiz/Guard-Logik aus MainForm entfernt (jetzt zentral).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Protokoll:
- SyncResult.Changes erfasst jede tatsaechliche Aenderung (erstellt/aktualisiert/
geloescht/verknuepft/zusammengefuehrt je Kontakt) per Action()-Helfer.
- Beide Sync-Pfade (Auto/Tray via RunSync UND manueller Sync via
SyncProgressForm) schreiben Start, Ergebnis, Aenderungen, Konflikte und Fehler
ins persistente Protokoll.
Benachrichtigungen:
- UserSettings: NotificationsEnabled (allgemein) + NotifyWarningsErrors
(Konflikte/Fehler), beide in der Einstellungen-Maske als Haken.
- MainForm.Balloon() zeigt Tray-Meldungen nur, wenn der passende Haken aktiv
ist; Zusammenfassung gilt als Warnung, wenn Fehler/Konflikte auftraten.
- Protokoll wird unabhaengig von den Benachrichtigungs-Einstellungen geschrieben.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tray-Meldungen verschwinden - daher ein dauerhaftes Protokoll.
- Logger: threadsicheres Datei-Log in %AppData%\StarfaceOutlookSync\sync.log
mit Rotation bei 2 MB.
- MainForm protokolliert Sync-Start, Ergebnis, Konflikte (lokal + von anderen
Arbeitsplaetzen), Fehler und uebersprungene Laeufe (Sperre).
- Neuer Button "Protokoll" oeffnet LogViewerForm (Aktualisieren/Leeren/Ordner
oeffnen, scrollt ans Ende).
- README/CHANGELOG aktualisiert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bisher bekam jeder Arbeitsplatz, der den Konflikt-Kontakt im Adressbuch hat,
den Hinweis - auch wenn er den Wert gar nicht selbst gepflegt hat.
Jetzt zeigt ein Client eine fremde Konflikt-Notiz nur, wenn er den Kontakt hat
UND sein eigener Feldwert vom uebernommenen (Gewinner-)Wert abweicht. Die
Pruefung laeuft VOR dem Sync (gegen den eigenen Mapping-Snapshot), bevor der
Sync den Stand auf den Gewinner-Wert angleicht.
- FieldConflict/ConflictNotice: stabiler FieldKey zusaetzlich zum Anzeige-Label.
- ContactMerger: GetValue/ValuesEqual per FieldKey (telefon-normalisiert).
- ConflictNotifier.GetPending: Filter "eigener Wert != Gewinner-Wert", bekommt
StarfaceId -> eigener Kontaktstand.
- MainForm zeigt die Hinweise jetzt vor dem Sync und liefert den eigenen Stand
aus den Mapping-Snapshots.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wird ein echter Feld-Konflikt aufgeloest, erfahren jetzt auch die ANDEREN
Arbeitsplaetze davon - nicht nur der aufloesende Client.
- ConflictNotice-Modell + ConflictNotifier: schreibt pro Konflikt eine Notiz
in <shared>/conflicts/ (nach StarfaceId), liest beim Sync ungesehene Notizen
zu eigenen Kontakten, zeigt sie als Tray-Hinweis und merkt sich gezeigte
lokal (seen-conflicts.json). Veraltete Notizen (>7 Tage) werden aufgeraeumt.
- MainForm: schreibt nach einem Sync mit Konflikten die Notizen und zeigt
ausstehende Notizen anderer Clients (gefiltert auf eigene gemappte
StarfaceIds). AcquireCrossClientLock nimmt jetzt das gemeinsame Verzeichnis
als Parameter.
- README/CHANGELOG aktualisiert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beschreibt Lock-Datei starface-sync.lock: atomares Anlegen (CreateNew),
nur waehrend des Syncs vorhanden, Inhalt (Host/User/Zeit/PID), Warte- und
Absturz-Verhalten, sowie den Hinweis auf bidirektionalen Modus im Mehrplatz.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Das gemeinsame Verzeichnis darf ein UNC-Pfad sein (\\server\freigabe\...),
kein Netzlaufwerksbuchstabe noetig. Statt nur Directory.Exists zu pruefen
(und sonst still ohne Sperre zu syncen) wird das Verzeichnis bei Bedarf
angelegt; nur bei echtem Zugriffsfehler wird ohne Sperre fortgefahren.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verhindert, dass mehrere Arbeitsplaetze gleichzeitig dasselbe Starface-
Adressbuch synchronisieren (Dubletten/Lost-Updates bei echter Ueberlappung).
- Neues optionales Setting "Gemeinsames Verzeichnis" (UserSettings.SharedDirectory)
in der Einstellungen-Maske inkl. Ordner-Browser.
- SyncLock: atomare Lock-Datei (FileMode.CreateNew) im gemeinsamen Verzeichnis,
waehrend des Syncs offen gehalten -> bei Absturz gibt das OS das Handle frei
und ein anderer Client uebernimmt die verwaiste Datei (Stale-Erkennung 15 Min,
Loeschen scheitert am offenen Handle eines lebenden Halters).
- MainForm wartet vor dem Sync bis zu 2 Min auf eine freie Sperre, sonst wird
der Lauf uebersprungen. Ohne/bei nicht erreichbarem Verzeichnis laeuft der
Sync ohne diese Sperre weiter (lokaler Interlocked-Schutz bleibt).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Der Schutz gegen gleichzeitige Syncs (manuell vs. Auto-Sync-Timer) war ein
nicht-atomares pruefen-und-setzen auf einem volatile bool. Zwischen Pruefung
und Setzen konnten ein UI-Klick und der Timer-Thread beide durchrutschen und
zwei Syncs gleichzeitig starten.
Jetzt per Interlocked.CompareExchange atomar.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Outlook->Starface macht das Starface-Adressbuch zur exakten Kopie von
Outlook: Kontakte, die nur in Starface existieren, werden geloescht.
Starface->Outlook entsprechend umgekehrt (Phase 4).
Sicherheit:
- Loeschphase laeuft nur bei vollstaendig geladener Liste (unvollstaendige
Ladevorgaenge brechen schon vorher ab).
- Ist die Quelle komplett leer (z.B. falscher Ordner), wird die Loeschphase
uebersprungen statt die Zielseite zu leeren.
UI: Profil-Editor zeigt jetzt unter der Sync-Richtung einen Warnhinweis, der
das jeweilige Verhalten erklaert. README/CHANGELOG aktualisiert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Im Both-Modus wurde ein auf einer Seite geloeschter Kontakt bisher auf der
anderen Seite einfach wieder angelegt, statt die Loeschung zu spiegeln.
Jetzt wird anhand der gespeicherten Baseline (LastOutlookHash /
LastStarfaceHash) entschieden:
- Gegenseite seit letztem Sync unveraendert -> es war eine Loeschung ->
auf der anderen Seite ebenfalls loeschen.
- Gegenseite wurde geaendert -> Bearbeitung gewinnt -> neu anlegen
(kein Datenverlust).
In den Ein-Richtungs-Modi bleibt die Quelle fuehrend: eine Loeschung im
Ziel wird aus der Quelle wiederhergestellt (StarfaceToOutlook legt einen
in Outlook geloeschten Kontakt jetzt ebenfalls wieder an statt ein totes
Mapping zu behalten).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Der vorherige Fix war zu konservativ: bei einem in der geladenen Liste
fehlenden Starface-Kontakt wurde das Mapping immer behalten und nichts neu
angelegt - auch wenn der Kontakt in Starface wirklich geloescht war. In
Richtung Outlook->Starface wurden geloeschte Kontakte dadurch nie wieder
angelegt.
Jetzt wird der Kontakt per ID abgefragt:
- existiert noch (anderes Adressbuch) -> Mapping behalten, nichts anlegen
- 404 (wirklich geloescht) -> in Both/OutlookToStarface neu anlegen
(Phase 2), in StarfaceToOutlook Loeschung nach Outlook spiegeln
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Behebt Dubletten auf beiden Seiten und sehr langsame Syncs:
- Getrennte Hash-Baselines pro Seite (LastOutlookHash/LastStarfaceHash)
statt eines gemeinsamen Hashes. Outlook und Starface stellen denselben
Kontakt unterschiedlich dar, wodurch der gemeinsame Hash nie passte und
bei jedem Lauf praktisch jeder Kontakt neu geschrieben wurde.
- Update-Methoden geben den frisch eingelesenen Stand zurueck, damit die
Baseline nach dem Schreiben korrekt gesetzt wird (sauberes Konvergieren).
- Unvollstaendig geladene Starface-Liste bricht jetzt mit Fehler ab
(inkl. Retry) statt still mit Teil-Liste weiterzuarbeiten - das liess
Kontakte faelschlich als geloescht erscheinen und erzeugte Dubletten.
- Fehlender Starface-Kontakt (anderes Adressbuch) behaelt das Mapping,
statt es zu verwerfen und neu anzulegen.
- Lockereres Re-Matching: gleicher E-Mail- oder voller Namens-Treffer
reicht; umformatierte Telefonnummern blockieren ihn nicht mehr.
- Starface-Kontaktdetails werden parallel geladen (8 gleichzeitig).
Bestehende Mappings werden beim ersten Sync automatisch migriert.
CHANGELOG.md hinzugefuegt.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two fixes for delete propagation in Phase 1:
1. When Outlook contact not found by EntryID, try to re-match
by name/email/phone before assuming it was deleted. Outlook
can change EntryIDs on restart or profile changes, causing
the sync to think ALL contacts were deleted.
2. When Starface contact not found in current list, DON'T delete
from Outlook. The contact may belong to a different address
book. Just drop the mapping and let Phase 2/3 re-link it.
These changes make delete propagation much safer and prevent
accidental mass-deletion of contacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Include step-by-step instructions for both standalone PCs and
domain/Terminal Server environments where GPO locks the setting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On domain PCs, HKCU policies are controlled by GPO and the
Trust Center settings are greyed out. Now also writes to HKLM
(requires admin rights) which overrides GPO settings.
Shows orange hint in settings when GPO lock is detected:
"Auf Domaenen-PCs: App einmalig als Admin starten!"
The app tries all 8 combinations: HKCU/HKLM x Policies/direct
x 16.0/15.0. Silently skips paths where permissions are denied.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On Terminal Servers, normal users cannot write to HKCU\Software\
Policies. Now also writes to HKCU\Software\Microsoft\Office\...\
Security which is always writable and also read by Outlook.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Apply to both 16.0 (2016-2024/365) and 15.0 (2013) registry
paths. Costs nothing and ensures it works regardless of which
Office version is installed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Main window wider (830px) so all buttons fit without resizing
- Set ALL Outlook Object Model Guard registry values (not just 3)
- Clean removal: delete entire Security subkey when disabling
- Add hint in settings that Outlook restart is needed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New setting "Outlook-Sicherheitsabfrage automatisch erlauben"
sets registry keys under HKCU\Policies\Microsoft\Office\16.0\
Outlook\Security to auto-approve Object Model Guard prompts.
Applied at app startup and when saving settings. Disabling the
option removes the registry values (back to Outlook default).
Works with all Outlook versions (2016-2024, same registry path).
No admin rights needed (HKCU).
Outlook must be restarted after changing this setting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- "Ueber" menu item in tray context menu opens About dialog
- New user setting "Beim Start automatisch synchronisieren"
syncs all enabled profiles once at app startup
- Sync lock prevents concurrent sync runs (timer, manual,
on-start cannot overlap - second request is skipped)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Include tags in PUT request (Starface may require it)
- Log failed updates with status code and response body
- Also committed: SafeGet for Outlook COM property reading
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dynamic COM with ?? operator can fail silently for properties
that return COM null vs .NET null. Use GetType().InvokeMember()
which reliably reads any Outlook property and catches COM errors
per field instead of crashing the whole contact read.
Fixes Fax and other fields that may not have been read correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fax and HomePhone are now checked for compatibility (filled on
one side, empty on other = different contacts)
- Fax number can serve as a strong match together with Company
- Prevents fax-only contacts from being missed or mismatched
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New matching logic checks ALL identifying fields for compatibility:
- Empty vs filled field = different contacts (not a match)
- Both empty = compatible (ignored for matching)
- Both filled = must be equal
Fields checked: Email, FirstName, LastName, Company, PhoneWork,
PhoneMobile. Requires at least one strong match (email, name,
or phone) plus no conflicting fields.
Example: Two "Max Mustermann" where one has Company="Firma A"
and the other has empty Company are now correctly identified as
different contacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a mapped contact is deleted on one side, delete it on the
other side too (respecting sync direction setting):
- Deleted in Outlook -> delete in Starface (if direction allows)
- Deleted in Starface -> delete in Outlook (if direction allows)
- Deleted on both sides -> just remove the mapping
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>