Rewrite as standalone C# WinForms app

Replace the Office Web Add-in with a native Windows application.
The web add-in required Exchange/M365 for registration which is
not available in all customer environments (standalone Office,
POP/IMAP only).

The new app:
- Uses COM Interop to access Outlook contacts directly
- Communicates with Starface REST API (accepts self-signed certs)
- Runs as System Tray app with optional auto-sync
- Profile-based config stored in %AppData%
- No webserver, no certificates, no Exchange, no M365 needed
- Inno Setup installer for clean MSI-style deployment
- Works with any Outlook version (Classic and New)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 10:26:58 +02:00
parent 8d7ae01ac3
commit 84ba78a1c5
51 changed files with 2205 additions and 10109 deletions
+76
View File
@@ -0,0 +1,76 @@
using System.Drawing;
using System.Windows.Forms;
namespace StarfaceOutlookSync.UI
{
public class AboutForm : Form
{
public AboutForm()
{
Text = "Info";
Size = new Size(360, 280);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9);
BackColor = Color.White;
var lblProduct = new Label
{
Text = "Outlook-SYNC \u2194 Starface",
Left = 0, Top = 24, Width = 340, Height = 30,
TextAlign = ContentAlignment.MiddleCenter,
Font = new Font("Segoe UI", 14, FontStyle.Bold),
ForeColor = Color.FromArgb(0, 120, 212)
};
var lblVersion = new Label
{
Text = "Version 0.0.0.1",
Left = 0, Top = 56, Width = 340, Height = 20,
TextAlign = ContentAlignment.MiddleCenter,
ForeColor = Color.Gray
};
var separator = new Label
{
Left = 40, Top = 86, Width = 260, Height = 1,
BorderStyle = BorderStyle.Fixed3D
};
var lblCompany = new Label
{
Text = "HackerSoft",
Left = 0, Top = 100, Width = 340, Height = 24,
TextAlign = ContentAlignment.MiddleCenter,
Font = new Font("Segoe UI", 11, FontStyle.Bold)
};
var lblSubtitle = new Label
{
Text = "Hacker-Net Telekommunikation",
Left = 0, Top = 124, Width = 340, Height = 20,
TextAlign = ContentAlignment.MiddleCenter,
ForeColor = Color.Gray
};
var lblAddress = new Label
{
Text = "Stefan Hacker\r\nAm Wunderburgpark 5b\r\n26135 Oldenburg",
Left = 0, Top = 152, Width = 340, Height = 56,
TextAlign = ContentAlignment.MiddleCenter,
ForeColor = Color.FromArgb(96, 94, 92)
};
var btnOk = new Button
{
Text = "OK", Left = 130, Top = 212, Width = 80, Height = 28,
DialogResult = DialogResult.OK
};
Controls.AddRange(new Control[] { lblProduct, lblVersion, separator, lblCompany, lblSubtitle, lblAddress, btnOk });
AcceptButton = btnOk;
}
}
}
+328
View File
@@ -0,0 +1,328 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using StarfaceOutlookSync.Models;
using StarfaceOutlookSync.Services;
using Timer = System.Timers.Timer;
namespace StarfaceOutlookSync.UI
{
public class MainForm : Form
{
private readonly ProfileManager _profileManager = new ProfileManager();
private readonly SyncEngine _syncEngine = new SyncEngine();
private NotifyIcon _trayIcon;
private ContextMenuStrip _trayMenu;
private ListView _profileList;
private Button _btnNew, _btnEdit, _btnDelete, _btnSync, _btnInfo;
private StatusStrip _statusBar;
private ToolStripStatusLabel _statusLabel;
private Timer _autoSyncTimer;
public MainForm()
{
InitializeComponent();
SetupTrayIcon();
SetupAutoSync();
RefreshProfileList();
}
private void InitializeComponent()
{
Text = "Starface Kontakt-Sync";
Size = new Size(620, 450);
MinimumSize = new Size(500, 350);
StartPosition = FormStartPosition.CenterScreen;
Font = new Font("Segoe UI", 9);
// Profil-Liste
_profileList = new ListView
{
Dock = DockStyle.Fill,
View = View.Details,
FullRowSelect = true,
GridLines = true,
MultiSelect = false
};
_profileList.Columns.Add("Profil", 150);
_profileList.Columns.Add("Starface", 130);
_profileList.Columns.Add("Outlook-Ordner", 130);
_profileList.Columns.Add("Richtung", 90);
_profileList.Columns.Add("Letzte Sync", 130);
_profileList.DoubleClick += (s, e) => EditProfile();
// Button-Panel
var buttonPanel = new FlowLayoutPanel
{
Dock = DockStyle.Bottom,
Height = 45,
Padding = new Padding(8, 8, 8, 4),
FlowDirection = FlowDirection.LeftToRight
};
_btnNew = new Button { Text = "Neues Profil", Width = 100, Height = 30 };
_btnNew.Click += (s, e) => NewProfile();
_btnEdit = new Button { Text = "Bearbeiten", Width = 90, Height = 30 };
_btnEdit.Click += (s, e) => EditProfile();
_btnDelete = new Button { Text = "Loeschen", Width = 80, Height = 30 };
_btnDelete.Click += (s, e) => DeleteProfile();
_btnSync = new Button { Text = "Jetzt synchronisieren", Width = 150, Height = 30 };
_btnSync.Click += async (s, e) => await SyncSelectedProfile();
_btnInfo = new Button { Text = "Info", Width = 50, Height = 30 };
_btnInfo.Click += (s, e) => ShowAbout();
buttonPanel.Controls.AddRange(new Control[] { _btnNew, _btnEdit, _btnDelete, _btnSync, _btnInfo });
// Statusbar
_statusBar = new StatusStrip();
_statusLabel = new ToolStripStatusLabel("Bereit");
_statusBar.Items.Add(_statusLabel);
Controls.Add(_profileList);
Controls.Add(buttonPanel);
Controls.Add(_statusBar);
}
private void SetupTrayIcon()
{
_trayMenu = new ContextMenuStrip();
_trayMenu.Items.Add("Oeffnen", null, (s, e) => ShowMainWindow());
_trayMenu.Items.Add("-");
// Schnell-Sync fuer jedes Profil
var profiles = _profileManager.GetProfiles();
foreach (var p in profiles.Where(p => p.Enabled))
{
var profile = p;
_trayMenu.Items.Add($"Sync: {profile.Name}", null, async (s, e) =>
{
await RunSync(profile);
});
}
if (profiles.Any(p => p.Enabled))
_trayMenu.Items.Add("-");
_trayMenu.Items.Add("Beenden", null, (s, e) => ExitApplication());
_trayIcon = new NotifyIcon
{
Text = "Starface Kontakt-Sync",
Icon = SystemIcons.Application, // Placeholder, wird durch eigenes Icon ersetzt
ContextMenuStrip = _trayMenu,
Visible = true
};
_trayIcon.DoubleClick += (s, e) => ShowMainWindow();
}
private void SetupAutoSync()
{
_autoSyncTimer = new Timer(60000); // Jede Minute pruefen
_autoSyncTimer.Elapsed += async (s, e) => await CheckAutoSync();
_autoSyncTimer.Start();
}
private async Task CheckAutoSync()
{
var profiles = _profileManager.GetProfiles();
foreach (var profile in profiles.Where(p => p.Enabled && p.AutoSyncIntervalMinutes > 0))
{
if (string.IsNullOrEmpty(profile.LastSync))
{
await RunSync(profile);
continue;
}
if (DateTime.TryParse(profile.LastSync, out var lastSync))
{
if (DateTime.Now - lastSync > TimeSpan.FromMinutes(profile.AutoSyncIntervalMinutes))
{
await RunSync(profile);
}
}
}
}
private void ShowMainWindow()
{
Show();
WindowState = FormWindowState.Normal;
BringToFront();
RefreshProfileList();
}
private void RefreshProfileList()
{
_profileList.Items.Clear();
var profiles = _profileManager.GetProfiles();
foreach (var p in profiles)
{
var dirText = p.SyncDirection == SyncDirection.Both ? "Bidirektional" :
p.SyncDirection == SyncDirection.OutlookToStarface ? "OL -> SF" : "SF -> OL";
var lastSync = string.IsNullOrEmpty(p.LastSync) ? "Noch nie" :
DateTime.TryParse(p.LastSync, out var dt) ? dt.ToString("dd.MM.yyyy HH:mm") : p.LastSync;
var item = new ListViewItem(new[]
{
p.Name,
$"{p.StarfaceConnection.Host} / {p.StarfaceAddressBook.Name}",
p.OutlookFolderName,
dirText,
lastSync
});
item.Tag = p;
if (!p.Enabled)
item.ForeColor = Color.Gray;
_profileList.Items.Add(item);
}
// Tray-Menu aktualisieren
SetupTrayIcon();
}
private void NewProfile()
{
using (var editor = new ProfileEditorForm(null))
{
if (editor.ShowDialog(this) == DialogResult.OK)
RefreshProfileList();
}
}
private void EditProfile()
{
if (_profileList.SelectedItems.Count == 0) return;
var profile = _profileList.SelectedItems[0].Tag as SyncProfile;
if (profile == null) return;
using (var editor = new ProfileEditorForm(profile))
{
if (editor.ShowDialog(this) == DialogResult.OK)
RefreshProfileList();
}
}
private void DeleteProfile()
{
if (_profileList.SelectedItems.Count == 0) return;
var profile = _profileList.SelectedItems[0].Tag as SyncProfile;
if (profile == null) return;
if (MessageBox.Show($"Profil '{profile.Name}' wirklich loeschen?",
"Profil loeschen", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
_profileManager.DeleteProfile(profile.Id);
RefreshProfileList();
}
}
private async Task SyncSelectedProfile()
{
if (_profileList.SelectedItems.Count == 0)
{
MessageBox.Show("Bitte ein Profil auswaehlen.", "Sync",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var profile = _profileList.SelectedItems[0].Tag as SyncProfile;
if (profile == null) return;
using (var syncForm = new SyncProgressForm(profile))
{
syncForm.ShowDialog(this);
}
RefreshProfileList();
}
private async Task RunSync(SyncProfile profile)
{
try
{
SetStatus($"Synchronisiere '{profile.Name}'...");
_trayIcon.ShowBalloonTip(2000, "Starface Sync",
$"Synchronisiere '{profile.Name}'...", ToolTipIcon.Info);
var result = await _syncEngine.SyncProfileAsync(profile);
var msg = $"{profile.Name}: {result.Created} erstellt, {result.Updated} aktualisiert";
if (result.Errors > 0) msg += $", {result.Errors} Fehler";
_trayIcon.ShowBalloonTip(3000, "Starface Sync", msg,
result.Errors > 0 ? ToolTipIcon.Warning : ToolTipIcon.Info);
SetStatus(msg);
}
catch (Exception ex)
{
_trayIcon.ShowBalloonTip(3000, "Starface Sync Fehler",
ex.Message, ToolTipIcon.Error);
SetStatus($"Fehler: {ex.Message}");
}
}
private void SetStatus(string text)
{
if (InvokeRequired)
Invoke(new Action(() => _statusLabel.Text = text));
else
_statusLabel.Text = text;
}
private void ShowAbout()
{
using (var about = new AboutForm())
{
about.ShowDialog(this);
}
}
private void ExitApplication()
{
_autoSyncTimer?.Stop();
_trayIcon.Visible = false;
_trayIcon.Dispose();
Application.Exit();
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide(); // In den Tray minimieren
_trayIcon.ShowBalloonTip(2000, "Starface Kontakt-Sync",
"Laeuft im Hintergrund weiter. Rechtsklick auf das Tray-Icon fuer Optionen.",
ToolTipIcon.Info);
}
else
{
base.OnFormClosing(e);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_autoSyncTimer?.Dispose();
_trayIcon?.Dispose();
_trayMenu?.Dispose();
}
base.Dispose(disposing);
}
}
}
@@ -0,0 +1,323 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using StarfaceOutlookSync.Models;
using StarfaceOutlookSync.Services;
namespace StarfaceOutlookSync.UI
{
public class ProfileEditorForm : Form
{
private readonly ProfileManager _pm = new ProfileManager();
private readonly SyncProfile _existingProfile;
private readonly bool _isNew;
// Controls
private TextBox _txtName, _txtHost, _txtPort, _txtLoginId, _txtPassword;
private CheckBox _chkSsl, _chkEnabled;
private ComboBox _cmbAddressBook, _cmbOutlookFolder, _cmbDirection;
private NumericUpDown _numAutoSync;
private Button _btnTest, _btnLoadBooks, _btnSave, _btnCancel;
private Label _lblTestResult;
private List<StarfaceAddressBook> _addressBooks = new List<StarfaceAddressBook>();
public ProfileEditorForm(SyncProfile profile)
{
_existingProfile = profile;
_isNew = profile == null;
InitializeComponent();
LoadData();
}
private void InitializeComponent()
{
Text = _isNew ? "Neues Sync-Profil" : "Profil bearbeiten";
Size = new Size(480, 620);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9);
var panel = new Panel { Dock = DockStyle.Fill, AutoScroll = true, Padding = new Padding(16) };
int y = 8;
// Profilname
panel.Controls.Add(MakeLabel("Profilname:", 12, y)); y += 22;
_txtName = new TextBox { Left = 12, Top = y, Width = 420 }; panel.Controls.Add(_txtName); y += 32;
// === Starface ===
panel.Controls.Add(MakeSectionLabel("Starface-Verbindung", 12, y)); y += 26;
panel.Controls.Add(MakeLabel("Host / IP-Adresse:", 12, y)); y += 22;
_txtHost = new TextBox { Left = 12, Top = y, Width = 320 };
_txtPort = new TextBox { Left = 340, Top = y, Width = 60, Text = "443" };
panel.Controls.Add(_txtHost);
panel.Controls.Add(MakeLabel("Port:", 340, y - 22));
panel.Controls.Add(_txtPort); y += 32;
_chkSsl = new CheckBox { Text = "HTTPS verwenden", Left = 12, Top = y, Checked = true, AutoSize = true };
panel.Controls.Add(_chkSsl); y += 28;
panel.Controls.Add(MakeLabel("Login-ID:", 12, y)); y += 22;
_txtLoginId = new TextBox { Left = 12, Top = y, Width = 200 }; panel.Controls.Add(_txtLoginId); y += 32;
panel.Controls.Add(MakeLabel("Kennwort:", 12, y)); y += 22;
_txtPassword = new TextBox { Left = 12, Top = y, Width = 200, UseSystemPasswordChar = true };
panel.Controls.Add(_txtPassword); y += 32;
_btnTest = new Button { Text = "Verbindung testen", Left = 12, Top = y, Width = 130, Height = 28 };
_btnTest.Click += async (s, e) => await TestConnection();
_btnLoadBooks = new Button { Text = "Adressbuecher laden", Left = 150, Top = y, Width = 140, Height = 28 };
_btnLoadBooks.Click += async (s, e) => await LoadAddressBooks();
panel.Controls.Add(_btnTest);
panel.Controls.Add(_btnLoadBooks); y += 32;
_lblTestResult = new Label { Left = 12, Top = y, Width = 420, Height = 20, ForeColor = Color.Gray };
panel.Controls.Add(_lblTestResult); y += 26;
panel.Controls.Add(MakeLabel("Starface-Adressbuch:", 12, y)); y += 22;
_cmbAddressBook = new ComboBox { Left = 12, Top = y, Width = 420, DropDownStyle = ComboBoxStyle.DropDownList };
panel.Controls.Add(_cmbAddressBook); y += 32;
// === Outlook ===
panel.Controls.Add(MakeSectionLabel("Outlook-Einstellungen", 12, y)); y += 26;
panel.Controls.Add(MakeLabel("Kontakte-Ordner:", 12, y)); y += 22;
_cmbOutlookFolder = new ComboBox { Left = 12, Top = y, Width = 420, DropDownStyle = ComboBoxStyle.DropDownList };
panel.Controls.Add(_cmbOutlookFolder); y += 32;
panel.Controls.Add(MakeLabel("Sync-Richtung:", 12, y)); y += 22;
_cmbDirection = new ComboBox { Left = 12, Top = y, Width = 250, DropDownStyle = ComboBoxStyle.DropDownList };
_cmbDirection.Items.AddRange(new object[] { "Bidirektional", "Outlook -> Starface", "Starface -> Outlook" });
_cmbDirection.SelectedIndex = 0;
panel.Controls.Add(_cmbDirection); y += 32;
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 };
panel.Controls.Add(_numAutoSync); y += 32;
_chkEnabled = new CheckBox { Text = "Profil aktiviert", Left = 12, Top = y, Checked = true, AutoSize = true };
panel.Controls.Add(_chkEnabled); y += 36;
// Buttons
_btnSave = new Button { Text = "Speichern", Left = 12, Top = y, Width = 100, Height = 30, DialogResult = DialogResult.None };
_btnSave.Click += (s, e) => SaveProfile();
_btnCancel = new Button { Text = "Abbrechen", Left = 120, Top = y, Width = 100, Height = 30, DialogResult = DialogResult.Cancel };
panel.Controls.Add(_btnSave);
panel.Controls.Add(_btnCancel);
Controls.Add(panel);
AcceptButton = _btnSave;
CancelButton = _btnCancel;
}
private Label MakeLabel(string text, int x, int y)
{
return new Label { Text = text, Left = x, Top = y, AutoSize = true };
}
private Label MakeSectionLabel(string text, int x, int y)
{
return new Label { Text = text, Left = x, Top = y, AutoSize = true, Font = new Font("Segoe UI", 10, FontStyle.Bold) };
}
private void LoadData()
{
// Outlook-Ordner laden
try
{
using (var outlook = new OutlookContactsService())
{
var folders = outlook.GetContactFolderPaths();
_cmbOutlookFolder.Items.Clear();
foreach (var f in folders)
_cmbOutlookFolder.Items.Add(f);
if (folders.Count > 0)
_cmbOutlookFolder.SelectedIndex = 0;
}
}
catch
{
_cmbOutlookFolder.Items.Add("\\\\Kontakte");
_cmbOutlookFolder.SelectedIndex = 0;
}
// Bestehende Werte laden
if (_existingProfile != null)
{
_txtName.Text = _existingProfile.Name;
_txtHost.Text = _existingProfile.StarfaceConnection.Host;
_txtPort.Text = _existingProfile.StarfaceConnection.Port.ToString();
_chkSsl.Checked = _existingProfile.StarfaceConnection.UseSsl;
_txtLoginId.Text = _existingProfile.StarfaceConnection.LoginId;
_txtPassword.Text = _existingProfile.StarfaceConnection.Password;
_chkEnabled.Checked = _existingProfile.Enabled;
_numAutoSync.Value = _existingProfile.AutoSyncIntervalMinutes;
_cmbDirection.SelectedIndex = (int)_existingProfile.SyncDirection;
// Outlook-Ordner auswaehlen
for (int i = 0; i < _cmbOutlookFolder.Items.Count; i++)
{
if (_cmbOutlookFolder.Items[i].ToString() == _existingProfile.OutlookFolderPath)
{
_cmbOutlookFolder.SelectedIndex = i;
break;
}
}
// Adressbuch
if (_existingProfile.StarfaceAddressBook != null)
{
_addressBooks.Add(_existingProfile.StarfaceAddressBook);
_cmbAddressBook.Items.Add(_existingProfile.StarfaceAddressBook.Name);
_cmbAddressBook.SelectedIndex = 0;
}
}
}
private StarfaceConnection GetConnection()
{
return new StarfaceConnection
{
Host = _txtHost.Text.Trim(),
Port = int.TryParse(_txtPort.Text, out var p) ? p : 443,
UseSsl = _chkSsl.Checked,
LoginId = _txtLoginId.Text.Trim(),
Password = _txtPassword.Text
};
}
private async Task TestConnection()
{
_lblTestResult.Text = "Teste Verbindung...";
_lblTestResult.ForeColor = Color.Gray;
_btnTest.Enabled = false;
try
{
using (var client = new StarfaceApiClient(GetConnection()))
{
var ok = await client.LoginAsync();
if (ok)
{
_lblTestResult.Text = "Verbindung erfolgreich!";
_lblTestResult.ForeColor = Color.Green;
await client.LogoutAsync();
}
else
{
_lblTestResult.Text = "Login fehlgeschlagen. Zugangsdaten pruefen.";
_lblTestResult.ForeColor = Color.Red;
}
}
}
catch (Exception ex)
{
_lblTestResult.Text = $"Fehler: {ex.Message}";
_lblTestResult.ForeColor = Color.Red;
}
_btnTest.Enabled = true;
}
private async Task LoadAddressBooks()
{
_lblTestResult.Text = "Lade Adressbuecher...";
_lblTestResult.ForeColor = Color.Gray;
_btnLoadBooks.Enabled = false;
try
{
using (var client = new StarfaceApiClient(GetConnection()))
{
var ok = await client.LoginAsync();
if (!ok)
{
_lblTestResult.Text = "Login fehlgeschlagen.";
_lblTestResult.ForeColor = Color.Red;
_btnLoadBooks.Enabled = true;
return;
}
_addressBooks = await client.GetAddressBooksAsync();
await client.LogoutAsync();
_cmbAddressBook.Items.Clear();
foreach (var book in _addressBooks)
_cmbAddressBook.Items.Add(book.Name);
if (_addressBooks.Count > 0)
{
_cmbAddressBook.SelectedIndex = 0;
_lblTestResult.Text = $"{_addressBooks.Count} Adressbuch/buecher gefunden.";
_lblTestResult.ForeColor = Color.Green;
}
else
{
_lblTestResult.Text = "Keine Adressbuecher gefunden.";
_lblTestResult.ForeColor = Color.Orange;
}
}
}
catch (Exception ex)
{
_lblTestResult.Text = $"Fehler: {ex.Message}";
_lblTestResult.ForeColor = Color.Red;
}
_btnLoadBooks.Enabled = true;
}
private void SaveProfile()
{
if (string.IsNullOrWhiteSpace(_txtName.Text))
{
MessageBox.Show("Bitte Profilnamen eingeben.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (string.IsNullOrWhiteSpace(_txtHost.Text))
{
MessageBox.Show("Bitte Starface-Host eingeben.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (_cmbAddressBook.SelectedIndex < 0 || _addressBooks.Count == 0)
{
MessageBox.Show("Bitte ein Starface-Adressbuch auswaehlen.\nZuerst 'Adressbuecher laden' klicken.",
"Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var selectedFolder = _cmbOutlookFolder.SelectedItem?.ToString() ?? "";
var folderName = selectedFolder;
if (folderName.Contains("\\"))
folderName = folderName.Substring(folderName.LastIndexOf('\\') + 1);
var profile = new SyncProfile
{
Id = _existingProfile?.Id ?? _pm.GenerateId(),
Name = _txtName.Text.Trim(),
StarfaceConnection = GetConnection(),
StarfaceAddressBook = _addressBooks[_cmbAddressBook.SelectedIndex],
OutlookFolderPath = selectedFolder,
OutlookFolderName = folderName,
SyncDirection = (SyncDirection)_cmbDirection.SelectedIndex,
Enabled = _chkEnabled.Checked,
AutoSyncIntervalMinutes = (int)_numAutoSync.Value,
LastSync = _existingProfile?.LastSync ?? ""
};
if (_isNew)
_pm.AddProfile(profile);
else
_pm.UpdateProfile(profile);
DialogResult = DialogResult.OK;
Close();
}
}
}
@@ -0,0 +1,127 @@
using System;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using StarfaceOutlookSync.Models;
using StarfaceOutlookSync.Services;
namespace StarfaceOutlookSync.UI
{
public class SyncProgressForm : Form
{
private readonly SyncProfile _profile;
private readonly SyncEngine _engine = new SyncEngine();
private TextBox _txtLog;
private ProgressBar _progressBar;
private Button _btnClose, _btnStart;
private Label _lblResult;
public SyncProgressForm(SyncProfile profile)
{
_profile = profile;
InitializeComponent();
}
private void InitializeComponent()
{
Text = $"Synchronisation - {_profile.Name}";
Size = new Size(500, 400);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
StartPosition = FormStartPosition.CenterParent;
Font = new Font("Segoe UI", 9);
var infoLabel = new Label
{
Text = $"{_profile.StarfaceConnection.Host} ({_profile.StarfaceAddressBook.Name}) <-> {_profile.OutlookFolderName}",
Left = 12, Top = 12, Width = 460, AutoSize = false, Height = 20
};
_progressBar = new ProgressBar
{
Left = 12, Top = 38, Width = 460, Height = 22,
Style = ProgressBarStyle.Marquee
};
_txtLog = new TextBox
{
Left = 12, Top = 68, Width = 460, Height = 200,
Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Vertical,
BackColor = Color.FromArgb(30, 30, 30), ForeColor = Color.FromArgb(212, 212, 212),
Font = new Font("Consolas", 9)
};
_lblResult = new Label
{
Left = 12, Top = 276, Width = 460, Height = 40,
AutoSize = false, ForeColor = Color.Gray
};
_btnStart = new Button
{
Text = "Synchronisation starten", Left = 12, Top = 322, Width = 180, Height = 30
};
_btnStart.Click += async (s, e) => await RunSync();
_btnClose = new Button
{
Text = "Schliessen", Left = 380, Top = 322, Width = 90, Height = 30,
DialogResult = DialogResult.Cancel
};
Controls.AddRange(new Control[] { infoLabel, _progressBar, _txtLog, _lblResult, _btnStart, _btnClose });
CancelButton = _btnClose;
}
private void AppendLog(string message)
{
if (InvokeRequired)
{
Invoke(new Action(() => AppendLog(message)));
return;
}
_txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\r\n");
}
private async Task RunSync()
{
_btnStart.Enabled = false;
_btnClose.Enabled = false;
_progressBar.Style = ProgressBarStyle.Marquee;
_lblResult.Text = "";
_engine.OnProgress += AppendLog;
try
{
var result = await Task.Run(() => _engine.SyncProfileAsync(_profile));
_progressBar.Style = ProgressBarStyle.Blocks;
_progressBar.Value = 100;
var resultText = $"Erstellt: {result.Created} | Aktualisiert: {result.Updated} | Fehler: {result.Errors}";
_lblResult.Text = resultText;
_lblResult.ForeColor = result.Errors > 0 ? Color.OrangeRed : Color.Green;
if (result.ErrorMessages.Count > 0)
{
AppendLog("--- Fehler ---");
foreach (var err in result.ErrorMessages)
AppendLog(err);
}
}
catch (Exception ex)
{
_lblResult.Text = $"Fehler: {ex.Message}";
_lblResult.ForeColor = Color.Red;
AppendLog($"FEHLER: {ex.Message}");
}
_engine.OnProgress -= AppendLog;
_btnStart.Enabled = true;
_btnStart.Text = "Erneut synchronisieren";
_btnClose.Enabled = true;
}
}
}