Add cross-client sync lock via shared directory
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>
This commit is contained in:
@@ -348,8 +348,17 @@ namespace StarfaceOutlookSync.UI
|
||||
return;
|
||||
}
|
||||
|
||||
SyncLock crossLock = null;
|
||||
try
|
||||
{
|
||||
// Clientuebergreifende Sperre (falls gemeinsames Verzeichnis konfiguriert).
|
||||
crossLock = await AcquireCrossClientLock();
|
||||
if (crossLock == null)
|
||||
{
|
||||
SetStatus("Anderer Arbeitsplatz synchronisiert gerade - uebersprungen.");
|
||||
return;
|
||||
}
|
||||
|
||||
SetStatus($"Synchronisiere '{profile.Name}'...");
|
||||
_trayIcon.ShowBalloonTip(2000, "Starface Sync",
|
||||
$"Synchronisiere '{profile.Name}'...", ToolTipIcon.Info);
|
||||
@@ -384,10 +393,53 @@ namespace StarfaceOutlookSync.UI
|
||||
}
|
||||
finally
|
||||
{
|
||||
crossLock?.Dispose();
|
||||
Interlocked.Exchange(ref _syncRunning, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holt die clientuebergreifende Lock-Datei aus dem konfigurierten
|
||||
/// gemeinsamen Verzeichnis. Wartet bis zu 2 Minuten, falls ein anderer
|
||||
/// Arbeitsplatz gerade synct. Gibt null zurueck, wenn die Sperre nicht
|
||||
/// erlangt werden konnte (-> diesen Lauf ueberspringen).
|
||||
/// Ist kein Verzeichnis konfiguriert oder es ist nicht erreichbar, wird
|
||||
/// ohne clientuebergreifende Sperre fortgefahren (lokaler Schutz bleibt).
|
||||
/// </summary>
|
||||
private async Task<SyncLock> AcquireCrossClientLock()
|
||||
{
|
||||
var dir = UserSettings.Load().SharedDirectory;
|
||||
if (string.IsNullOrWhiteSpace(dir))
|
||||
return SyncLock.NoOp();
|
||||
|
||||
try
|
||||
{
|
||||
if (!System.IO.Directory.Exists(dir))
|
||||
{
|
||||
SetStatus("Gemeinsames Verzeichnis nicht erreichbar - synce ohne Sperre.");
|
||||
return SyncLock.NoOp();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return SyncLock.NoOp();
|
||||
}
|
||||
|
||||
var deadline = DateTime.UtcNow.AddMinutes(2);
|
||||
int attempt = 0;
|
||||
while (true)
|
||||
{
|
||||
var l = SyncLock.TryAcquire(dir, out var heldBy);
|
||||
if (l != null) return l;
|
||||
|
||||
if (DateTime.UtcNow >= deadline) return null;
|
||||
|
||||
attempt++;
|
||||
SetStatus($"Warte auf anderen Arbeitsplatz ({heldBy ?? "unbekannt"})... ({attempt})");
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStatus(string text)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace StarfaceOutlookSync.UI
|
||||
public class SettingsForm : Form
|
||||
{
|
||||
private CheckBox _chkStartMinimized, _chkSyncOnStart, _chkAutoAcceptOutlook;
|
||||
private TextBox _txtSharedDir;
|
||||
private Button _btnBrowseShared;
|
||||
private Button _btnSave, _btnCancel;
|
||||
private readonly UserSettings _settings;
|
||||
|
||||
@@ -54,35 +56,77 @@ namespace StarfaceOutlookSync.UI
|
||||
var lblHint = new Label
|
||||
{
|
||||
Text = hintText,
|
||||
Left = 38, Top = 102, Width = 310, Height = 36,
|
||||
Left = 38, Top = 102, Width = 320, Height = 36,
|
||||
ForeColor = UserSettings.IsOutlookSecurityLockedByPolicy() ? Color.OrangeRed : Color.Gray,
|
||||
Font = new Font("Segoe UI", 8)
|
||||
};
|
||||
|
||||
var lblShared = new Label
|
||||
{
|
||||
Text = "Gemeinsames Verzeichnis fuer Sync-Sperre (Mehrplatz, optional):",
|
||||
Left = 20, Top = 150, AutoSize = true
|
||||
};
|
||||
|
||||
_txtSharedDir = new TextBox
|
||||
{
|
||||
Left = 20, Top = 172, Width = 250,
|
||||
Text = _settings.SharedDirectory
|
||||
};
|
||||
|
||||
_btnBrowseShared = new Button
|
||||
{
|
||||
Text = "...", Left = 274, Top = 171, Width = 36, Height = 24
|
||||
};
|
||||
_btnBrowseShared.Click += (s, e) => BrowseSharedDir();
|
||||
|
||||
var lblSharedHint = new Label
|
||||
{
|
||||
Text = "Netzlaufwerk/UNC, das alle Arbeitsplaetze erreichen. Leer = keine\n" +
|
||||
"clientuebergreifende Sperre (nur Schutz auf diesem PC).",
|
||||
Left = 20, Top = 198, Width = 330, Height = 32,
|
||||
ForeColor = Color.Gray, Font = new Font("Segoe UI", 8)
|
||||
};
|
||||
|
||||
_btnSave = new Button
|
||||
{
|
||||
Text = "Speichern", Left = 95, Top = 170, Width = 85, Height = 28,
|
||||
Text = "Speichern", Left = 95, Top = 240, Width = 85, Height = 28,
|
||||
DialogResult = DialogResult.None
|
||||
};
|
||||
_btnSave.Click += (s, e) => Save();
|
||||
|
||||
_btnCancel = new Button
|
||||
{
|
||||
Text = "Abbrechen", Left = 189, Top = 170, Width = 85, Height = 28,
|
||||
Text = "Abbrechen", Left = 189, Top = 240, Width = 85, Height = 28,
|
||||
DialogResult = DialogResult.Cancel
|
||||
};
|
||||
|
||||
Size = new Size(380, 260);
|
||||
Controls.AddRange(new Control[] { _chkStartMinimized, _chkSyncOnStart, _chkAutoAcceptOutlook, lblHint, _btnSave, _btnCancel });
|
||||
Size = new Size(380, 330);
|
||||
Controls.AddRange(new Control[] { _chkStartMinimized, _chkSyncOnStart, _chkAutoAcceptOutlook, lblHint,
|
||||
lblShared, _txtSharedDir, _btnBrowseShared, lblSharedHint, _btnSave, _btnCancel });
|
||||
AcceptButton = _btnSave;
|
||||
CancelButton = _btnCancel;
|
||||
}
|
||||
|
||||
private void BrowseSharedDir()
|
||||
{
|
||||
using (var dlg = new FolderBrowserDialog())
|
||||
{
|
||||
dlg.Description = "Gemeinsames Verzeichnis fuer die Sync-Sperre waehlen";
|
||||
if (!string.IsNullOrWhiteSpace(_txtSharedDir.Text))
|
||||
{
|
||||
try { dlg.SelectedPath = _txtSharedDir.Text; } catch { }
|
||||
}
|
||||
if (dlg.ShowDialog(this) == DialogResult.OK)
|
||||
_txtSharedDir.Text = dlg.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
_settings.StartMinimized = _chkStartMinimized.Checked;
|
||||
_settings.SyncOnStart = _chkSyncOnStart.Checked;
|
||||
_settings.AutoAcceptOutlookPrompt = _chkAutoAcceptOutlook.Checked;
|
||||
_settings.SharedDirectory = _txtSharedDir.Text.Trim();
|
||||
_settings.Save();
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
|
||||
Reference in New Issue
Block a user