stan44 e0f298ba36 Add LyricFlow .NET backend API and Python bridge integration
- introduce `LyricFlow.Core.Backend` with shared DTOs, rhyme/spellcheck engines, and REST endpoints
- wire Python GUI/core to run and call the backend via new bridge/client modules
- add backend parity/integration tests and update packaging/ignore settings
2026-03-15 01:44:56 -05:00

156 lines
4.5 KiB
C#

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<string, int> ReadCursorPositions(JsonElement payload)
{
var cursorPositions = new Dictionary<string, int>();
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<string> ReadStringList(JsonElement payload, string key)
{
var values = new List<string>();
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
}