using System.Text.Json; using LyricFlow.Core.Dtos; using LyricFlow.Core.Serialization; namespace LyricFlow.Core.Services; public class ProjectStateService { // MARK: - Project Read And Write #region Project Read And Write public ProjectStateDto ReadProject(string projectFile) { var fullPath = Path.GetFullPath(projectFile); using var stream = File.OpenRead(fullPath); using var document = JsonDocument.Parse(stream); var fallbackName = Path.GetFileName(Path.GetDirectoryName(fullPath)) ?? string.Empty; return FromRaw(document.RootElement, fallbackName); } public void WriteProject(string projectFile, ProjectStateDto state) { var fullPath = Path.GetFullPath(projectFile); var directory = Path.GetDirectoryName(fullPath); if (!string.IsNullOrWhiteSpace(directory)) { Directory.CreateDirectory(directory); } File.WriteAllText(fullPath, JsonSerializer.Serialize(state, LyricFlowCoreJsonContext.Default.ProjectStateDto)); } #endregion // MARK: - Payload Conversion #region Payload Conversion private static ProjectStateDto FromRaw(JsonElement payload, string fallbackName) { if (payload.ValueKind != JsonValueKind.Object) { return new ProjectStateDto(2, fallbackName, [], null, [], false); } var version = ReadInt(payload, "version", 2); var name = ReadString(payload, "name") ?? fallbackName; var openFiles = ReadStringList(payload, "open_files"); var activeFile = ReadString(payload, "active_file"); var scratchpadOpen = ReadBool(payload, "scratchpad_open", false); var cursorPositions = ReadCursorPositions(payload); return new ProjectStateDto(Math.Max(1, version), name, openFiles, activeFile, cursorPositions, scratchpadOpen); } #endregion // MARK: - JSON Readers #region JSON Readers private static Dictionary ReadCursorPositions(JsonElement payload) { var cursorPositions = new Dictionary(); if (!payload.TryGetProperty("cursor_positions", out var cursorElement) || cursorElement.ValueKind != JsonValueKind.Object) { return cursorPositions; } foreach (var property in cursorElement.EnumerateObject()) { if (TryReadInt(property.Value, out var value)) { cursorPositions[property.Name] = Math.Max(0, value); } } return cursorPositions; } private static List ReadStringList(JsonElement payload, string key) { var values = new List(); if (!payload.TryGetProperty(key, out var element) || element.ValueKind != JsonValueKind.Array) { return values; } foreach (var item in element.EnumerateArray()) { if (item.ValueKind == JsonValueKind.String) { var text = item.GetString(); if (!string.IsNullOrWhiteSpace(text)) { values.Add(text); } } } return values; } private static string? ReadString(JsonElement payload, string key) { if (payload.TryGetProperty(key, out var element) && element.ValueKind == JsonValueKind.String) { return element.GetString(); } return null; } private static bool ReadBool(JsonElement payload, string key, bool fallback) { if (!payload.TryGetProperty(key, out var element)) { return fallback; } return element.ValueKind switch { JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.String when bool.TryParse(element.GetString(), out var value) => value, _ => fallback, }; } private static int ReadInt(JsonElement payload, string key, int fallback) { if (payload.TryGetProperty(key, out var element) && TryReadInt(element, out var value)) { return value; } return fallback; } private static bool TryReadInt(JsonElement element, out int value) { if (element.ValueKind == JsonValueKind.Number && element.TryGetInt32(out value)) { return true; } if (element.ValueKind == JsonValueKind.String && int.TryParse(element.GetString(), out value)) { return true; } value = 0; return false; } #endregion }