Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1987e25c37 | |||
| b5ad59ff9d | |||
| 1e9ff63833 |
@@ -38,6 +38,20 @@ Versionsschema ist `x.x.x.x` (siehe `release.sh`).
|
|||||||
|
|
||||||
### Geaendert
|
### Geaendert
|
||||||
|
|
||||||
|
- **Ein-Richtungs-Modi sind jetzt echtes "Ersetzen".** Outlook->Starface macht
|
||||||
|
das Starface-Adressbuch zu einer exakten Kopie von Outlook: Kontakte, die nur
|
||||||
|
in Starface existieren (kein Pendant in Outlook), werden geloescht.
|
||||||
|
Starface->Outlook entsprechend umgekehrt. Schutz: Ist die Quelle komplett
|
||||||
|
leer (z.B. falscher Ordner gewaehlt), wird die Loeschphase uebersprungen
|
||||||
|
statt die Zielseite zu leeren. Mass-Loeschungen finden nur bei vollstaendig
|
||||||
|
geladener Liste statt (unvollstaendige Ladevorgaenge brechen vorher ab).
|
||||||
|
- **Bidirektionale Loeschungen** werden jetzt erkannt. Wird ein Kontakt auf
|
||||||
|
einer Seite geloescht und ist die andere Seite seit dem letzten Sync
|
||||||
|
unveraendert (Abgleich ueber die gespeicherte Baseline), wird die Loeschung
|
||||||
|
auf die andere Seite gespiegelt – statt den Kontakt wieder anzulegen. Wurde
|
||||||
|
die andere Seite zwischenzeitlich bearbeitet, gewinnt die Bearbeitung und der
|
||||||
|
Kontakt wird neu angelegt (kein Datenverlust). In den Ein-Richtungs-Modi
|
||||||
|
bleibt die jeweilige Quelle fuehrend (Loeschung im Ziel wird wiederhergestellt).
|
||||||
- Starface-Kontaktdetails werden beim Laden parallel abgerufen (8 gleichzeitig)
|
- Starface-Kontaktdetails werden beim Laden parallel abgerufen (8 gleichzeitig)
|
||||||
statt einzeln nacheinander – deutlich schneller bei grossen Adressbuechern.
|
statt einzeln nacheinander – deutlich schneller bei grossen Adressbuechern.
|
||||||
- `UpdateContact` (Outlook) und `UpdateContactAsync` (Starface) geben jetzt den
|
- `UpdateContact` (Outlook) und `UpdateContactAsync` (Starface) geben jetzt den
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ Windows-Anwendung zur bidirektionalen Synchronisation von Kontakten zwischen Mic
|
|||||||
- **Profil-System** zum Verwalten mehrerer Sync-Konfigurationen (verschiedene Adressbuecher oder Anlagen)
|
- **Profil-System** zum Verwalten mehrerer Sync-Konfigurationen (verschiedene Adressbuecher oder Anlagen)
|
||||||
- **Starface-Adressbuecher**: Zentrales Adressbuch, persoenliches Adressbuch und Tag-basierte Adressbuecher
|
- **Starface-Adressbuecher**: Zentrales Adressbuch, persoenliches Adressbuch und Tag-basierte Adressbuecher
|
||||||
- **Outlook-Kontaktordner**: Frei waehlbarer Kontaktordner als Sync-Ziel
|
- **Outlook-Kontaktordner**: Frei waehlbarer Kontaktordner als Sync-Ziel
|
||||||
- **Sync-Richtung** konfigurierbar: Outlook -> Starface, Starface -> Outlook oder bidirektional
|
- **Sync-Richtung** konfigurierbar:
|
||||||
|
- *Bidirektional*: Aenderungen werden in beide Richtungen abgeglichen (inkl. Loeschungen)
|
||||||
|
- *Outlook -> Starface*: Das Starface-Adressbuch wird zur exakten Kopie von Outlook (nur in Starface vorhandene Kontakte werden geloescht)
|
||||||
|
- *Starface -> Outlook*: Der Outlook-Ordner wird zur exakten Kopie von Starface (nur in Outlook vorhandene Kontakte werden geloescht)
|
||||||
- **Intelligentes Matching**: Kontakte werden anhand von E-Mail-Adresse oder Name abgeglichen
|
- **Intelligentes Matching**: Kontakte werden anhand von E-Mail-Adresse oder Name abgeglichen
|
||||||
- **Aenderungserkennung**: Nur geaenderte Kontakte werden uebertragen (Hash-basiert)
|
- **Aenderungserkennung**: Nur geaenderte Kontakte werden uebertragen (Hash-basiert)
|
||||||
- **Auto-Sync**: Optionaler automatischer Sync in konfigurierbarem Intervall
|
- **Auto-Sync**: Optionaler automatischer Sync in konfigurierbarem Intervall
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
; Erfordert Inno Setup 6.x (https://jrsoftware.org/isinfo.php)
|
; Erfordert Inno Setup 6.x (https://jrsoftware.org/isinfo.php)
|
||||||
|
|
||||||
#define MyAppName "Starface Outlook Sync"
|
#define MyAppName "Starface Outlook Sync"
|
||||||
#define MyAppVersion "0.0.0.25"
|
#define MyAppVersion "0.0.0.26"
|
||||||
#define MyAppPublisher "HackerSoft - Hacker-Net Telekommunikation"
|
#define MyAppPublisher "HackerSoft - Hacker-Net Telekommunikation"
|
||||||
#define MyAppURL "https://www.hacker-net.de"
|
#define MyAppURL "https://www.hacker-net.de"
|
||||||
#define MyAppExeName "StarfaceOutlookSync.exe"
|
#define MyAppExeName "StarfaceOutlookSync.exe"
|
||||||
|
|||||||
@@ -163,19 +163,46 @@ namespace StarfaceOutlookSync.Services
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wirklich geloescht -> in Starface auch loeschen
|
// Wirklich in Outlook geloescht.
|
||||||
if (profile.SyncDirection == SyncDirection.Both || profile.SyncDirection == SyncDirection.OutlookToStarface)
|
if (profile.SyncDirection == SyncDirection.OutlookToStarface)
|
||||||
{
|
{
|
||||||
|
// Outlook ist fuehrend -> Loeschung nach Starface spiegeln.
|
||||||
if (await starface.DeleteContactAsync(mapping.StarfaceId))
|
if (await starface.DeleteContactAsync(mapping.StarfaceId))
|
||||||
{
|
{
|
||||||
result.Updated++;
|
result.Updated++;
|
||||||
Log($" Geloescht (OL->SF): {sc.DisplayName}");
|
Log($" Geloescht (OL->SF): {sc.DisplayName}");
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (profile.SyncDirection == SyncDirection.Both)
|
||||||
{
|
{
|
||||||
newMappings.Add(mapping);
|
// Bidirektional: anhand der Baseline pruefen, ob die
|
||||||
|
// Starface-Seite seit dem letzten Sync unveraendert ist.
|
||||||
|
bool sfUnchanged = !string.IsNullOrEmpty(mapping.LastStarfaceHash)
|
||||||
|
&& sc.GetHash() == mapping.LastStarfaceHash;
|
||||||
|
if (sfUnchanged)
|
||||||
|
{
|
||||||
|
// Unveraendert + in Outlook geloescht -> Loeschung gilt
|
||||||
|
// -> auch aus Starface entfernen.
|
||||||
|
if (await starface.DeleteContactAsync(mapping.StarfaceId))
|
||||||
|
{
|
||||||
|
result.Updated++;
|
||||||
|
Log($" Geloescht (OL->SF): {sc.DisplayName}");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// In Outlook geloescht, aber in Starface geaendert ->
|
||||||
|
// Bearbeitung gewinnt, in Outlook neu anlegen (Phase 3).
|
||||||
|
Log($" In Outlook geloescht, in Starface geaendert -> neu anlegen: {sc.DisplayName}");
|
||||||
|
processedStarfaceIds.Remove(sc.StarfaceId);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StarfaceToOutlook: Starface ist alleinige Quelle -> in Outlook
|
||||||
|
// neu anlegen (Loeschung im Ziel zaehlt nicht).
|
||||||
|
Log($" Outlook-Kontakt geloescht, wird neu angelegt: {sc.DisplayName}");
|
||||||
|
processedStarfaceIds.Remove(sc.StarfaceId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,21 +224,10 @@ namespace StarfaceOutlookSync.Services
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wirklich geloescht.
|
// Wirklich in Starface geloescht.
|
||||||
if (profile.SyncDirection == SyncDirection.Both
|
if (profile.SyncDirection == SyncDirection.StarfaceToOutlook)
|
||||||
|| profile.SyncDirection == SyncDirection.OutlookToStarface)
|
|
||||||
{
|
{
|
||||||
// Outlook ist (mit-)fuehrend -> Kontakt in Starface neu
|
// Starface ist fuehrend -> Loeschung nach Outlook spiegeln.
|
||||||
// anlegen. Mapping verwerfen und oc wieder freigeben,
|
|
||||||
// damit Phase 2 ihn anlegt (inkl. Duplikat-Pruefung).
|
|
||||||
Log($" Starface-Kontakt geloescht, wird neu angelegt: {oc.DisplayName}");
|
|
||||||
processedOutlookIds.Remove(oc.OutlookEntryId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// StarfaceToOutlook: Starface ist fuehrend, Loeschung
|
|
||||||
// nach Outlook spiegeln.
|
|
||||||
if (_outlookService.DeleteContact(oc.OutlookEntryId))
|
if (_outlookService.DeleteContact(oc.OutlookEntryId))
|
||||||
{
|
{
|
||||||
result.Updated++;
|
result.Updated++;
|
||||||
@@ -219,6 +235,36 @@ namespace StarfaceOutlookSync.Services
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (profile.SyncDirection == SyncDirection.Both)
|
||||||
|
{
|
||||||
|
// Bidirektional: anhand der Baseline entscheiden, ob der
|
||||||
|
// Outlook-Kontakt seit dem letzten Sync unveraendert ist.
|
||||||
|
bool olUnchanged = !string.IsNullOrEmpty(mapping.LastOutlookHash)
|
||||||
|
&& oc.GetHash() == mapping.LastOutlookHash;
|
||||||
|
if (olUnchanged)
|
||||||
|
{
|
||||||
|
// Unveraendert + in Starface geloescht -> Loeschung gilt
|
||||||
|
// -> aus Outlook entfernen.
|
||||||
|
if (_outlookService.DeleteContact(oc.OutlookEntryId))
|
||||||
|
{
|
||||||
|
result.Updated++;
|
||||||
|
Log($" Geloescht (SF->OL): {oc.DisplayName}");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// In Starface geloescht, aber in Outlook geaendert ->
|
||||||
|
// Bearbeitung gewinnt, in Starface neu anlegen.
|
||||||
|
Log($" In Starface geloescht, in Outlook geaendert -> neu anlegen: {oc.DisplayName}");
|
||||||
|
processedOutlookIds.Remove(oc.OutlookEntryId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutlookToStarface: Outlook ist alleinige Quelle -> Kontakt
|
||||||
|
// in Starface neu anlegen (Loeschung im Ziel zaehlt nicht).
|
||||||
|
Log($" Starface-Kontakt geloescht, wird neu angelegt: {oc.DisplayName}");
|
||||||
|
processedOutlookIds.Remove(oc.OutlookEntryId);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oc != null && sc != null)
|
if (oc != null && sc != null)
|
||||||
@@ -448,6 +494,77 @@ namespace StarfaceOutlookSync.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Phase 4: Ersetzen-Modus - Zielseite an Quelle angleichen
|
||||||
|
// ============================================
|
||||||
|
// In den Ein-Richtungs-Modi soll die Zielseite eine exakte Kopie
|
||||||
|
// der Quelle werden. Kontakte, die nur auf der Zielseite existieren
|
||||||
|
// (kein Mapping, kein Treffer in der Quelle), werden geloescht.
|
||||||
|
// Sicher, weil unvollstaendige Ladevorgaenge vorher abbrechen.
|
||||||
|
if (profile.SyncDirection == SyncDirection.OutlookToStarface)
|
||||||
|
{
|
||||||
|
// Schutz gegen versehentliches Leerraeumen (z.B. falscher Ordner).
|
||||||
|
if (outlookContacts.Count == 0)
|
||||||
|
{
|
||||||
|
Log("Ersetzen-Modus uebersprungen: Outlook-Ordner ist leer (Schutz vor versehentlichem Leeren).");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var leftover = starfaceContacts
|
||||||
|
.Where(c => !string.IsNullOrEmpty(c.StarfaceId) && !processedStarfaceIds.Contains(c.StarfaceId))
|
||||||
|
.ToList();
|
||||||
|
if (leftover.Count > 0)
|
||||||
|
Log($"Ersetzen-Modus: entferne {leftover.Count} Kontakt(e) aus Starface, die nicht in Outlook existieren");
|
||||||
|
foreach (var sc in leftover)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (await starface.DeleteContactAsync(sc.StarfaceId))
|
||||||
|
{
|
||||||
|
result.Updated++;
|
||||||
|
Log($" Geloescht (nur in Starface): {sc.DisplayName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Errors++;
|
||||||
|
result.ErrorMessages.Add($"Loeschen SF {sc.DisplayName}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (profile.SyncDirection == SyncDirection.StarfaceToOutlook)
|
||||||
|
{
|
||||||
|
if (starfaceContacts.Count == 0)
|
||||||
|
{
|
||||||
|
Log("Ersetzen-Modus uebersprungen: Starface-Adressbuch ist leer (Schutz vor versehentlichem Leeren).");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var leftover = outlookContacts
|
||||||
|
.Where(c => !string.IsNullOrEmpty(c.OutlookEntryId) && !processedOutlookIds.Contains(c.OutlookEntryId))
|
||||||
|
.ToList();
|
||||||
|
if (leftover.Count > 0)
|
||||||
|
Log($"Ersetzen-Modus: entferne {leftover.Count} Kontakt(e) aus Outlook, die nicht in Starface existieren");
|
||||||
|
foreach (var oc in leftover)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_outlookService.DeleteContact(oc.OutlookEntryId))
|
||||||
|
{
|
||||||
|
result.Updated++;
|
||||||
|
Log($" Geloescht (nur in Outlook): {oc.DisplayName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Errors++;
|
||||||
|
result.ErrorMessages.Add($"Loeschen OL {oc.DisplayName}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mappings speichern
|
// Mappings speichern
|
||||||
_profileManager.SaveMappings(profile.Id, newMappings);
|
_profileManager.SaveMappings(profile.Id, newMappings);
|
||||||
_profileManager.UpdateLastSync(profile.Id);
|
_profileManager.UpdateLastSync(profile.Id);
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<AssemblyTitle>Starface Outlook Sync</AssemblyTitle>
|
<AssemblyTitle>Starface Outlook Sync</AssemblyTitle>
|
||||||
<Company>HackerSoft - Hacker-Net Telekommunikation</Company>
|
<Company>HackerSoft - Hacker-Net Telekommunikation</Company>
|
||||||
<Product>Starface Outlook Sync</Product>
|
<Product>Starface Outlook Sync</Product>
|
||||||
<Version>0.0.0.25</Version>
|
<Version>0.0.0.26</Version>
|
||||||
<AssemblyVersion>0.0.0.25</AssemblyVersion>
|
<AssemblyVersion>0.0.0.26</AssemblyVersion>
|
||||||
<FileVersion>0.0.0.25</FileVersion>
|
<FileVersion>0.0.0.26</FileVersion>
|
||||||
<Description>Synchronisiert Outlook-Kontakte mit Starface Telefonanlage</Description>
|
<Description>Synchronisiert Outlook-Kontakte mit Starface Telefonanlage</Description>
|
||||||
<Copyright>Stefan Hacker - HackerSoft</Copyright>
|
<Copyright>Stefan Hacker - HackerSoft</Copyright>
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace StarfaceOutlookSync.UI
|
|||||||
|
|
||||||
var lblVersion = new Label
|
var lblVersion = new Label
|
||||||
{
|
{
|
||||||
Text = "Version 0.0.0.25",
|
Text = "Version 0.0.0.26",
|
||||||
Left = 0, Top = 56, Width = 340, Height = 20,
|
Left = 0, Top = 56, Width = 340, Height = 20,
|
||||||
TextAlign = ContentAlignment.MiddleCenter,
|
TextAlign = ContentAlignment.MiddleCenter,
|
||||||
ForeColor = Color.Gray
|
ForeColor = Color.Gray
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace StarfaceOutlookSync.UI
|
|||||||
private NumericUpDown _numAutoSync;
|
private NumericUpDown _numAutoSync;
|
||||||
private Button _btnTest, _btnLoadBooks, _btnSave, _btnCancel;
|
private Button _btnTest, _btnLoadBooks, _btnSave, _btnCancel;
|
||||||
private Label _lblTestResult;
|
private Label _lblTestResult;
|
||||||
|
private Label _lblDirectionHint;
|
||||||
|
|
||||||
private List<StarfaceAddressBook> _addressBooks = new List<StarfaceAddressBook>();
|
private List<StarfaceAddressBook> _addressBooks = new List<StarfaceAddressBook>();
|
||||||
private List<string> _outlookFolderPaths = new List<string>();
|
private List<string> _outlookFolderPaths = new List<string>();
|
||||||
@@ -101,7 +102,18 @@ namespace StarfaceOutlookSync.UI
|
|||||||
_cmbDirection = new ComboBox { Left = 12, Top = y, Width = 250, DropDownStyle = ComboBoxStyle.DropDownList };
|
_cmbDirection = new ComboBox { Left = 12, Top = y, Width = 250, DropDownStyle = ComboBoxStyle.DropDownList };
|
||||||
_cmbDirection.Items.AddRange(new object[] { "Bidirektional", "Outlook -> Starface", "Starface -> Outlook" });
|
_cmbDirection.Items.AddRange(new object[] { "Bidirektional", "Outlook -> Starface", "Starface -> Outlook" });
|
||||||
_cmbDirection.SelectedIndex = 0;
|
_cmbDirection.SelectedIndex = 0;
|
||||||
panel.Controls.Add(_cmbDirection); y += 32;
|
panel.Controls.Add(_cmbDirection); y += 28;
|
||||||
|
|
||||||
|
_lblDirectionHint = new Label
|
||||||
|
{
|
||||||
|
Left = 12, Top = y, Width = 360, Height = 32,
|
||||||
|
ForeColor = Color.FromArgb(150, 80, 0),
|
||||||
|
Font = new Font("Segoe UI", 8.25f)
|
||||||
|
};
|
||||||
|
_cmbDirection.SelectedIndexChanged += (s, e) => UpdateDirectionHint();
|
||||||
|
panel.Controls.Add(_lblDirectionHint);
|
||||||
|
UpdateDirectionHint();
|
||||||
|
y += 36;
|
||||||
|
|
||||||
panel.Controls.Add(MakeLabel("Auto-Sync Intervall (Minuten, 0 = manuell):", 12, y)); y += 22;
|
panel.Controls.Add(MakeLabel("Auto-Sync Intervall (Minuten, 0 = manuell):", 12, y)); y += 22;
|
||||||
_numAutoSync = new NumericUpDown { Left = 12, Top = y, Width = 80, Minimum = 0, Maximum = 1440, Value = 0 };
|
_numAutoSync = new NumericUpDown { Left = 12, Top = y, Width = 80, Minimum = 0, Maximum = 1440, Value = 0 };
|
||||||
@@ -122,6 +134,22 @@ namespace StarfaceOutlookSync.UI
|
|||||||
CancelButton = _btnCancel;
|
CancelButton = _btnCancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateDirectionHint()
|
||||||
|
{
|
||||||
|
switch (_cmbDirection.SelectedIndex)
|
||||||
|
{
|
||||||
|
case 1: // Outlook -> Starface
|
||||||
|
_lblDirectionHint.Text = "Achtung: Das Starface-Adressbuch wird zur exakten Kopie von Outlook.\nKontakte, die nur in Starface existieren, werden geloescht.";
|
||||||
|
break;
|
||||||
|
case 2: // Starface -> Outlook
|
||||||
|
_lblDirectionHint.Text = "Achtung: Der Outlook-Ordner wird zur exakten Kopie von Starface.\nKontakte, die nur in Outlook existieren, werden geloescht.";
|
||||||
|
break;
|
||||||
|
default: // Bidirektional
|
||||||
|
_lblDirectionHint.Text = "Aenderungen werden in beide Richtungen abgeglichen.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Label MakeLabel(string text, int x, int y)
|
private Label MakeLabel(string text, int x, int y)
|
||||||
{
|
{
|
||||||
return new Label { Text = text, Left = x, Top = y, AutoSize = true };
|
return new Label { Text = text, Left = x, Top = y, AutoSize = true };
|
||||||
|
|||||||
Reference in New Issue
Block a user