Files
cursor-lang/CursorLang/Services/SettingsService.cs
T
2026-08-08 21:48:16 +05:00

144 lines
4.5 KiB
C#

using System.ComponentModel;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Windows.Media;
using System.Windows.Threading;
using CursorLang.Models;
namespace CursorLang.Services;
/// <summary>
/// Хранит настройки в %APPDATA%\CursorLang\settings.json.
/// </summary>
public sealed class SettingsService : IDisposable
{
private static readonly JsonSerializerOptions SerializerOptions = new()
{
WriteIndented = true,
Converters = { new ColorJsonConverter(), new JsonStringEnumConverter() },
};
private readonly string _filePath;
private readonly DispatcherTimer _saveTimer;
private AppSettings? _settings;
public SettingsService()
{
string folder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"CursorLang");
_filePath = Path.Combine(folder, "settings.json");
// Ползунки меняют значения непрерывно, поэтому запись на диск
// откладывается до паузы в изменениях
_saveTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
_saveTimer.Tick += OnSaveTimerTick;
}
/// <summary>
/// Читает настройки с диска либо отдаёт значения по умолчанию,
/// и дальше сам сохраняет любые изменения.
/// </summary>
public AppSettings Load()
{
_settings = ReadFile() ?? CreateDefault();
_settings.PropertyChanged += OnSettingsChanged;
return _settings;
}
public void Save()
{
if (_settings is null)
{
return;
}
try
{
Directory.CreateDirectory(Path.GetDirectoryName(_filePath)!);
File.WriteAllText(_filePath, JsonSerializer.Serialize(_settings, SerializerOptions));
}
catch (Exception e) when (e is IOException or UnauthorizedAccessException)
{
// Настройки — не тот случай, ради которого стоит ронять приложение
}
}
public void Dispose()
{
_saveTimer.Stop();
_saveTimer.Tick -= OnSaveTimerTick;
if (_settings is not null)
{
_settings.PropertyChanged -= OnSettingsChanged;
Save();
}
}
private AppSettings? ReadFile()
{
try
{
if (!File.Exists(_filePath))
{
return null;
}
return JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(_filePath), SerializerOptions);
}
catch (Exception e) when (e is IOException or UnauthorizedAccessException or JsonException)
{
// Испорченный файл не должен мешать запуску: начнём с умолчаний
return null;
}
}
// Язык по умолчанию берём системный, если он поддерживается
private static AppSettings CreateDefault()
{
string uiLanguage = System.Globalization.CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
return new AppSettings { Language = uiLanguage == "ru" ? "ru" : "en" };
}
private void OnSettingsChanged(object? sender, PropertyChangedEventArgs e)
{
_saveTimer.Stop();
_saveTimer.Start();
}
private void OnSaveTimerTick(object? sender, EventArgs e)
{
_saveTimer.Stop();
Save();
}
// Color не сериализуется штатно, а хранить его читаемым в файле удобно
private sealed class ColorJsonConverter : JsonConverter<Color>
{
public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string? value = reader.GetString();
if (string.IsNullOrWhiteSpace(value))
{
return Colors.Black;
}
try
{
return (Color)ColorConverter.ConvertFromString(value)!;
}
catch (FormatException)
{
return Colors.Black;
}
}
public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
}