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>
64 lines
3.4 KiB
C#
64 lines
3.4 KiB
C#
using System.IO;
|
||
using System.Text;
|
||
using System.Windows;
|
||
|
||
namespace Rozpisky.Data;
|
||
|
||
/// <summary>
|
||
/// Společné zacházení s JSON konfiguračními soubory (<c>ciselniky.user.json</c>, <c>cad.json</c>,
|
||
/// <c>layout.json</c> v uživatelské složce, <c>mapovani.json</c> vedle .exe). Řeší dvě slabiny přímého
|
||
/// <c>File.WriteAllText</c>: pád uprostřed zápisu nesmí poškodit původní soubor (atomický zápis
|
||
/// přes .tmp) a poškozený soubor se nesmí tiše zahodit – ručně editovaný config s překlepem by se
|
||
/// jinak bez varování přepsal výchozími hodnotami a uživatel by přišel o vlastní data.
|
||
/// </summary>
|
||
public static class JsonConfigFile
|
||
{
|
||
/// <summary>Zapíše text nejdřív do <c>cesta.tmp</c> a pak ho přejmenuje přes cíl. Pád uprostřed
|
||
/// zápisu tak nechá původní soubor netknutý (nedokončený .tmp se při dalším zápisu přepíše).</summary>
|
||
public static void ZapisAtomicky(string cesta, string obsah)
|
||
{
|
||
var tmp = cesta + ".tmp";
|
||
File.WriteAllText(tmp, obsah);
|
||
File.Move(tmp, cesta, overwrite: true);
|
||
}
|
||
|
||
/// <summary>Zavolat, když existující config nejde načíst (rozbité JSON, nečitelný soubor):
|
||
/// zkopíruje ho do <c>cesta.bak</c> (starší zálohu přepíše) a uživatele upozorní. Upozornění jde
|
||
/// do <c>error.log</c> hned a MessageBox se odloží přes Dispatcher – configy se načítají už
|
||
/// v konstruktoru VM, kdy hlavní okno ještě nestojí. Kopíruje (nepřesouvá), aby soubor, který
|
||
/// nikdo nepřepisuje (mapovani.json), varoval při každém startu, dokud ho uživatel neopraví.</summary>
|
||
public static void ZalohujAOhlasPoskozeny(string cesta, Exception? duvod = null)
|
||
{
|
||
string? zaloha = cesta + ".bak";
|
||
try { File.Copy(cesta, zaloha, overwrite: true); }
|
||
catch { zaloha = null; } // i nečitelný originál → aspoň upozornit, bez zálohy
|
||
|
||
var jmeno = Path.GetFileName(cesta);
|
||
ZapisDoErrorLogu($"„{jmeno}“ je poškozený ({duvod?.Message ?? "neznámý důvod"}); " +
|
||
(zaloha is null ? "záloha se nepodařila." : $"záloha: {zaloha}"));
|
||
|
||
var zprava = $"Soubor {jmeno} je poškozený nebo nečitelný, načetly se výchozí hodnoty.\n\n" +
|
||
(zaloha is null
|
||
? "Zálohu původního souboru se nepodařilo vytvořit."
|
||
: $"Původní obsah je zálohovaný v:\n{zaloha}") +
|
||
$"\n\nPodrobnosti jsou v souboru error.log ve složce\n{UzivatelskaData.Slozka}";
|
||
// BeginInvoke: hláška se ukáže až po rozběhnutí UI smyčky (config se čte před oknem).
|
||
Application.Current?.Dispatcher.BeginInvoke(() =>
|
||
MessageBox.Show(zprava, "Poškozený konfigurační soubor",
|
||
MessageBoxButton.OK, MessageBoxImage.Warning));
|
||
}
|
||
|
||
/// <summary>Zápis do <c>error.log</c> stejným formátem jako globální záchytná síť v App.
|
||
/// Nesmí nic shodit (read-only složka apod.).</summary>
|
||
private static void ZapisDoErrorLogu(string text)
|
||
{
|
||
try
|
||
{
|
||
File.AppendAllText(UzivatelskaData.Cesta("error.log"),
|
||
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Config: {text}{Environment.NewLine}{Environment.NewLine}",
|
||
Encoding.UTF8);
|
||
}
|
||
catch { /* error.log je jen stopa navíc – MessageBox se ukáže i tak */ }
|
||
}
|
||
}
|