File.WriteAllText kuerzt die Zieldatei zuerst auf null und fuellt sie dann. Bricht der Vorgang dazwischen ab, ist der alte Inhalt weg und der neue unvollstaendig. Das ist im Betrieb bereits eingetreten: In der Instanz TradingTeam lag eine TokenUsage.json.corrupt_..., die die Fehlerbehandlung beiseitegelegt hatte. Der Verbrauch bis dahin war verloren. AtomicFile schreibt in eine Nebendatei, erzwingt das Schreiben auf die Platte und ersetzt dann. Umgestellt sind ChatHistory, ChatContext, alle Instanz- und Agentenkonfigurationen, Identity und Soul, die App-Einstellungen sowie der Stock-Index. Zum Ersetzen wurde das Windows-Verhalten gemessen statt vermutet. Mit einem Leser, der die Zieldatei geoeffnet haelt: Freigabe des Lesers File.Move File.Replace Read scheitert scheitert ReadWrite scheitert scheitert ReadWrite | Delete scheitert funktioniert File.Move verlangt die Zieldatei exklusiv und scheitert deshalb immer, sobald jemand sie geoeffnet hat. Daher File.Replace — und ein Lesehelfer AtomicFile.ReadAllText, der das Loeschen freigibt, damit unsere eigenen Leser keinen Schreiber blockieren. Die Leser in InstanceDirectoryManager und beim Laden der Chatverlaeufe nutzen ihn jetzt. Zusaetzlich ein Schloss je Zieldatei: Zwei gleichzeitige Schreibvorgaenge auf dieselbe Datei sind ohnehin ein Rennen, ohne Serialisierung scheitern sie aber zusaetzlich mit "Zugriff verweigert". Fuer fremde Leser wie Virenscanner bleibt eine Wiederholung mit Wartezeit. 366 Tests gruen (218 Core, 148 Tools). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
65 lines
1.7 KiB
C#
65 lines
1.7 KiB
C#
using System.Text.Json;
|
||
using ClawdDotNet.Models;
|
||
|
||
namespace ClawdDotNet.Services;
|
||
|
||
public sealed class SettingsManager
|
||
{
|
||
private const string SettingsFileName = "Settings.json";
|
||
|
||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||
{
|
||
WriteIndented = true,
|
||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||
AllowTrailingCommas = true,
|
||
PropertyNameCaseInsensitive = true
|
||
};
|
||
|
||
private readonly string _settingsPath;
|
||
|
||
public AppSettings AppSettings { get; private set; } = new();
|
||
|
||
public SettingsManager(string? basePath = null)
|
||
{
|
||
var dir = basePath ?? AppDomain.CurrentDomain.BaseDirectory;
|
||
_settingsPath = Path.Combine(dir, SettingsFileName);
|
||
}
|
||
|
||
public void Load()
|
||
{
|
||
if (!File.Exists(_settingsPath))
|
||
{
|
||
AppSettings = new AppSettings();
|
||
Save(); // Defaults schreiben
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
var json = File.ReadAllText(_settingsPath);
|
||
AppSettings = JsonSerializer.Deserialize<AppSettings>(json, JsonOptions)
|
||
?? new AppSettings();
|
||
}
|
||
catch
|
||
{
|
||
AppSettings = new AppSettings();
|
||
}
|
||
}
|
||
|
||
public void Save()
|
||
{
|
||
try
|
||
{
|
||
var json = JsonSerializer.Serialize(AppSettings, JsonOptions);
|
||
ClawdDotNet.Core.Storage.AtomicFile.WriteAllText(_settingsPath, json);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// Logging ist hier ggf. noch nicht verfügbar – Fallback auf MessageBox
|
||
MessageBox.Show(
|
||
$"Settings konnten nicht gespeichert werden:\n{ex.Message}",
|
||
"Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
}
|
||
}
|
||
}
|