Vše, co píše uživatel, míří přes novou třídu UzivatelskaData do profilu uživatele: cad.json, layout.json, error.log, tisk_diag.log. Dosud se zapisovalo vedle .exe, takže .exe v Program Files (složka jen pro čtení) nebo přeinstalace novou verzí uživatele o data připravily. Dodávané podklady (šablona, mustr, loga, mapovani.json) se dál čtou vedle .exe – ty se naopak při každé instalaci obnovují. Hranice mezi tím, co se instaluje, a tím, co patří uživateli, vede přesně tudy. Portable režim zůstává: soubor portable.txt vedle .exe vrátí staré chování (běh z USB, vývoj s jednou složkou). Migrace: Migrace.PrenesStaraData() při startu přenese cad.json a layout.json ze staré polohy, pokud v cíli ještě nejsou; originál označí příponou .migrovano. Nic se nemaže a nic novějšího se nepřepíše. Číselníky přijdou na řadu samostatně (rozpad na dodávanou a uživatelskou vrstvu). Build 0 chyb / 0 varování, 36 testů zelených. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
5.2 KiB
C#
102 lines
5.2 KiB
C#
using System.IO;
|
||
using System.Text.Encodings.Web;
|
||
using System.Text.Json;
|
||
using Rozpisky.Data;
|
||
|
||
namespace Rozpisky.Cad;
|
||
|
||
/// <summary>
|
||
/// Načítání/ukládání seznamu podporovaných CAD systémů + naposledy vybraného enginu do JSON souboru
|
||
/// v <b>uživatelské složce</b> (<see cref="Data.UzivatelskaData"/>). CAD je vlastnost stroje
|
||
/// (ne zakázky), takže výběr žije mimo projekt a je společný všem projektům – a mimo instalační
|
||
/// složku, aby o něj uživatel při přeinstalaci nepřišel.
|
||
/// </summary>
|
||
public static class CadEnginesStore
|
||
{
|
||
public const string SouborNazev = "cad.json";
|
||
|
||
private static readonly JsonSerializerOptions Opts = new()
|
||
{
|
||
WriteIndented = true,
|
||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||
};
|
||
|
||
/// <summary>Výchozí umístění: uživatelská složka (viz <see cref="UzivatelskaData"/>).</summary>
|
||
public static string DefaultPath => UzivatelskaData.Cesta(SouborNazev);
|
||
|
||
/// <summary>Uložený výběr CADu: buď předdefinovaný engine (<see cref="VybranyProgId"/>), nebo
|
||
/// ruční režim „Manuálně" (<see cref="RucniRezim"/> + <see cref="RucniProgId"/>). Režim a ruční
|
||
/// text se drží zvlášť, aby se po startu obnovil přesně stav UI včetně (i prázdného) inputboxu –
|
||
/// a aby ruční hodnota shodná s předdefinovaným ProgID nepřeklopila UI zpět do comboboxu.</summary>
|
||
public readonly record struct Vyber(string? VybranyProgId, bool RucniRezim, string? RucniProgId);
|
||
|
||
/// <summary>Načte seznam enginů a uložený výběr. Když soubor chybí/je poškozený, vrátí vestavěný
|
||
/// <see cref="Seed"/> (a rovnou ho uloží, aby šel soubor dál ručně upravovat).</summary>
|
||
public static (List<CadEngine> Engines, Vyber Vyber) Load(string? path = null)
|
||
{
|
||
path ??= DefaultPath;
|
||
try
|
||
{
|
||
if (File.Exists(path))
|
||
{
|
||
var data = JsonSerializer.Deserialize<Dto>(File.ReadAllText(path), Opts);
|
||
if (data?.Engines is { Count: > 0 })
|
||
{
|
||
// „Mrtvý“ ProgId (ručně upravený soubor) → žádný výběr comboboxu, ať nedrží
|
||
// engine, který v seznamu není. Ruční režim tím netknutý.
|
||
var vybrany = data.Engines.Any(e => e.ProgId == data.VybranyProgId)
|
||
? data.VybranyProgId : null;
|
||
return (data.Engines, new Vyber(vybrany, data.RucniRezim, data.RucniProgId));
|
||
}
|
||
// Soubor existuje, ale nenese žádný engine („null" / prázdný seznam) → stejné
|
||
// zacházení jako s rozbitým JSON, viz catch níže.
|
||
JsonConfigFile.ZalohujAOhlasPoskozeny(path);
|
||
}
|
||
}
|
||
catch (Exception ex) when (ex is IOException or JsonException or UnauthorizedAccessException)
|
||
{
|
||
// Poškozený / nečitelný soubor → fallback na seed. Soubor se hned dole přepíše seedem,
|
||
// proto se originál (třeba ručně dopsané ProgID) nejdřív zazálohuje a uživatel dozví.
|
||
JsonConfigFile.ZalohujAOhlasPoskozeny(path, ex);
|
||
}
|
||
var seed = Seed();
|
||
Save(seed, default, path);
|
||
return (seed, default);
|
||
}
|
||
|
||
/// <summary>Uloží seznam enginů + výběr (režim comboboxu vs. Manuálně a ruční text) – atomicky
|
||
/// přes .tmp, aby pád uprostřed zápisu soubor nepoškodil. Chyby zápisu (např. read-only složka
|
||
/// u .exe v Program Files) propustí volajícímu.</summary>
|
||
public static void Save(List<CadEngine> engines, Vyber vyber, string? path = null)
|
||
=> JsonConfigFile.ZapisAtomicky(path ?? DefaultPath, JsonSerializer.Serialize(
|
||
new Dto
|
||
{
|
||
Engines = engines,
|
||
VybranyProgId = vyber.VybranyProgId,
|
||
RucniRezim = vyber.RucniRezim,
|
||
RucniProgId = vyber.RucniProgId,
|
||
}, Opts));
|
||
|
||
/// <summary>Vestavěný výchozí seznam CADů (databáze od programátora, PODKLAD_CAD_Layout_Tisk.md §1:
|
||
/// pořadí AutoCAD → ZWCAD → BricsCAD). Vedle verzově nezávislých ProgID („poslední nainstalovaná")
|
||
/// jsou i verzově konkrétní položky – verzově nezávislé „ZWCAD.Application" se u některých instalací
|
||
/// na běžící instanci nenapojí, „ZWCAD.Application.2023" ano. Kdo potřebuje jinou verzi, dopíše ji
|
||
/// ručně přes režim „Manuálně" v UI (nebo do cad.json). Soubor jde kdykoli ručně upravit.</summary>
|
||
public static List<CadEngine> Seed() =>
|
||
[
|
||
new() { ProgId = "AutoCAD.Application", Nazev = "AutoCAD (poslední)" },
|
||
new() { ProgId = "ZWCAD.Application", Nazev = "ZWCAD (poslední)" },
|
||
new() { ProgId = "ZWCAD.Application.2023", Nazev = "ZWCAD 2023" },
|
||
new() { ProgId = "ZWCAD.Application.2022", Nazev = "ZWCAD 2022" },
|
||
new() { ProgId = "BricscadApp.AcadApplication", Nazev = "BricsCAD" },
|
||
];
|
||
|
||
private sealed class Dto
|
||
{
|
||
public List<CadEngine> Engines { get; set; } = new();
|
||
public string? VybranyProgId { get; set; }
|
||
public bool RucniRezim { get; set; }
|
||
public string? RucniProgId { get; set; }
|
||
}
|
||
}
|