PATCH-04: backup <path>.bak abans de reescriure config/presets per fallada

Helper `backupFile(path)` que copia el YAML a `<path>.bak` si existeix.
Es crida a `loadFromFile` (versió mismatch i catch) i a
`loadPresetsFromFile<T>` (manca `presets:`, llista buida, catch).
La primera execució no genera .bak. Cada reescriptura forçada registra el
motiu per stderr (INCONS-02 migrarà a std::cout).

Prova manual: config.yaml amb contingut \"===broken===\" genera
config.yaml.bak amb el contingut previ i un nou config.yaml amb defaults.
This commit is contained in:
JailDesigner committed 2026-04-20 17:40:39 +02:00
1 parent 505c20dabc
commit db2e5d66e0
2 files changed
+31 -4

No files matched your search

+2 -2
View File
@@ -21,7 +21,7 @@ Els IDs es mantenen estables una vegada assignats. Cada entrada apunta al fitxer
| HC-03 | Geometria del mapa cablejada a `map.hpp` (bloqueja "pirámide a pirámide") | DONE |
| COUP-01 | `core/` inclou `game/ui/notification_presets.hpp` (dependència invertida) | DONE |
| DUP-01 | PostFX i CrtPi són dos pipelines duplicats a `options.cpp` | DONE |
| PATCH-04 | Reescriure silenciosament `config.yaml` al fallar parseig o versió | PENDING |
| PATCH-04 | Reescriure silenciosament `config.yaml` al fallar parseig o versió | DONE |
| HC-01 | `SPEED_PX_PER_S`, caixa de col·lisió i rutes d'assets cablejades | PENDING |
---
@@ -56,7 +56,7 @@ Els IDs es mantenen estables una vegada assignats. Cada entrada apunta al fitxer
| PATCH-01 | `player.cpp:66-77` | Col·lisió per eix fent `AnimatedSprite::update` + revertir + reaplicar | Importante | PENDING | `tryMove(dx, dy, collisionFn)` a la classe base |
| PATCH-02 | `player.cpp:13-15` | Comentari tipus TODO ("si més endavant volem sprint...") | Menor | PENDING | Treure o convertir en feature amb issue |
| PATCH-03 | `map.cpp:13-15` | `overlapStrict` custom perquè SDL3 considera el toc com intersecció | Menor | PENDING | Si s'usa més, mourea `utils/` |
| PATCH-04 | `options.cpp:361-363,371-375,475-480,528-533` | Reescriure silenciosament `config.yaml` al fallar parseig o canviar versió | Crítico | PENDING | `.bak` + log abans de sobreescriure |
| PATCH-04 | `options.cpp:361-363,371-375,475-480,528-533` | Reescriure silenciosament `config.yaml` al fallar parseig o canviar versió | Crítico | DONE | Helper `backupFile()` que copia a `<path>.bak` si existeix. Cridat abans de cada reescriptura forçada (versió mismatch + catch a `loadFromFile`, `presets:` absent/invàlid/buit + catch a `loadPresetsFromFile`). Primera execució no genera `.bak`. Missatge explícit per motiu de reescriptura |
| PATCH-05 | `game_scene.cpp:51` | `if (Audio::get() != nullptr)` com a defensa sobre un singleton Meyers | Menor | PENDING | Assumir invariant, treure |
| PATCH-06 | `game_scene.cpp:78-91` | Ordenació per `getFeetY()` amb 1 jugador i sense sarcs-sprite: cerimonial | Menor | PENDING | Comentar "reactivar quan hi haja sarcs-sprite" o treure |
| PATCH-07 | `map.hpp:64` + `map.cpp:19` | `Map` declara ctor però tots els mètodes són `static` (transició a mitges) | Importante | PENDING | Decidir: namespace o instanciar |
+29 -2
View File
@@ -76,6 +76,20 @@ namespace {
return file.good();
}
// Còpia `<path>` a `<path>.bak` per protegir ediciones manuals de
// l'usuari quan estem a punt de reescriure el fitxer amb defaults per
// error de parseig o canvi de versió. Si el fitxer no existeix (primera
// execució) o no es pot obrir, no fa res: no hi ha res a salvar.
void backupFile(const std::string& path) {
if (path.empty()) { return; }
std::ifstream src(path, std::ios::binary);
if (!src) { return; }
std::ofstream dst(path + ".bak", std::ios::binary);
if (!dst) { return; }
dst << src.rdbuf();
std::cerr << "Options: còpia de seguretat a " << path << ".bak\n";
}
void parseWindow(const fkyaml::node& yaml) {
if (!yaml.contains("window")) { return; }
const auto& node = yaml["window"];
@@ -351,12 +365,15 @@ namespace {
const std::string CONTENT = readFileOrEmpty(path);
if (CONTENT.empty()) {
// Primera execució (no existia o era buit): no cal backup.
presets = defaults_fn();
return save_fn();
}
try {
auto yaml = fkyaml::node::deserialize(CONTENT);
if (!yaml.contains("presets") || !yaml["presets"].is_sequence()) {
std::cerr << "Options: " << kind_label << " manca `presets:`; reescrivint amb defaults.\n";
backupFile(path);
presets = defaults_fn();
return save_fn();
}
@@ -367,6 +384,8 @@ namespace {
presets.push_back(preset);
}
if (presets.empty()) {
std::cerr << "Options: " << kind_label << " sense presets; reescrivint amb defaults.\n";
backupFile(path);
presets = defaults_fn();
return save_fn();
}
@@ -374,6 +393,7 @@ namespace {
} catch (const std::exception& e) {
std::cerr << "Options: error parsejant " << kind_label << ": " << e.what() << '\n';
std::cerr << "Options: reescrivint el fitxer amb presets per defecte.\n";
backupFile(path);
presets = defaults_fn();
return save_fn();
}
@@ -426,8 +446,14 @@ namespace Options {
file_version = yaml["version"].get_value<std::string>();
}
// Sense migració per ara: si la versió no coincideix, reescrivim
// amb els defaults de la versió nova.
if (file_version != Texts::VERSION) { return saveToFile(); }
// amb els defaults de la versió nova. El fitxer original queda
// a `<path>.bak` per si l'usuari volia preservar tuning manual.
if (file_version != Texts::VERSION) {
std::cerr << "Options: config.yaml versió \"" << file_version
<< "\" != \"" << Texts::VERSION << "\"; reescrivint amb defaults.\n";
backupFile(config_file_path);
return saveToFile();
}
config_version = file_version;
if (yaml.contains("locale")) { Options::locale = yaml["locale"].get_value<std::string>(); }
@@ -438,6 +464,7 @@ namespace Options {
} catch (const std::exception& e) {
std::cerr << "Options: error parsejant config.yaml: " << e.what() << '\n';
std::cerr << "Options: reescrivint el fitxer amb defaults.\n";
backupFile(config_file_path);
return saveToFile();
}
}