| 179 | } |
| 180 | |
| 181 | void SettingsStore::save(const AppSettings& s) const { |
| 182 | try { |
| 183 | const std::string json = settings_to_json(s); |
| 184 | |
| 185 | if (const auto parent = file_.parent_path(); !parent.empty()) { |
| 186 | std::error_code ec; |
| 187 | std::filesystem::create_directories(parent, ec); |
| 188 | // Ignore ec here: if the directory truly can't be created the |
| 189 | // temp-file open below fails and is reported. A benign "already |
| 190 | // exists" is not an error worth surfacing. |
| 191 | } |
| 192 | |
| 193 | // Unique temp name per process AND per call. Two OID windows can be |
| 194 | // alive at once (e.g. one lingering from a previous debug session plus |
| 195 | // a fresh one), and both persist to the same settings file. A single |
| 196 | // shared "<file>.tmp" made their saves race: whichever renamed first |
| 197 | // consumed the temp, and the other's rename then failed with ENOENT. |
| 198 | // A per-writer temp name lets each save complete independently |
| 199 | // (last writer wins, which is fine for UI preferences). |
| 200 | static std::atomic<unsigned> tmp_counter{0}; |
| 201 | #if defined(_WIN32) |
| 202 | const auto pid = static_cast<long>(_getpid()); |
| 203 | #else |
| 204 | const auto pid = static_cast<long>(getpid()); |
| 205 | #endif |
| 206 | const std::filesystem::path tmp = std::format( |
| 207 | "{}.{}.{}.tmp", file_.string(), pid, tmp_counter.fetch_add(1)); |
| 208 | { |
| 209 | std::ofstream os{tmp, std::ios::binary | std::ios::trunc}; |
| 210 | os << json; |
| 211 | if (!os) { |
| 212 | std::cerr << "[OID] settings save failed: write error (disk " |
| 213 | "full?)\n"; |
| 214 | std::error_code ec; |
| 215 | std::filesystem::remove(tmp, ec); // best-effort cleanup |
| 216 | return; |
| 217 | } |
| 218 | } |
| 219 | std::error_code ren_ec; |
| 220 | std::filesystem::rename(tmp, file_, ren_ec); // atomic replace |
| 221 | if (ren_ec) { |
| 222 | std::cerr << "[OID] settings save failed: " << ren_ec.message() |
| 223 | << '\n'; |
| 224 | std::error_code rm_ec; |
| 225 | std::filesystem::remove(tmp, rm_ec); // best-effort cleanup |
| 226 | } |
| 227 | } catch (const std::exception& e) { |
| 228 | std::cerr << "[OID] settings save failed: " << e.what() << '\n'; |
| 229 | } catch (...) { |
| 230 | std::cerr << "[OID] settings save failed: unknown error\n"; |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | } // namespace oid::host |