diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1ffb4c0a..06ee6a7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,12 @@ Versionsschema ist `x.x.x.x` (siehe `release.sh`).
### Hinzugefuegt
+- **Protokoll (Logdatei) + "Protokoll"-Button.** Syncs, Ergebnisse, Konflikte
+ (lokal und von anderen Arbeitsplaetzen) und Fehler werden dauerhaft in
+ `%AppData%\StarfaceOutlookSync\sync.log` festgehalten - so ist auch nach dem
+ Verschwinden einer Tray-Meldung nachvollziehbar, wer/was/wann. Im Hauptfenster
+ oeffnet der Button "Protokoll" einen Betrachter (Aktualisieren/Leeren/Ordner
+ oeffnen). Die Datei rotiert bei 2 MB.
- **Clientuebergreifende Konflikt-Hinweise (Mehrplatz).** Wird ein echter
Feld-Konflikt aufgeloest, legt der Client eine Notiz im gemeinsamen Verzeichnis
(`conflicts/`) ab - zugeordnet nach Kontakt (StarfaceId). Ein anderer
diff --git a/README.md b/README.md
index 81fd0cf0..f843f33f 100644
--- a/README.md
+++ b/README.md
@@ -260,6 +260,8 @@ StarfaceOutlookSync.exe
Alle Daten werden lokal pro Benutzer gespeichert:
- `%AppData%\StarfaceOutlookSync\profiles.json` - Sync-Profile mit Zugangsdaten
- `%AppData%\StarfaceOutlookSync\mappings\` - Kontakt-Zuordnungen pro Profil
+- `%AppData%\StarfaceOutlookSync\sync.log` - Protokoll (Syncs, Konflikte, Fehler);
+ im Hauptfenster ueber den Button **Protokoll** einsehbar
### SSL-Zertifikate
diff --git a/src/StarfaceOutlookSync/Services/Logger.cs b/src/StarfaceOutlookSync/Services/Logger.cs
new file mode 100644
index 00000000..e0f3f9c4
--- /dev/null
+++ b/src/StarfaceOutlookSync/Services/Logger.cs
@@ -0,0 +1,80 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace StarfaceOutlookSync.Services
+{
+ ///
+ /// Einfaches, threadsicheres Datei-Protokoll. Haelt fest, was bei Syncs
+ /// passiert (Ergebnisse, Konflikte, Fehler) - dauerhaft nachlesbar, auch
+ /// nachdem eine Tray-Meldung verschwunden ist.
+ ///
+ public static class Logger
+ {
+ private static readonly object _lock = new object();
+ private const long MaxBytes = 2 * 1024 * 1024; // 2 MB, dann Rotation
+
+ public static string LogFilePath { get; } = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "StarfaceOutlookSync", "sync.log");
+
+ public static void Log(string message)
+ {
+ try
+ {
+ lock (_lock)
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));
+ Rotate();
+ var line = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}{Environment.NewLine}";
+ File.AppendAllText(LogFilePath, line, Encoding.UTF8);
+ }
+ }
+ catch { }
+ }
+
+ private static void Rotate()
+ {
+ try
+ {
+ var fi = new FileInfo(LogFilePath);
+ if (fi.Exists && fi.Length > MaxBytes)
+ {
+ var bak = LogFilePath + ".1";
+ if (File.Exists(bak)) File.Delete(bak);
+ File.Move(LogFilePath, bak);
+ }
+ }
+ catch { }
+ }
+
+ public static string ReadAll()
+ {
+ try
+ {
+ lock (_lock)
+ {
+ if (!File.Exists(LogFilePath)) return "";
+ using (var fs = new FileStream(LogFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ using (var sr = new StreamReader(fs, Encoding.UTF8))
+ return sr.ReadToEnd();
+ }
+ }
+ catch { return ""; }
+ }
+
+ public static void Clear()
+ {
+ try
+ {
+ lock (_lock)
+ {
+ if (File.Exists(LogFilePath)) File.Delete(LogFilePath);
+ var bak = LogFilePath + ".1";
+ if (File.Exists(bak)) File.Delete(bak);
+ }
+ }
+ catch { }
+ }
+ }
+}
diff --git a/src/StarfaceOutlookSync/UI/LogViewerForm.cs b/src/StarfaceOutlookSync/UI/LogViewerForm.cs
new file mode 100644
index 00000000..034c3760
--- /dev/null
+++ b/src/StarfaceOutlookSync/UI/LogViewerForm.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.Windows.Forms;
+using StarfaceOutlookSync.Services;
+
+namespace StarfaceOutlookSync.UI
+{
+ /// Zeigt das Sync-Protokoll an (read-only) mit Aktualisieren/Leeren.
+ public class LogViewerForm : Form
+ {
+ private TextBox _txt;
+ private Button _btnClose;
+ private Panel _panel;
+
+ public LogViewerForm()
+ {
+ Text = "Protokoll";
+ Size = new Size(720, 500);
+ StartPosition = FormStartPosition.CenterParent;
+ Font = new Font("Segoe UI", 9);
+
+ _txt = new TextBox
+ {
+ Multiline = true,
+ ReadOnly = true,
+ ScrollBars = ScrollBars.Both,
+ WordWrap = false,
+ Dock = DockStyle.Fill,
+ BackColor = Color.White,
+ Font = new Font("Consolas", 9)
+ };
+
+ _panel = new Panel { Dock = DockStyle.Bottom, Height = 44 };
+
+ var btnRefresh = new Button { Text = "Aktualisieren", Left = 10, Top = 8, Width = 100, Height = 28 };
+ btnRefresh.Click += (s, e) => LoadLog();
+
+ var btnClear = new Button { Text = "Leeren", Left = 118, Top = 8, Width = 80, Height = 28 };
+ btnClear.Click += (s, e) =>
+ {
+ if (MessageBox.Show("Protokoll wirklich leeren?", "Protokoll",
+ MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+ {
+ Logger.Clear();
+ LoadLog();
+ }
+ };
+
+ var btnFolder = new Button { Text = "Ordner oeffnen", Left = 206, Top = 8, Width = 120, Height = 28 };
+ btnFolder.Click += (s, e) => OpenFolder();
+
+ _btnClose = new Button
+ {
+ Text = "Schliessen", Top = 8, Width = 100, Height = 28,
+ Anchor = AnchorStyles.Top | AnchorStyles.Right
+ };
+ _btnClose.Click += (s, e) => Close();
+
+ _panel.Controls.AddRange(new Control[] { btnRefresh, btnClear, btnFolder, _btnClose });
+
+ Controls.Add(_txt);
+ Controls.Add(_panel);
+
+ Load += (s, e) =>
+ {
+ _btnClose.Left = _panel.ClientSize.Width - _btnClose.Width - 10;
+ LoadLog();
+ };
+ }
+
+ private void LoadLog()
+ {
+ _txt.Text = Logger.ReadAll();
+ _txt.SelectionStart = _txt.TextLength;
+ _txt.ScrollToCaret();
+ }
+
+ private void OpenFolder()
+ {
+ try
+ {
+ var dir = Path.GetDirectoryName(Logger.LogFilePath);
+ Process.Start(new ProcessStartInfo { FileName = dir, UseShellExecute = true });
+ }
+ catch { }
+ }
+ }
+}
diff --git a/src/StarfaceOutlookSync/UI/MainForm.cs b/src/StarfaceOutlookSync/UI/MainForm.cs
index 466921a7..063df882 100644
--- a/src/StarfaceOutlookSync/UI/MainForm.cs
+++ b/src/StarfaceOutlookSync/UI/MainForm.cs
@@ -20,7 +20,7 @@ namespace StarfaceOutlookSync.UI
private NotifyIcon _trayIcon;
private ContextMenuStrip _trayMenu;
private ListView _profileList;
- private Button _btnNew, _btnEdit, _btnDelete, _btnSync, _btnReset, _btnSettings, _btnInfo;
+ private Button _btnNew, _btnEdit, _btnDelete, _btnSync, _btnReset, _btnSettings, _btnLog, _btnInfo;
private StatusStrip _statusBar;
private ToolStripStatusLabel _statusLabel;
private Timer _autoSyncTimer;
@@ -129,10 +129,13 @@ namespace StarfaceOutlookSync.UI
_btnSettings = new Button { Text = "Einstellungen", Width = 95, Height = 30 };
_btnSettings.Click += (s, e) => ShowSettings();
+ _btnLog = new Button { Text = "Protokoll", Width = 80, Height = 30 };
+ _btnLog.Click += (s, e) => ShowLog();
+
_btnInfo = new Button { Text = "Info", Width = 50, Height = 30 };
_btnInfo.Click += (s, e) => ShowAbout();
- buttonPanel.Controls.AddRange(new Control[] { _btnNew, _btnEdit, _btnDelete, _btnSync, _btnReset, _btnSettings, _btnInfo });
+ buttonPanel.Controls.AddRange(new Control[] { _btnNew, _btnEdit, _btnDelete, _btnSync, _btnReset, _btnSettings, _btnLog, _btnInfo });
// Statusbar
_statusBar = new StatusStrip();
@@ -358,9 +361,12 @@ namespace StarfaceOutlookSync.UI
if (crossLock == null)
{
SetStatus("Anderer Arbeitsplatz synchronisiert gerade - uebersprungen.");
+ Logger.Log($"Sync '{profile.Name}' uebersprungen: anderer Arbeitsplatz synct gerade.");
return;
}
+ Logger.Log($"Sync gestartet: '{profile.Name}' (Richtung: {profile.SyncDirection})");
+
// Konflikt-Hinweise von ANDEREN Arbeitsplaetzen anzeigen, BEVOR der
// Sync den eigenen Stand auf den Gewinner-Wert aktualisiert (sonst
// koennte man nicht mehr erkennen, ob man betroffen war).
@@ -379,6 +385,10 @@ namespace StarfaceOutlookSync.UI
_trayIcon.ShowBalloonTip(3000, "Starface Sync", msg,
result.Errors > 0 ? ToolTipIcon.Warning : ToolTipIcon.Info);
+ Logger.Log($"Sync fertig: {msg}");
+ foreach (var em in result.ErrorMessages)
+ Logger.Log($" Fehler: {em}");
+
// Echte Feld-Konflikte gesondert melden, damit der Benutzer weiss,
// dass ein Wert ueberschrieben wurde.
if (result.Conflicts.Count > 0)
@@ -389,6 +399,9 @@ namespace StarfaceOutlookSync.UI
_trayIcon.ShowBalloonTip(10000,
$"Konflikt bei {result.Conflicts.Count} Kontakt(en)", detail, ToolTipIcon.Warning);
+ foreach (var c in result.Conflicts)
+ Logger.Log($" KONFLIKT: {c}");
+
// Andere Arbeitsplaetze ueber das gemeinsame Verzeichnis informieren.
_conflictNotifier.Write(sharedDir, result.Conflicts);
}
@@ -400,6 +413,7 @@ namespace StarfaceOutlookSync.UI
_trayIcon.ShowBalloonTip(3000, "Starface Sync Fehler",
ex.Message, ToolTipIcon.Error);
SetStatus($"Fehler: {ex.Message}");
+ Logger.Log($"Sync FEHLER '{profile.Name}': {ex.Message}");
}
finally
{
@@ -461,6 +475,9 @@ namespace StarfaceOutlookSync.UI
var pending = _conflictNotifier.GetPending(sharedDir, MyContactsByStarfaceId());
if (pending.Count == 0) return;
+ foreach (var p in pending)
+ Logger.Log($" KONFLIKT (anderer Arbeitsplatz): {p}");
+
var detail = string.Join("\n", pending.Take(5).Select(p => p.ToString()));
if (pending.Count > 5)
detail += $"\n... und {pending.Count - 5} weitere";
@@ -513,6 +530,14 @@ namespace StarfaceOutlookSync.UI
}
}
+ private void ShowLog()
+ {
+ using (var log = new LogViewerForm())
+ {
+ log.ShowDialog(this);
+ }
+ }
+
private void ExitApplication()
{
_autoSyncTimer?.Stop();