1
0

Settings.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. /*
  2. * Settings.cpp
  3. * ------------
  4. * Purpose: Application setting handling framework.
  5. * Notes : (currently none)
  6. * Authors: Joern Heusipp
  7. * OpenMPT Devs
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #include "Settings.h"
  12. #include "mpt/binary/hex.hpp"
  13. #include "../common/misc_util.h"
  14. #include "../common/mptStringBuffer.h"
  15. #include "Mptrack.h"
  16. #include "Mainfrm.h"
  17. #include <algorithm>
  18. #include "../common/mptFileIO.h"
  19. OPENMPT_NAMESPACE_BEGIN
  20. mpt::ustring SettingValue::FormatTypeAsString() const
  21. {
  22. if(GetType() == SettingTypeNone)
  23. {
  24. return U_("nil");
  25. }
  26. mpt::ustring result;
  27. switch(GetType())
  28. {
  29. case SettingTypeBool:
  30. result += U_("bool");
  31. break;
  32. case SettingTypeInt:
  33. result += U_("int");
  34. break;
  35. case SettingTypeFloat:
  36. result += U_("float");
  37. break;
  38. case SettingTypeString:
  39. result += U_("string");
  40. break;
  41. case SettingTypeBinary:
  42. result += U_("binary");
  43. break;
  44. case SettingTypeNone:
  45. default:
  46. result += U_("nil");
  47. break;
  48. }
  49. if(HasTypeTag() && !GetTypeTag().empty())
  50. {
  51. result += U_(":") + mpt::ToUnicode(mpt::Charset::ASCII, GetTypeTag());
  52. }
  53. return result;
  54. }
  55. mpt::ustring SettingValue::FormatValueAsString() const
  56. {
  57. switch(GetType())
  58. {
  59. case SettingTypeBool:
  60. return mpt::ufmt::val(as<bool>());
  61. break;
  62. case SettingTypeInt:
  63. return mpt::ufmt::val(as<int32>());
  64. break;
  65. case SettingTypeFloat:
  66. return mpt::ufmt::val(as<double>());
  67. break;
  68. case SettingTypeString:
  69. return as<mpt::ustring>();
  70. break;
  71. case SettingTypeBinary:
  72. return mpt::encode_hex(mpt::as_span(as<std::vector<std::byte>>()));
  73. break;
  74. case SettingTypeNone:
  75. default:
  76. return mpt::ustring();
  77. break;
  78. }
  79. }
  80. void SettingValue::SetFromString(const AnyStringLocale &newVal)
  81. {
  82. switch(GetType())
  83. {
  84. case SettingTypeBool:
  85. value = ConvertStrTo<bool>(newVal);
  86. break;
  87. case SettingTypeInt:
  88. value = ConvertStrTo<int32>(newVal);
  89. break;
  90. case SettingTypeFloat:
  91. value = ConvertStrTo<double>(newVal);
  92. break;
  93. case SettingTypeString:
  94. value = newVal;
  95. break;
  96. case SettingTypeBinary:
  97. value = mpt::decode_hex(newVal);
  98. break;
  99. case SettingTypeNone:
  100. default:
  101. break;
  102. }
  103. }
  104. SettingValue SettingsContainer::BackendsReadSetting(const SettingPath &path, const SettingValue &def) const
  105. {
  106. return backend->ReadSetting(path, def);
  107. }
  108. void SettingsContainer::BackendsWriteSetting(const SettingPath &path, const SettingValue &val)
  109. {
  110. backend->WriteSetting(path, val);
  111. }
  112. void SettingsContainer::BackendsRemoveSetting(const SettingPath &path)
  113. {
  114. backend->RemoveSetting(path);
  115. }
  116. void SettingsContainer::BackendsRemoveSection(const mpt::ustring &section)
  117. {
  118. backend->RemoveSection(section);
  119. }
  120. SettingValue SettingsContainer::ReadSetting(const SettingPath &path, const SettingValue &def) const
  121. {
  122. ASSERT(theApp.InGuiThread());
  123. ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
  124. auto entry = map.find(path);
  125. if(entry == map.end())
  126. {
  127. entry = map.insert(map.begin(), std::make_pair(path, SettingState(def).assign(BackendsReadSetting(path, def), false)));
  128. }
  129. return entry->second;
  130. }
  131. bool SettingsContainer::IsDefaultSetting(const SettingPath &path) const
  132. {
  133. ASSERT(theApp.InGuiThread());
  134. ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
  135. auto entry = map.find(path);
  136. if(entry == map.end())
  137. {
  138. return true;
  139. }
  140. return entry->second.IsDefault();
  141. }
  142. void SettingsContainer::WriteSetting(const SettingPath &path, const SettingValue &val, SettingFlushMode flushMode)
  143. {
  144. ASSERT(theApp.InGuiThread());
  145. ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
  146. auto entry = map.find(path);
  147. if(entry == map.end())
  148. {
  149. map[path] = val;
  150. entry = map.find(path);
  151. } else
  152. {
  153. entry->second = val;
  154. }
  155. NotifyListeners(path);
  156. if(immediateFlush || flushMode == SettingWriteThrough)
  157. {
  158. BackendsWriteSetting(path, val);
  159. entry->second.Clean();
  160. }
  161. }
  162. void SettingsContainer::ForgetSetting(const SettingPath &path)
  163. {
  164. ASSERT(theApp.InGuiThread());
  165. ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
  166. map.erase(path);
  167. }
  168. void SettingsContainer::ForgetAll()
  169. {
  170. ASSERT(theApp.InGuiThread());
  171. ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
  172. map.clear();
  173. }
  174. void SettingsContainer::RemoveSetting(const SettingPath &path)
  175. {
  176. ASSERT(theApp.InGuiThread());
  177. ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
  178. map.erase(path);
  179. BackendsRemoveSetting(path);
  180. }
  181. void SettingsContainer::RemoveSection(const mpt::ustring &section)
  182. {
  183. ASSERT(theApp.InGuiThread());
  184. ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
  185. std::vector<SettingPath> pathsToRemove;
  186. for(const auto &entry : map)
  187. {
  188. if(entry.first.GetSection() == section)
  189. {
  190. pathsToRemove.push_back(entry.first);
  191. }
  192. }
  193. for(const auto &path : pathsToRemove)
  194. {
  195. map.erase(path);
  196. }
  197. BackendsRemoveSection(section);
  198. }
  199. void SettingsContainer::NotifyListeners(const SettingPath &path)
  200. {
  201. const auto entry = mapListeners.find(path);
  202. if(entry != mapListeners.end())
  203. {
  204. for(auto &it : entry->second)
  205. {
  206. it->SettingChanged(path);
  207. }
  208. }
  209. }
  210. void SettingsContainer::WriteSettings()
  211. {
  212. ASSERT(theApp.InGuiThread());
  213. ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
  214. for(auto &[path, value] : map)
  215. {
  216. if(value.IsDirty())
  217. {
  218. BackendsWriteSetting(path, value);
  219. value.Clean();
  220. }
  221. }
  222. }
  223. void SettingsContainer::Flush()
  224. {
  225. ASSERT(theApp.InGuiThread());
  226. ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
  227. WriteSettings();
  228. }
  229. void SettingsContainer::SetImmediateFlush(bool newImmediateFlush)
  230. {
  231. if(newImmediateFlush)
  232. {
  233. Flush();
  234. }
  235. immediateFlush = newImmediateFlush;
  236. }
  237. void SettingsContainer::Register(ISettingChanged *listener, const SettingPath &path)
  238. {
  239. mapListeners[path].insert(listener);
  240. }
  241. void SettingsContainer::UnRegister(ISettingChanged *listener, const SettingPath &path)
  242. {
  243. mapListeners[path].erase(listener);
  244. }
  245. SettingsContainer::~SettingsContainer()
  246. {
  247. WriteSettings();
  248. }
  249. SettingsContainer::SettingsContainer(ISettingsBackend *backend)
  250. : backend(backend)
  251. {
  252. MPT_ASSERT(backend);
  253. }
  254. std::vector<std::byte> IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, const std::vector<std::byte> &def) const
  255. {
  256. std::vector<std::byte> result = def;
  257. if(!mpt::in_range<UINT>(result.size()))
  258. {
  259. return result;
  260. }
  261. ::GetPrivateProfileStruct(GetSection(path).c_str(), GetKey(path).c_str(), result.data(), static_cast<UINT>(result.size()), filename.AsNative().c_str());
  262. return result;
  263. }
  264. mpt::ustring IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, const mpt::ustring &def) const
  265. {
  266. std::vector<TCHAR> buf(128);
  267. while(::GetPrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::ToWin(def).c_str(), buf.data(), static_cast<DWORD>(buf.size()), filename.AsNative().c_str()) == buf.size() - 1)
  268. {
  269. if(buf.size() == std::numeric_limits<DWORD>::max())
  270. {
  271. return def;
  272. }
  273. buf.resize(mpt::exponential_grow(buf.size(), std::numeric_limits<DWORD>::max()));
  274. }
  275. return mpt::ToUnicode(mpt::winstring(buf.data()));
  276. }
  277. double IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, double def) const
  278. {
  279. std::vector<TCHAR> buf(128);
  280. while(::GetPrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::tfmt::val(def).c_str(), buf.data(), static_cast<DWORD>(buf.size()), filename.AsNative().c_str()) == buf.size() - 1)
  281. {
  282. if(buf.size() == std::numeric_limits<DWORD>::max())
  283. {
  284. return def;
  285. }
  286. buf.resize(mpt::exponential_grow(buf.size(), std::numeric_limits<DWORD>::max()));
  287. }
  288. return ConvertStrTo<double>(mpt::winstring(buf.data()));
  289. }
  290. int32 IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, int32 def) const
  291. {
  292. return (int32)::GetPrivateProfileInt(GetSection(path).c_str(), GetKey(path).c_str(), (UINT)def, filename.AsNative().c_str());
  293. }
  294. bool IniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, bool def) const
  295. {
  296. return ::GetPrivateProfileInt(GetSection(path).c_str(), GetKey(path).c_str(), def?1:0, filename.AsNative().c_str()) ? true : false;
  297. }
  298. void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, const std::vector<std::byte> &val)
  299. {
  300. MPT_ASSERT(mpt::in_range<UINT>(val.size()));
  301. ::WritePrivateProfileStruct(GetSection(path).c_str(), GetKey(path).c_str(), (LPVOID)val.data(), static_cast<UINT>(val.size()), filename.AsNative().c_str());
  302. }
  303. void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, const mpt::ustring &val)
  304. {
  305. ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::ToWin(val).c_str(), filename.AsNative().c_str());
  306. if(mpt::ToUnicode(mpt::Charset::Locale, mpt::ToCharset(mpt::Charset::Locale, val)) != val) // explicit round-trip
  307. {
  308. // Value is not representable in ANSI CP.
  309. // Now check if the string got stored correctly.
  310. if(ReadSettingRaw(path, mpt::ustring()) != val)
  311. {
  312. // The ini file is probably ANSI encoded.
  313. ConvertToUnicode();
  314. // Re-write non-ansi-representable value.
  315. ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::ToWin(val).c_str(), filename.AsNative().c_str());
  316. }
  317. }
  318. }
  319. void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, double val)
  320. {
  321. ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::tfmt::val(val).c_str(), filename.AsNative().c_str());
  322. }
  323. void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, int32 val)
  324. {
  325. ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::tfmt::val(val).c_str(), filename.AsNative().c_str());
  326. }
  327. void IniFileSettingsBackend::WriteSettingRaw(const SettingPath &path, bool val)
  328. {
  329. ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), mpt::tfmt::val(val).c_str(), filename.AsNative().c_str());
  330. }
  331. void IniFileSettingsBackend::RemoveSettingRaw(const SettingPath &path)
  332. {
  333. ::WritePrivateProfileString(GetSection(path).c_str(), GetKey(path).c_str(), NULL, filename.AsNative().c_str());
  334. }
  335. void IniFileSettingsBackend::RemoveSectionRaw(const mpt::ustring &section)
  336. {
  337. ::WritePrivateProfileSection(mpt::ToWin(section).c_str(), _T("\0"), filename.AsNative().c_str());
  338. }
  339. mpt::winstring IniFileSettingsBackend::GetSection(const SettingPath &path)
  340. {
  341. return mpt::ToWin(path.GetSection());
  342. }
  343. mpt::winstring IniFileSettingsBackend::GetKey(const SettingPath &path)
  344. {
  345. return mpt::ToWin(path.GetKey());
  346. }
  347. IniFileSettingsBackend::IniFileSettingsBackend(const mpt::PathString &filename)
  348. : filename(filename)
  349. {
  350. return;
  351. }
  352. IniFileSettingsBackend::~IniFileSettingsBackend()
  353. {
  354. return;
  355. }
  356. static std::vector<char> ReadFile(const mpt::PathString &filename)
  357. {
  358. mpt::ifstream s(filename, std::ios::binary);
  359. std::vector<char> result;
  360. while(s)
  361. {
  362. char buf[4096];
  363. s.read(buf, 4096);
  364. std::streamsize count = s.gcount();
  365. result.insert(result.end(), buf, buf + count);
  366. }
  367. return result;
  368. }
  369. static void WriteFileUTF16LE(const mpt::PathString &filename, const std::wstring &str)
  370. {
  371. static_assert(sizeof(wchar_t) == 2);
  372. mpt::SafeOutputFile sinifile(filename, std::ios::binary, mpt::FlushMode::Full);
  373. mpt::ofstream& inifile = sinifile;
  374. const uint8 UTF16LE_BOM[] = { 0xff, 0xfe };
  375. inifile.write(reinterpret_cast<const char*>(UTF16LE_BOM), 2);
  376. inifile.write(reinterpret_cast<const char*>(str.c_str()), str.length() * sizeof(std::wstring::value_type));
  377. }
  378. void IniFileSettingsBackend::ConvertToUnicode(const mpt::ustring &backupTag)
  379. {
  380. // Force ini file to be encoded in UTF16.
  381. // This causes WINAPI ini file functions to keep it in UTF16 encoding
  382. // and thus support storing unicode strings uncorrupted.
  383. // This is backwards compatible because even ANSI WINAPI behaves the
  384. // same way in this case.
  385. const std::vector<char> data = ReadFile(filename);
  386. if(!data.empty() && IsTextUnicode(data.data(), mpt::saturate_cast<int>(data.size()), NULL))
  387. {
  388. return;
  389. }
  390. const mpt::PathString backupFilename = filename + mpt::PathString::FromUnicode(backupTag.empty() ? U_(".ansi.bak") : U_(".ansi.") + backupTag + U_(".bak"));
  391. CopyFile(filename.AsNative().c_str(), backupFilename.AsNative().c_str(), FALSE);
  392. WriteFileUTF16LE(filename, mpt::ToWide(mpt::Charset::Locale, mpt::buffer_cast<std::string>(data)));
  393. }
  394. SettingValue IniFileSettingsBackend::ReadSetting(const SettingPath &path, const SettingValue &def) const
  395. {
  396. switch(def.GetType())
  397. {
  398. case SettingTypeBool: return SettingValue(ReadSettingRaw(path, def.as<bool>()), def.GetTypeTag()); break;
  399. case SettingTypeInt: return SettingValue(ReadSettingRaw(path, def.as<int32>()), def.GetTypeTag()); break;
  400. case SettingTypeFloat: return SettingValue(ReadSettingRaw(path, def.as<double>()), def.GetTypeTag()); break;
  401. case SettingTypeString: return SettingValue(ReadSettingRaw(path, def.as<mpt::ustring>()), def.GetTypeTag()); break;
  402. case SettingTypeBinary: return SettingValue(ReadSettingRaw(path, def.as<std::vector<std::byte> >()), def.GetTypeTag()); break;
  403. default: return SettingValue(); break;
  404. }
  405. }
  406. void IniFileSettingsBackend::WriteSetting(const SettingPath &path, const SettingValue &val)
  407. {
  408. ASSERT(val.GetType() != SettingTypeNone);
  409. switch(val.GetType())
  410. {
  411. case SettingTypeBool: WriteSettingRaw(path, val.as<bool>()); break;
  412. case SettingTypeInt: WriteSettingRaw(path, val.as<int32>()); break;
  413. case SettingTypeFloat: WriteSettingRaw(path, val.as<double>()); break;
  414. case SettingTypeString: WriteSettingRaw(path, val.as<mpt::ustring>()); break;
  415. case SettingTypeBinary: WriteSettingRaw(path, val.as<std::vector<std::byte> >()); break;
  416. default: break;
  417. }
  418. }
  419. void IniFileSettingsBackend::RemoveSetting(const SettingPath &path)
  420. {
  421. RemoveSettingRaw(path);
  422. }
  423. void IniFileSettingsBackend::RemoveSection(const mpt::ustring &section)
  424. {
  425. RemoveSectionRaw(section);
  426. }
  427. IniFileSettingsContainer::IniFileSettingsContainer(const mpt::PathString &filename)
  428. : IniFileSettingsBackend(filename)
  429. , SettingsContainer(this)
  430. {
  431. return;
  432. }
  433. IniFileSettingsContainer::~IniFileSettingsContainer()
  434. {
  435. return;
  436. }
  437. DefaultSettingsContainer::DefaultSettingsContainer()
  438. : IniFileSettingsContainer(theApp.GetConfigFileName())
  439. {
  440. return;
  441. }
  442. DefaultSettingsContainer::~DefaultSettingsContainer()
  443. {
  444. return;
  445. }
  446. OPENMPT_NAMESPACE_END