From 4c2469693f5f37f8a4c7d0df6023638cde781fa8 Mon Sep 17 00:00:00 2001 From: Rainer Kottenhoff Date: Thu, 12 Mar 2020 16:59:53 +0100 Subject: [PATCH] + fix: sync. INI-File exclusive reader / writer locking + enh: File-MRU handling --- Versions/build.txt | 2 +- Versions/day.txt | 2 +- minipath/minipath.vcxproj | 2 +- minipath/minipath.vcxproj.filters | 2 +- minipath/src/Config.cpp | 490 ++-- minipath/src/Config.h | 3 +- minipath/src/Dialogs.c | 83 +- minipath/src/Helpers.c | 36 +- minipath/src/SimpleIni.h | 3726 ----------------------------- minipath/src/minipath.c | 107 +- res/Notepad3.exe.manifest.conf | 2 +- src/Config/Config.cpp | 149 +- src/Dialogs.c | 28 +- src/TypeDefs.h | 1 - src/VersionEx.h | 6 +- 15 files changed, 551 insertions(+), 4088 deletions(-) delete mode 100644 minipath/src/SimpleIni.h diff --git a/Versions/build.txt b/Versions/build.txt index b8626c4cf..d00491fd7 100644 --- a/Versions/build.txt +++ b/Versions/build.txt @@ -1 +1 @@ -4 +1 diff --git a/Versions/day.txt b/Versions/day.txt index b661fff6c..a1e0432c9 100644 --- a/Versions/day.txt +++ b/Versions/day.txt @@ -1 +1 @@ -311 +312 diff --git a/minipath/minipath.vcxproj b/minipath/minipath.vcxproj index 5c9515863..593b5ac24 100644 --- a/minipath/minipath.vcxproj +++ b/minipath/minipath.vcxproj @@ -290,6 +290,7 @@ + @@ -298,7 +299,6 @@ - diff --git a/minipath/minipath.vcxproj.filters b/minipath/minipath.vcxproj.filters index c4d415eb9..81ec44a07 100644 --- a/minipath/minipath.vcxproj.filters +++ b/minipath/minipath.vcxproj.filters @@ -66,7 +66,7 @@ H Source Files - + H Source Files diff --git a/minipath/src/Config.cpp b/minipath/src/Config.cpp index 2e0e2ee20..f193bf423 100644 --- a/minipath/src/Config.cpp +++ b/minipath/src/Config.cpp @@ -49,7 +49,7 @@ extern "C" int flagPosParam; // ---------------------------------------------------------------------------- -#include "SimpleIni.h" +#include "..\..\src\Config\SimpleIni.h" #include "Config.h" // ============================================================================ @@ -68,6 +68,45 @@ constexpr bool SI_Success(const SI_Error rc) noexcept { // ============================================================================ + +// ---------------------------------------------------------------------------- +// No mechanism for EXCLUSIVE WRITE / SHARD READ: +// cause we need completely synchronized exclusive access for READ _and_ WRITE +// of complete file to preserve integrety of any transaction +// ---------------------------------------------------------------------------- + +HANDLE AcquireFileLock(LPCWSTR lpIniFilePath, OVERLAPPED& rOvrLpd) +{ + HANDLE hFile = CreateFile(lpIniFilePath, + GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + + DWORD const flags = LOCKFILE_EXCLUSIVE_LOCK; + bool const bLocked = LockFileEx(hFile, flags, 0, MAXDWORD, 0, &rOvrLpd); + + return (bLocked ? hFile : INVALID_HANDLE_VALUE); +} + +// ---------------------------------------------------------------------------- + +bool ReleaseFileLock(HANDLE hFile, OVERLAPPED& rOvrLpd) +{ + bool bUnLocked = true; + if (hFile != INVALID_HANDLE_VALUE) { + FlushFileBuffers(hFile); + bUnLocked = !UnlockFileEx(hFile, 0, MAXDWORD, 0, &rOvrLpd); + CloseHandle(hFile); + } + return bUnLocked; +} + +// ============================================================================ + + +// ============================================================================ + +static OVERLAPPED s_OvrLpd = { 0 }; +static HANDLE s_INI_Hndl = INVALID_HANDLE_VALUE; static CSimpleIni s_INI(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); @@ -76,20 +115,36 @@ extern "C" BOOL LoadIniFile(LPCWSTR lpIniFilePath) s_INI.Reset(); s_INI.SetSpaces(s_bSetSpaces); s_INI.SetMultiLine(s_bUseMultiLine); - SI_Error const rc = s_INI.LoadFile(lpIniFilePath); - return SI_Success(rc); + + s_INI_Hndl = AcquireFileLock(lpIniFilePath, s_OvrLpd); + if (s_INI_Hndl == INVALID_HANDLE_VALUE) { + return false; + } + + return SI_Success(s_INI.LoadFile(s_INI_Hndl)); } + +extern "C" bool IsIniFileLoaded() +{ + return (s_INI_Hndl != INVALID_HANDLE_VALUE); +} + + extern "C" void ReleaseIniFile() { s_INI.Reset(); + ReleaseFileLock(s_INI_Hndl, s_OvrLpd); + s_INI_Hndl = INVALID_HANDLE_VALUE; + ZeroMemory(&s_OvrLpd, sizeof(OVERLAPPED)); } -extern "C" BOOL SaveIniFile(LPCWSTR lpIniFilePath) + +extern "C" BOOL SaveIniFile() { s_INI.SetSpaces(s_bSetSpaces); s_INI.SetMultiLine(s_bUseMultiLine); - SI_Error const rc = s_INI.SaveFile(lpIniFilePath, s_bWriteSIG); + SI_Error const rc = s_INI.SaveFile(s_INI_Hndl, s_bWriteSIG); ReleaseIniFile(); return SI_Success(rc); } @@ -97,7 +152,7 @@ extern "C" BOOL SaveIniFile(LPCWSTR lpIniFilePath) //============================================================================= // -// Manipulation of (cached) INI file +// Manipulation of (cached) ini file // //============================================================================= @@ -243,7 +298,7 @@ extern "C" BOOL IniClearAllSections(LPCWSTR lpPrefix, BOOL bRemoveEmpty) { if (lstrcmpi(section.pItem, lpPrefix) == 0) { - s_INI.Delete(section.pItem, nullptr, bRemoveEmpty); + IniSectionClear(section.pItem, bRemoveEmpty); } } return TRUE; @@ -259,7 +314,17 @@ extern "C" size_t IniFileGetString(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LP LPWSTR lpReturnedString, size_t cchReturnedString) { CSimpleIni Ini(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); - SI_Error const rc = Ini.LoadFile(lpFilePath); + + OVERLAPPED ovrLpd = { 0 }; + HANDLE hFile = AcquireFileLock(lpFilePath, ovrLpd); + if (hFile == INVALID_HANDLE_VALUE) { + StringCchCopyW(lpReturnedString, cchReturnedString, lpDefault); + return (size_t)lstrlen(lpReturnedString); + } + + SI_Error const rc = Ini.LoadFile(hFile); + ReleaseFileLock(hFile, ovrLpd); + if (SI_Success(rc)) { bool bHasMultiple = false; StringCchCopyW(lpReturnedString, cchReturnedString, Ini.GetValue(lpSectionName, lpKeyName, lpDefault, &bHasMultiple)); @@ -277,16 +342,24 @@ extern "C" BOOL IniFileSetString(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LPCW { CSimpleIni Ini(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); Ini.SetSpaces(s_bSetSpaces); - Ini.SetMultiLine(s_bUseMultiLine); - SI_Error rc = Ini.LoadFile(lpFilePath); + + OVERLAPPED ovrLpd = { 0 }; + HANDLE hFile = AcquireFileLock(lpFilePath, ovrLpd); + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + + SI_Error rc = Ini.LoadFile(hFile); if (SI_Success(rc)) { SI_Error const res = Ini.SetValue(lpSectionName, lpKeyName, lpString, nullptr, !s_bUseMultiKey); rc = SI_Success(res) ? SI_Error::SI_OK : SI_Error::SI_FAIL; if (SI_Success(rc)) { - rc = Ini.SaveFile(lpFilePath, s_bWriteSIG); + rc = Ini.SaveFile(hFile, s_bWriteSIG); } } + ReleaseFileLock(hFile, ovrLpd); + return SI_Success(rc); } // ============================================================================ @@ -295,8 +368,18 @@ extern "C" BOOL IniFileSetString(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LPCW extern "C" int IniFileGetInt(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LPCWSTR lpKeyName, int iDefault) { CSimpleIni Ini(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); - SI_Error const rc = Ini.LoadFile(lpFilePath); - if (SI_Success(rc)) { + + OVERLAPPED ovrLpd = { 0 }; + HANDLE hFile = AcquireFileLock(lpFilePath, ovrLpd); + if (hFile == INVALID_HANDLE_VALUE) { + return iDefault; + } + + SI_Error rc = Ini.LoadFile(hFile); + ReleaseFileLock(hFile, ovrLpd); + + if (SI_Success(rc)) + { bool bHasMultiple = false; int const iValue = Ini.GetLongValue(lpSectionName, lpKeyName, (long)iDefault, &bHasMultiple); //assert(!bHasMultiple); @@ -311,13 +394,21 @@ extern "C" BOOL IniFileSetInt(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LPCWSTR { CSimpleIni Ini(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); Ini.SetSpaces(s_bSetSpaces); - Ini.SetMultiLine(s_bUseMultiLine); - SI_Error rc = Ini.LoadFile(lpFilePath); - if (SI_Success(rc)) { - Ini.SetLongValue(lpSectionName, lpKeyName, (long)iValue, nullptr, false, !s_bUseMultiKey); - rc = Ini.SaveFile(lpFilePath, s_bWriteSIG); + + OVERLAPPED ovrLpd = { 0 }; + HANDLE hFile = AcquireFileLock(lpFilePath, ovrLpd); + if (hFile == INVALID_HANDLE_VALUE) { + return false; } - Ini.Reset(); + + SI_Error rc = Ini.LoadFile(hFile); + if (SI_Success(rc)) + { + Ini.SetLongValue(lpSectionName, lpKeyName, (long)iValue, nullptr, false, !s_bUseMultiKey); + rc = Ini.SaveFile(hFile, s_bWriteSIG); + } + ReleaseFileLock(hFile, ovrLpd); + return SI_Success(rc); } // ============================================================================ @@ -326,10 +417,20 @@ extern "C" BOOL IniFileSetInt(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LPCWSTR extern "C" BOOL IniFileGetBool(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LPCWSTR lpKeyName, BOOL bDefault) { CSimpleIni Ini(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); - SI_Error const rc = Ini.LoadFile(lpFilePath); - if (SI_Success(rc)) { + + OVERLAPPED ovrLpd = { 0 }; + HANDLE hFile = AcquireFileLock(lpFilePath, ovrLpd); + if (hFile == INVALID_HANDLE_VALUE) { + return bDefault; + } + + SI_Error rc = Ini.LoadFile(hFile); + ReleaseFileLock(hFile, ovrLpd); + + if (SI_Success(rc)) + { bool bHasMultiple = false; - BOOL const bValue = Ini.GetBoolValue(lpSectionName, lpKeyName, bDefault, &bHasMultiple); + bool const bValue = Ini.GetBoolValue(lpSectionName, lpKeyName, bDefault, &bHasMultiple); //assert(!bHasMultiple); return bValue; } @@ -342,13 +443,21 @@ extern "C" BOOL IniFileSetBool(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LPCWST { CSimpleIni Ini(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); Ini.SetSpaces(s_bSetSpaces); - Ini.SetMultiLine(s_bUseMultiLine); - SI_Error rc = Ini.LoadFile(lpFilePath); - if (SI_Success(rc)) { - Ini.SetBoolValue(lpSectionName, lpKeyName, bValue, nullptr, !s_bUseMultiKey); - rc = Ini.SaveFile(lpFilePath, s_bWriteSIG); + + OVERLAPPED ovrLpd = { 0 }; + HANDLE hFile = AcquireFileLock(lpFilePath, ovrLpd); + if (hFile == INVALID_HANDLE_VALUE) { + return false; } - Ini.Reset(); + + SI_Error rc = Ini.LoadFile(hFile); + if (SI_Success(rc)) + { + Ini.SetBoolValue(lpSectionName, lpKeyName, bValue, nullptr, !s_bUseMultiKey); + rc = Ini.SaveFile(hFile, s_bWriteSIG); + } + ReleaseFileLock(hFile, ovrLpd); + return SI_Success(rc); } // ============================================================================ @@ -357,26 +466,40 @@ extern "C" BOOL IniFileSetBool(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LPCWST extern "C" BOOL IniFileDelete(LPCWSTR lpFilePath, LPCWSTR lpSectionName, LPCWSTR lpKeyName, BOOL bRemoveEmpty) { CSimpleIni Ini(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); - Ini.SetMultiLine(s_bUseMultiLine); Ini.SetSpaces(s_bSetSpaces); - SI_Error rc = Ini.LoadFile(lpFilePath); + + OVERLAPPED ovrLpd = { 0 }; + HANDLE hFile = AcquireFileLock(lpFilePath, ovrLpd); + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + + SI_Error rc = Ini.LoadFile(hFile); if (SI_Success(rc)) { Ini.Delete(lpSectionName, lpKeyName, bRemoveEmpty); - Ini.SetSpaces(s_bSetSpaces); - rc = Ini.SaveFile(lpFilePath, s_bWriteSIG); + rc = Ini.SaveFile(hFile, s_bWriteSIG); } - Ini.Reset(); + ReleaseFileLock(hFile, ovrLpd); + return SI_Success(rc); } // ============================================================================ - extern "C" BOOL IniFileIterateSection(LPCWSTR lpFilePath, LPCWSTR lpSectionName, IterSectionFunc_t callBack) { CSimpleIni Ini(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); - SI_Error rc = Ini.LoadFile(lpFilePath); + + OVERLAPPED ovrLpd = { 0 }; + HANDLE hFile = AcquireFileLock(lpFilePath, ovrLpd); + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + + SI_Error rc = Ini.LoadFile(hFile); + ReleaseFileLock(hFile, ovrLpd); + if (SI_Success(rc)) { bool bHasMultiple = false; @@ -397,7 +520,6 @@ extern "C" BOOL IniFileIterateSection(LPCWSTR lpFilePath, LPCWSTR lpSectionName, - //============================================================================= // // InitDefaultSettings() @@ -441,39 +563,52 @@ void InitDefaultSettings() // int CreateIniFile() { - return(CreateIniFileEx(g_wchIniFile)); -} - -int CreateIniFileEx(LPCWSTR lpszIniFile) { - - if (lpszIniFile && *lpszIniFile) + int result = 0; + if (g_wchIniFile[0] != L'\0') { - WCHAR* pwchTail = StrRChrW(lpszIniFile, nullptr, L'\\'); + WCHAR* pwchTail = StrRChrW(g_wchIniFile, NULL, L'\\'); + if (pwchTail) { *pwchTail = 0; - SHCreateDirectoryEx(nullptr, lpszIniFile, nullptr); + SHCreateDirectoryEx(NULL, g_wchIniFile, NULL); *pwchTail = L'\\'; } - HANDLE hFile = CreateFile(lpszIniFile, - GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile != INVALID_HANDLE_VALUE) { - if (GetFileSize(hFile, nullptr) == 0) { - DWORD dw; - WriteFile(hFile, (LPCVOID)L"\xFEFF[minipath]\r\n", 26, &dw, nullptr); + DWORD dwFileSize = 0UL; + + if (!PathFileExists(g_wchIniFile)) { + HANDLE hFile = CreateFile(g_wchIniFile, + GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); } - CloseHandle(hFile); - return(1); } else { - return(0); + OVERLAPPED ovrLpd = { 0 }; + HANDLE hFile = AcquireFileLock(g_wchIniFile, ovrLpd); + if (hFile == INVALID_HANDLE_VALUE) { + return result; + } + DWORD dwFSHigh = 0UL; + dwFileSize = GetFileSize(hFile, &dwFSHigh); + ReleaseFileLock(hFile, ovrLpd); } + + if ((dwFileSize == 0) && (dwFileSize != INVALID_FILE_SIZE)) { + result = IniFileSetString(g_wchIniFile, L"minipath", NULL, NULL); + } + else { + result = true; + } + + return result; } - else { - return(0); - } + return result; } +//============================================================================= + + //============================================================================= // @@ -682,34 +817,39 @@ int TestIniFile() { // void LoadFlags() { - LoadIniFile(g_wchIniFile); + __try { - const WCHAR* const Settings_Section2 = L"Settings2"; + LoadIniFile(g_wchIniFile); + + const WCHAR* const Settings_Section2 = L"Settings2"; + + if (!IniSectionGetString(Settings_Section2, L"PreferredLanguageLocaleName", L"", + g_tchPrefLngLocName, COUNTOF(g_tchPrefLngLocName))) + { + // try to fetch Locale Name from Notepad3.ini + IniFileGetString(g_wchNP3IniFile, L"Settings2", L"PreferredLanguageLocaleName", L"", + g_tchPrefLngLocName, COUNTOF(g_tchPrefLngLocName)); + } + + if (!flagNoReuseWindow) { + + if (!IniSectionGetInt(Settings_Section2, L"ReuseWindow", 1)) + flagNoReuseWindow = 1; + } + + if (IniSectionGetInt(Settings_Section2, L"PortableMyDocs", 1)) + flagPortableMyDocs = 1; + + if (IniSectionGetInt(Settings_Section2, L"NoFadeHidden", 0)) + flagNoFadeHidden = 1; + + flagToolbarLook = IniSectionGetInt(Settings_Section2, L"ToolbarLook", 0); + flagToolbarLook = max(min(flagToolbarLook, 2), 0); - if (!IniSectionGetString(Settings_Section2, L"PreferredLanguageLocaleName", L"", - g_tchPrefLngLocName, COUNTOF(g_tchPrefLngLocName))) - { - // try to fetch Locale Name from Notepad3.ini - IniFileGetString(g_wchNP3IniFile, L"Settings2", L"PreferredLanguageLocaleName", L"", - g_tchPrefLngLocName, COUNTOF(g_tchPrefLngLocName)); } - - if (!flagNoReuseWindow) { - - if (!IniSectionGetInt(Settings_Section2, L"ReuseWindow", 1)) - flagNoReuseWindow = 1; + __finally { + ReleaseIniFile(); } - - if (IniSectionGetInt(Settings_Section2, L"PortableMyDocs", 1)) - flagPortableMyDocs = 1; - - if (IniSectionGetInt(Settings_Section2, L"NoFadeHidden", 0)) - flagNoFadeHidden = 1; - - flagToolbarLook = IniSectionGetInt(Settings_Section2, L"ToolbarLook", 0); - flagToolbarLook = max(min(flagToolbarLook, 2), 0); - - ReleaseIniFile(); } @@ -733,6 +873,8 @@ extern "C" LPWSTR lpFilterArg; void LoadSettings() { + __try { + LoadIniFile(g_wchIniFile); const WCHAR* const Settings_Section = L"Settings"; @@ -865,7 +1007,10 @@ void LoadSettings() Settings.crCustom[i] = Defaults.crCustom[i]; } - ReleaseIniFile(); + } + __finally { + ReleaseIniFile(); + } } @@ -909,99 +1054,104 @@ void SaveSettings(BOOL bSaveSettingsNow) return; } - LoadIniFile(g_wchIniFile); + __try { - const WCHAR* const Settings_Section = L"Settings"; + LoadIniFile(g_wchIniFile); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"SaveSettings", bSaveSettings); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"SingleClick", bSingleClick); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"TrackSelect", bTrackSelect); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"FullRowSelect", bFullRowSelect); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"UseRecycleBin", fUseRecycleBin); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"NoConfirmDelete", fNoConfirmDelete); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"ClearReadOnly", bClearReadOnly); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"RenameOnCollision", bRenameOnCollision); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"FocusEdit", bFocusEdit); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"AlwaysOnTop", bAlwaysOnTop); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"MinimizeToTray", bMinimizeToTray); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"TransparentMode", g_bTransparentMode); + const WCHAR* const Settings_Section = L"Settings"; + + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"SaveSettings", bSaveSettings); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"SingleClick", bSingleClick); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"TrackSelect", bTrackSelect); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"FullRowSelect", bFullRowSelect); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"UseRecycleBin", fUseRecycleBin); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"NoConfirmDelete", fNoConfirmDelete); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"ClearReadOnly", bClearReadOnly); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"RenameOnCollision", bRenameOnCollision); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"FocusEdit", bFocusEdit); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"AlwaysOnTop", bAlwaysOnTop); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"MinimizeToTray", bMinimizeToTray); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"TransparentMode", g_bTransparentMode); + + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"EscFunction", iEscFunction); + + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"StartupDirectory", iStartupDir); + if (Settings.iStartupDir == 1) { + IniSectionSetString(Settings_Section, L"MRUDirectory", Settings.szCurDir); + } + if (!Settings.bNP3sFavoritesSettings) { + PathRelativeToApp(Settings.g_tchFavoritesDir, wchTmp, COUNTOF(wchTmp), FALSE, TRUE, flagPortableMyDocs); + IniSectionSetString(Settings_Section, L"Favorites", wchTmp); + } + + PathRelativeToApp(Settings.szQuickview, wchTmp, COUNTOF(wchTmp), FALSE, TRUE, flagPortableMyDocs); + IniSectionSetString(Settings_Section, L"Quikview.exe", wchTmp); + SAVE_STRING_IF_NOT_EQ_DEFAULT(L"Quikview.exe", szQuickviewParams); + + PathRelativeToApp(Settings.tchOpenWithDir, wchTmp, COUNTOF(wchTmp), FALSE, TRUE, flagPortableMyDocs); + if (lstrcmp(wchTmp, Defaults.tchOpenWithDir) != 0) { + IniSectionSetString(Settings_Section, L"OpenWithDir", wchTmp); + } + + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"FillMask", dwFillMask); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"SortOptions", nSortFlags); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"SortReverse", fSortRev); + SAVE_STRING_IF_NOT_EQ_DEFAULT(L"FileFilter", tchFilter); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"NegativeFilter", bNegFilter); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"DefColorNoFilter", bDefCrNoFilt); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"DefColorFilter", bDefCrFilter); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"ColorNoFilter", crNoFilt); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"ColorFilter", crFilter); + SAVE_STRING_IF_NOT_EQ_DEFAULT(L"ToolbarButtons", tchToolbarButtons); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"ShowToolbar", bShowToolbar); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"ShowStatusbar", bShowStatusbar); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"ShowDriveBox", bShowDriveBox); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"GotoDlgSizeX", cxGotoDlg); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"OpenWithDlgSizeX", cxOpenWithDlg); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"OpenWithDlgSizeY", cyOpenWithDlg); + SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"CopyMoveDlgSizeX", cxCopyMoveDlg); + + // cleanup + IniSectionDelete(Settings_Section, L"WriteTest", FALSE); + + /* + SaveSettingsNow(): query Window Dimensions + */ + + if (bSaveSettingsNow) + { + WINDOWPLACEMENT wndpl; + ZeroMemory(&wndpl, sizeof(WINDOWPLACEMENT)); + // GetWindowPlacement + wndpl.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(hwndMain, &wndpl); + + Settings.wi.x = wndpl.rcNormalPosition.left; + Settings.wi.y = wndpl.rcNormalPosition.top; + Settings.wi.cx = wndpl.rcNormalPosition.right - wndpl.rcNormalPosition.left; + Settings.wi.cy = wndpl.rcNormalPosition.bottom - wndpl.rcNormalPosition.top; + } + + WCHAR tchPosX[32], tchPosY[32], tchSizeX[32], tchSizeY[32], tchMaximized[32]; + int ResX = GetSystemMetrics(SM_CXSCREEN); + int ResY = GetSystemMetrics(SM_CYSCREEN); + wsprintf(tchPosX, L"%ix%i PosX", ResX, ResY); + wsprintf(tchPosY, L"%ix%i PosY", ResX, ResY); + wsprintf(tchSizeX, L"%ix%i SizeX", ResX, ResY); + wsprintf(tchSizeY, L"%ix%i SizeY", ResX, ResY); + wsprintf(tchMaximized, L"%ix%i Maximized", ResX, ResY); + + const WCHAR* const Windows_Section = L"Window"; + + IniSectionSetInt(Windows_Section, tchPosX, Settings.wi.x); + IniSectionSetInt(Windows_Section, tchPosY, Settings.wi.y); + IniSectionSetInt(Windows_Section, tchSizeX, Settings.wi.cx); + IniSectionSetInt(Windows_Section, tchSizeY, Settings.wi.cy); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"EscFunction", iEscFunction); - - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"StartupDirectory", iStartupDir); - if (Settings.iStartupDir == 1) { - IniSectionSetString(Settings_Section, L"MRUDirectory", Settings.szCurDir); } - if (!Settings.bNP3sFavoritesSettings) { - PathRelativeToApp(Settings.g_tchFavoritesDir, wchTmp, COUNTOF(wchTmp), FALSE, TRUE, flagPortableMyDocs); - IniSectionSetString(Settings_Section, L"Favorites", wchTmp); + __finally { + SaveIniFile(); } - - PathRelativeToApp(Settings.szQuickview, wchTmp, COUNTOF(wchTmp), FALSE, TRUE, flagPortableMyDocs); - IniSectionSetString(Settings_Section, L"Quikview.exe", wchTmp); - SAVE_STRING_IF_NOT_EQ_DEFAULT(L"Quikview.exe", szQuickviewParams); - - PathRelativeToApp(Settings.tchOpenWithDir, wchTmp, COUNTOF(wchTmp), FALSE, TRUE, flagPortableMyDocs); - if (lstrcmp(wchTmp, Defaults.tchOpenWithDir) != 0) { - IniSectionSetString(Settings_Section, L"OpenWithDir", wchTmp); - } - - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"FillMask", dwFillMask); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"SortOptions", nSortFlags); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"SortReverse", fSortRev); - SAVE_STRING_IF_NOT_EQ_DEFAULT(L"FileFilter", tchFilter); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"NegativeFilter", bNegFilter); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"DefColorNoFilter", bDefCrNoFilt); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"DefColorFilter", bDefCrFilter); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"ColorNoFilter", crNoFilt); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"ColorFilter", crFilter); - SAVE_STRING_IF_NOT_EQ_DEFAULT(L"ToolbarButtons", tchToolbarButtons); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"ShowToolbar", bShowToolbar); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"ShowStatusbar", bShowStatusbar); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Bool, L"ShowDriveBox", bShowDriveBox); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"GotoDlgSizeX", cxGotoDlg); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"OpenWithDlgSizeX", cxOpenWithDlg); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"OpenWithDlgSizeY", cyOpenWithDlg); - SAVE_VALUE_IF_NOT_EQ_DEFAULT(Int, L"CopyMoveDlgSizeX", cxCopyMoveDlg); - - // cleanup - IniSectionDelete(Settings_Section, L"WriteTest", FALSE); - - /* - SaveSettingsNow(): query Window Dimensions - */ - - if (bSaveSettingsNow) - { - WINDOWPLACEMENT wndpl; - ZeroMemory(&wndpl, sizeof(WINDOWPLACEMENT)); - // GetWindowPlacement - wndpl.length = sizeof(WINDOWPLACEMENT); - GetWindowPlacement(hwndMain, &wndpl); - - Settings.wi.x = wndpl.rcNormalPosition.left; - Settings.wi.y = wndpl.rcNormalPosition.top; - Settings.wi.cx = wndpl.rcNormalPosition.right - wndpl.rcNormalPosition.left; - Settings.wi.cy = wndpl.rcNormalPosition.bottom - wndpl.rcNormalPosition.top; - } - - WCHAR tchPosX[32], tchPosY[32], tchSizeX[32], tchSizeY[32], tchMaximized[32]; - int ResX = GetSystemMetrics(SM_CXSCREEN); - int ResY = GetSystemMetrics(SM_CYSCREEN); - wsprintf(tchPosX, L"%ix%i PosX", ResX, ResY); - wsprintf(tchPosY, L"%ix%i PosY", ResX, ResY); - wsprintf(tchSizeX, L"%ix%i SizeX", ResX, ResY); - wsprintf(tchSizeY, L"%ix%i SizeY", ResX, ResY); - wsprintf(tchMaximized, L"%ix%i Maximized", ResX, ResY); - - const WCHAR* const Windows_Section = L"Window"; - - IniSectionSetInt(Windows_Section, tchPosX, Settings.wi.x); - IniSectionSetInt(Windows_Section, tchPosY, Settings.wi.y); - IniSectionSetInt(Windows_Section, tchSizeX, Settings.wi.cx); - IniSectionSetInt(Windows_Section, tchSizeY, Settings.wi.cy); - - SaveIniFile(g_wchIniFile); } //============================================================================= diff --git a/minipath/src/Config.h b/minipath/src/Config.h index 69083f3cd..a4c4a9aff 100644 --- a/minipath/src/Config.h +++ b/minipath/src/Config.h @@ -34,12 +34,11 @@ extern "C" { int FindIniFile(); int TestIniFile(); int CreateIniFile(); - int CreateIniFileEx(LPCWSTR); // ---------------------------------------------------------------------------- BOOL LoadIniFile(LPCWSTR lpIniFilePath); - BOOL SaveIniFile(LPCWSTR lpIniFilePath); + BOOL SaveIniFile(); void ReleaseIniFile(); size_t IniSectionGetString(LPCWSTR lpSectionName, LPCWSTR lpKeyName, LPCWSTR lpDefault, diff --git a/minipath/src/Dialogs.c b/minipath/src/Dialogs.c index 4045705fc..c3d1477de 100644 --- a/minipath/src/Dialogs.c +++ b/minipath/src/Dialogs.c @@ -2687,87 +2687,92 @@ INT_PTR CALLBACK FindTargetDlgProc(HWND hwnd,UINT umsg,WPARAM wParam,LPARAM lPar int i; // input validation - if ((IsDlgButtonChecked(hwnd,IDC_TARGET) && GetDlgItemText(hwnd,IDC_TARGETPATH,tch,COUNTOF(tch)) == 0) || - (IsDlgButtonChecked(hwnd,IDC_SENDDROPMSG) && StrIsEmpty(szTargetWndClass)) || - (IsDlgButtonChecked(hwnd,IDC_USEDDE) && - (GetDlgItemText(hwnd,IDC_DDEMSG,tch,COUNTOF(tch)) == 0 || - GetDlgItemText(hwnd,IDC_DDEAPP,tch,COUNTOF(tch)) == 0 || - GetDlgItemText(hwnd,IDC_DDETOPIC,tch,COUNTOF(tch)) == 0))) + if ((IsDlgButtonChecked(hwnd, IDC_TARGET) && GetDlgItemText(hwnd, IDC_TARGETPATH, tch, COUNTOF(tch)) == 0) || + (IsDlgButtonChecked(hwnd, IDC_SENDDROPMSG) && StrIsEmpty(szTargetWndClass)) || + (IsDlgButtonChecked(hwnd, IDC_USEDDE) && + (GetDlgItemText(hwnd, IDC_DDEMSG, tch, COUNTOF(tch)) == 0 || + GetDlgItemText(hwnd, IDC_DDEAPP, tch, COUNTOF(tch)) == 0 || + GetDlgItemText(hwnd, IDC_DDETOPIC, tch, COUNTOF(tch)) == 0))) - ErrorMessage(1,IDS_ERR_INVALIDTARGET); + ErrorMessage(1, IDS_ERR_INVALIDTARGET); - else { + else { + + __try { LoadIniFile(g_wchIniFile); + const WCHAR* const TargetApp_Section = L"Target Application"; i = (BST_CHECKED == IsDlgButtonChecked(hwnd, IDC_LAUNCH)); eUseTargetApplication = ((i) ? UTA_UNDEFINED : UTA_LAUNCH_TARGET); - IniSectionSetInt(TargetApp_Section,L"UseTargetApplication",eUseTargetApplication); + IniSectionSetInt(TargetApp_Section, L"UseTargetApplication", eUseTargetApplication); if (eUseTargetApplication != UTA_UNDEFINED) { - GetDlgItemText(hwnd,IDC_TARGETPATH,tch,COUNTOF(tch)); - ExtractFirstArgument(tch,szTargetApplication,szTargetApplicationParams); + GetDlgItemText(hwnd, IDC_TARGETPATH, tch, COUNTOF(tch)); + ExtractFirstArgument(tch, szTargetApplication, szTargetApplicationParams); } else { - lstrcpy(szTargetApplication,L""); - lstrcpy(szTargetApplicationParams,L""); + lstrcpy(szTargetApplication, L""); + lstrcpy(szTargetApplicationParams, L""); } - IniSectionSetString(TargetApp_Section,L"TargetApplicationPath",szTargetApplication); - IniSectionSetString(TargetApp_Section,L"TargetApplicationParams",szTargetApplicationParams); + IniSectionSetString(TargetApp_Section, L"TargetApplicationPath", szTargetApplication); + IniSectionSetString(TargetApp_Section, L"TargetApplicationParams", szTargetApplicationParams); if (eUseTargetApplication == UTA_UNDEFINED) { eTargetApplicationMode = TAM_ALWAYS_RUN; - IniSectionSetInt(TargetApp_Section,L"TargetApplicationMode",eTargetApplicationMode); + IniSectionSetInt(TargetApp_Section, L"TargetApplicationMode", eTargetApplicationMode); } else { - if (BST_CHECKED == IsDlgButtonChecked(hwnd,IDC_ALWAYSRUN)) { + if (BST_CHECKED == IsDlgButtonChecked(hwnd, IDC_ALWAYSRUN)) { eTargetApplicationMode = TAM_ALWAYS_RUN; - IniSectionSetInt(TargetApp_Section,L"TargetApplicationMode",eTargetApplicationMode); + IniSectionSetInt(TargetApp_Section, L"TargetApplicationMode", eTargetApplicationMode); } - else if (BST_CHECKED == IsDlgButtonChecked(hwnd,IDC_SENDDROPMSG)) { + else if (BST_CHECKED == IsDlgButtonChecked(hwnd, IDC_SENDDROPMSG)) { eTargetApplicationMode = TAM_SEND_DROP_MSG; - IniSectionSetInt(TargetApp_Section,L"TargetApplicationMode",eTargetApplicationMode); + IniSectionSetInt(TargetApp_Section, L"TargetApplicationMode", eTargetApplicationMode); } else { eTargetApplicationMode = TAM_SEND_DDE_MSG; - IniSectionSetInt(TargetApp_Section,L"TargetApplicationMode",eTargetApplicationMode); + IniSectionSetInt(TargetApp_Section, L"TargetApplicationMode", eTargetApplicationMode); } } - if (BST_CHECKED == IsDlgButtonChecked(hwnd,IDC_SENDDROPMSG) && !i) { - lstrcpy(szTargetApplicationWndClass,szTargetWndClass); - IniSectionSetString(TargetApp_Section,L"TargetApplicationWndClass",szTargetApplicationWndClass); + if (BST_CHECKED == IsDlgButtonChecked(hwnd, IDC_SENDDROPMSG) && !i) { + lstrcpy(szTargetApplicationWndClass, szTargetWndClass); + IniSectionSetString(TargetApp_Section, L"TargetApplicationWndClass", szTargetApplicationWndClass); } else { - lstrcpy(szTargetApplicationWndClass,L""); - IniSectionSetString(TargetApp_Section,L"TargetApplicationWndClass",szTargetApplicationWndClass); + lstrcpy(szTargetApplicationWndClass, L""); + IniSectionSetString(TargetApp_Section, L"TargetApplicationWndClass", szTargetApplicationWndClass); } - i = (BST_CHECKED == IsDlgButtonChecked(hwnd,IDC_USEDDE)); + i = (BST_CHECKED == IsDlgButtonChecked(hwnd, IDC_USEDDE)); if (i) - GetDlgItemText(hwnd,IDC_DDEMSG,szDDEMsg,COUNTOF(szDDEMsg)); + GetDlgItemText(hwnd, IDC_DDEMSG, szDDEMsg, COUNTOF(szDDEMsg)); else - lstrcpy(szDDEMsg,L""); - IniSectionSetString(TargetApp_Section,L"DDEMessage",szDDEMsg); + lstrcpy(szDDEMsg, L""); + IniSectionSetString(TargetApp_Section, L"DDEMessage", szDDEMsg); if (i) - GetDlgItemText(hwnd,IDC_DDEAPP,szDDEApp,COUNTOF(szDDEApp)); + GetDlgItemText(hwnd, IDC_DDEAPP, szDDEApp, COUNTOF(szDDEApp)); else - lstrcpy(szDDEApp,L""); - IniSectionSetString(TargetApp_Section,L"DDEApplication",szDDEApp); + lstrcpy(szDDEApp, L""); + IniSectionSetString(TargetApp_Section, L"DDEApplication", szDDEApp); if (i) - GetDlgItemText(hwnd,IDC_DDETOPIC,szDDETopic,COUNTOF(szDDETopic)); + GetDlgItemText(hwnd, IDC_DDETOPIC, szDDETopic, COUNTOF(szDDETopic)); else - lstrcpy(szDDETopic,L""); - IniSectionSetString(TargetApp_Section,L"DDETopic",szDDETopic); + lstrcpy(szDDETopic, L""); + IniSectionSetString(TargetApp_Section, L"DDETopic", szDDETopic); - SaveIniFile(g_wchIniFile); - - EndDialog(hwnd,IDOK); } + __finally { + SaveIniFile(); + } + EndDialog(hwnd, IDOK); } + } break; diff --git a/minipath/src/Helpers.c b/minipath/src/Helpers.c index cc741f51b..5d12bc37d 100644 --- a/minipath/src/Helpers.c +++ b/minipath/src/Helpers.c @@ -1529,28 +1529,33 @@ BOOL MRU_Load(LPMRULIST pmru) { WCHAR tchItem[1024]; MRU_Empty(pmru); - LoadIniFile(g_wchIniFile); + __try { + LoadIniFile(g_wchIniFile); - const WCHAR* const RegKey_Section = pmru->szRegKey; + const WCHAR* const RegKey_Section = pmru->szRegKey; - for (i = 0; i < pmru->iSize; i++) { - StringCchPrintf(tchName, COUNTOF(tchName), L"%.2i", i + 1); - if (IniSectionGetString(RegKey_Section, tchName, L"", tchItem, COUNTOF(tchItem))) { - size_t const len = (size_t)lstrlen(tchItem); - if ((len > 0) && (tchItem[0] == L'"') && (tchItem[len - 1] == L'"')) { - MoveMemory(tchItem, (tchItem + 1), len * sizeof(WCHAR)); - tchItem[len - 2] = L'\0'; // clear dangling '"' + for (i = 0; i < pmru->iSize; i++) { + StringCchPrintf(tchName, COUNTOF(tchName), L"%.2i", i + 1); + if (IniSectionGetString(RegKey_Section, tchName, L"", tchItem, COUNTOF(tchItem))) { + size_t const len = (size_t)lstrlen(tchItem); + if ((len > 0) && (tchItem[0] == L'"') && (tchItem[len - 1] == L'"')) { + MoveMemory(tchItem, (tchItem + 1), len * sizeof(WCHAR)); + tchItem[len - 2] = L'\0'; // clear dangling '"' + } + pmru->pszItems[n++] = StrDup(tchItem); } - pmru->pszItems[n++] = StrDup(tchItem); } } - ReleaseIniFile(); + __finally { + ReleaseIniFile(); + } return(1); } BOOL MRU_Save(LPMRULIST pmru) { - if (LoadIniFile(g_wchIniFile)) { + __try { + LoadIniFile(g_wchIniFile); WCHAR tchName[32]; WCHAR tchItem[1024] = { L'\0' }; @@ -1565,10 +1570,11 @@ BOOL MRU_Save(LPMRULIST pmru) { IniSectionSetString(RegKey_Section, tchName, tchItem); } } - SaveIniFile(g_wchIniFile); - return TRUE; } - return FALSE; + __finally { + SaveIniFile(g_wchIniFile); + } + return TRUE; } void MRU_LoadToCombobox(HWND hwnd,LPCWSTR pszKey) diff --git a/minipath/src/SimpleIni.h b/minipath/src/SimpleIni.h deleted file mode 100644 index 20edafe9b..000000000 --- a/minipath/src/SimpleIni.h +++ /dev/null @@ -1,3726 +0,0 @@ -// encoding: UTF-8 -/** @mainpage - - -
Library SimpleIni -
File SimpleIni.h -
Author Brodie Thiesfield [code at jellycan dot com] -
Source https://github.com/brofield/simpleini -
Version 4.17 -
- - Jump to the @link CSimpleIniTempl CSimpleIni @endlink interface documentation. - - @section intro INTRODUCTION - - This component allows an INI-style configuration file to be used on both - Windows and Linux/Unix. It is fast, simple and source code using this - component will compile unchanged on either OS. - - - @section features FEATURES - - - MIT Licence allows free use in all software (including GPL and commercial) - - multi-platform (Windows CE/9x/NT..10/etc, Linux, MacOSX, Unix) - - loading and saving of INI-style configuration files - - configuration files can have any newline format on all platforms - - liberal acceptance of file format - - key/values with no section - - removal of whitespace around sections, keys and values - - support for multi-line values (values with embedded newline characters) - - optional support for multiple keys with the same name - - optional case-insensitive sections and keys (for ASCII characters only) - - saves files with sections and keys in the same order as they were loaded - - preserves comments on the file, section and keys where possible. - - supports both char or wchar_t programming interfaces - - supports both MBCS (system locale) and UTF-8 file encodings - - system locale does not need to be UTF-8 on Linux/Unix to load UTF-8 file - - support for non-ASCII characters in section, keys, values and comments - - support for non-standard character types or file encodings - via user-written converter classes - - support for adding/modifying values programmatically - - compiles cleanly in the following compilers: - - Windows/VC6 (warning level 3) - - Windows/VC.NET 2003 (warning level 4) - - Windows/VC 2005 (warning level 4) - - Linux/gcc (-Wall) - - - @section usage USAGE SUMMARY - - -# Define the appropriate symbol for the converter you wish to use and - include the SimpleIni.h header file. If no specific converter is defined - then the default converter is used. The default conversion mode uses - SI_CONVERT_WIN32 on Windows and SI_CONVERT_GENERIC on all other - platforms. If you are using ICU then SI_CONVERT_ICU is supported on all - platforms. - -# Declare an instance the appropriate class. Note that the following - definitions are just shortcuts for commonly used types. Other types - (PRUnichar, unsigned short, unsigned char) are also possible. - -
Interface Case-sensitive Load UTF-8 Load MBCS Typedef -
SI_CONVERT_GENERIC -
char No Yes Yes #1 CSimpleIniA -
char Yes Yes Yes CSimpleIniCaseA -
wchar_t No Yes Yes CSimpleIniW -
wchar_t Yes Yes Yes CSimpleIniCaseW -
SI_CONVERT_WIN32 -
char No No #2 Yes CSimpleIniA -
char Yes Yes Yes CSimpleIniCaseA -
wchar_t No Yes Yes CSimpleIniW -
wchar_t Yes Yes Yes CSimpleIniCaseW -
SI_CONVERT_ICU -
char No Yes Yes CSimpleIniA -
char Yes Yes Yes CSimpleIniCaseA -
UChar No Yes Yes CSimpleIniW -
UChar Yes Yes Yes CSimpleIniCaseW -
- #1 On Windows you are better to use CSimpleIniA with SI_CONVERT_WIN32.
- #2 Only affects Windows. On Windows this uses MBCS functions and - so may fold case incorrectly leading to uncertain results. - -# Call LoadData() or LoadFile() to load and parse the INI configuration file - -# Access and modify the data of the file using the following functions - -
GetAllSections Return all section names -
GetAllKeys Return all key names within a section -
GetAllValues Return all values within a section & key -
GetSection Return all key names and values in a section -
GetSectionSize Return the number of keys in a section -
GetValue Return a value for a section & key -
SetValue Add or update a value for a section & key -
Delete Remove a section, or a key from a section -
- -# Call Save() or SaveFile() to save the INI configuration data - - @section iostreams IO STREAMS - - SimpleIni supports reading from and writing to STL IO streams. Enable this - by defining SI_SUPPORT_IOSTREAMS before including the SimpleIni.h header - file. Ensure that if the streams are backed by a file (e.g. ifstream or - ofstream) then the flag ios_base::binary has been used when the file was - opened. - - @section multiline MULTI-LINE VALUES - - Values that span multiple lines are created using the following format. - -
-        key = <<
-
-    Note the following:
-    - The text used for ENDTAG can be anything and is used to find
-      where the multi-line text ends.
-    - The newline after ENDTAG in the start tag, and the newline
-      before ENDTAG in the end tag is not included in the data value.
-    - The ending tag must be on it's own line with no whitespace before
-      or after it.
-    - The multi-line value is modified at load so that each line in the value
-      is delimited by a single '\\n' character on all platforms. At save time
-      it will be converted into the newline format used by the current
-      platform.
-
-    @section comments COMMENTS
-
-    Comments are preserved in the file within the following restrictions:
-    - Every file may have a single "file comment". It must start with the
-      first character in the file, and will end with the first non-comment
-      line in the file.
-    - Every section may have a single "section comment". It will start
-      with the first comment line following the file comment, or the last
-      data entry. It ends at the beginning of the section.
-    - Every key may have a single "key comment". This comment will start
-      with the first comment line following the section start, or the file
-      comment if there is no section name.
-    - Comments are set at the time that the file, section or key is first
-      created. The only way to modify a comment on a section or a key is to
-      delete that entry and recreate it with the new comment. There is no
-      way to change the file comment.
-
-    @section save SAVE ORDER
-
-    The sections and keys are written out in the same order as they were
-    read in from the file. Sections and keys added to the data after the
-    file has been loaded will be added to the end of the file when it is
-    written. There is no way to specify the location of a section or key
-    other than in first-created, first-saved order.
-
-    @section notes NOTES
-
-    - To load UTF-8 data on Windows 95, you need to use Microsoft Layer for
-      Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU.
-    - When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked.
-    - When using SI_CONVERT_ICU, ICU header files must be on the include
-      path and icuuc.lib must be linked in.
-    - To load a UTF-8 file on Windows AND expose it with SI_CHAR == char,
-      you should use SI_CONVERT_GENERIC.
-    - The collation (sorting) order used for sections and keys returned from
-      iterators is NOT DEFINED. If collation order of the text is important
-      then it should be done yourself by either supplying a replacement
-      SI_STRLESS class, or by sorting the strings external to this library.
-    - Usage of the  header on Windows can be disabled by defining
-      SI_NO_MBCS. This is defined automatically on Windows CE platforms.
-    - Not thread-safe so manage your own locking
-
-    @section contrib CONTRIBUTIONS
-    
-    - 2010/05/03: Tobias Gehrig: added GetDoubleValue()
-
-    @section licence MIT LICENCE
-
-    The licence text below is the boilerplate "MIT Licence" used from:
-    http://www.opensource.org/licenses/mit-license.php
-
-    Copyright (c) 2006-2012, Brodie Thiesfield
-
-    Permission is hereby granted, free of charge, to any person obtaining a copy
-    of this software and associated documentation files (the "Software"), to deal
-    in the Software without restriction, including without limitation the rights
-    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-    copies of the Software, and to permit persons to whom the Software is furnished
-    to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be included in
-    all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-    FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-    COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
-
-#ifndef INCLUDED_SimpleIni_h
-#define INCLUDED_SimpleIni_h
-
-#if defined(_MSC_VER) && (_MSC_VER >= 1020)
-# pragma once
-#endif
-
-// Disable these warnings in MSVC:
-//  4127 "conditional expression is constant" as the conversion classes trigger
-//  it with the statement if (sizeof(SI_CHAR) == sizeof(char)). This test will
-//  be optimized away in a release build.
-//  4503 'insert' : decorated name length exceeded, name was truncated
-//  4702 "unreachable code" as the MS STL header causes it in release mode.
-//  Again, the code causing the warning will be cleaned up by the compiler.
-//  4786 "identifier truncated to 256 characters" as this is thrown hundreds
-//  of times VC6 as soon as STL is used.
-#ifdef _MSC_VER
-# pragma warning (push)
-# pragma warning (disable: 4127 4503 4702 4786)
-#endif
-
-
-// Defines the conversion classes for different libraries. Before including
-// SimpleIni.h, set the converter that you wish you use by defining one of the
-// following symbols.
-//
-//  SI_CONVERT_GENERIC      Use the Unicode reference conversion library in
-//                          the accompanying files ConvertUTF.h/c
-//  SI_CONVERT_ICU          Use the IBM ICU conversion library. Requires
-//                          ICU headers on include path and icuuc.lib
-//  SI_CONVERT_WIN32        Use the Win32 API functions for conversion.
-
-#if !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU)
-# ifdef _WIN32
-#  define SI_CONVERT_WIN32
-# else
-#  define SI_CONVERT_GENERIC
-# endif
-#endif
-
-#ifdef SI_CONVERT_WIN32
-#define VC_EXTRALEAN 1
-#define WIN32_LEAN_AND_MEAN 1
-#include 
-#endif
-
-#include   // for wcscpy_s, wcscat_s
-#include   // for _countof
-#include 
-#include 
-#include 
-#include 
-#include 
-
-#ifdef SI_SUPPORT_IOSTREAMS
-# include 
-#endif // SI_SUPPORT_IOSTREAMS
-
-#ifdef _DEBUG
-# ifndef assert
-#  include 
-# endif
-# define SI_ASSERT(x)   assert(x)
-#else
-# define SI_ASSERT(x)
-#endif
-
-enum class SI_Error : int {
-    SI_OK       =  0,   //!< No error
-    SI_UPDATED  =  1,   //!< An existing value was updated
-    SI_INSERTED =  2,   //!< A new value was inserted
-
-    // note: test for any error with (retval < 0)
-    SI_FAIL     = -1,   //!< Generic failure
-    SI_NOMEM    = -2,   //!< Out of memory error
-    SI_FILE     = -3    //!< File error (see errno for detail error)
-};
-
-
-#define SI_UTF8_SIGNATURE      "\xEF\xBB\xBF"
-#define SI_UTF16LE_SIGNATURE   "\xFF\xFE"
-#define SI_UTF16BE_SIGNATURE   "\xFE\xFF"
-
-#ifndef MAX_PATH
-#define MAX_PATH   260
-#endif
-
-#ifdef _WIN32
-# define SI_NEWLINE_A   "\r\n"
-# define SI_NEWLINE_W   L"\r\n"
-#else // !_WIN32
-# define SI_NEWLINE_A   "\n"
-# define SI_NEWLINE_W   L"\n"
-#endif // _WIN32
-
-#if defined(SI_CONVERT_ICU)
-# include 
-#endif
-
-#if defined(_WIN32)
-# define SI_HAS_WIDE_FILE
-# define SI_USE_LOCKING_WIDE_FILE
-# define SI_WCHAR_T     wchar_t
-#elif defined(SI_CONVERT_ICU)
-# define SI_HAS_WIDE_FILE
-# define SI_WCHAR_T     UChar
-#endif
-
-// ---------------------------------------------------------------------------
-//                              MAIN TEMPLATE CLASS
-// ---------------------------------------------------------------------------
-
-/** Simple INI file reader.
-
-    This can be instantiated with the choice of unicode or native characterset,
-    and case sensitive or insensitive comparisons of section and key names.
-    The supported combinations are pre-defined with the following typedefs:
-
-    
-        
Interface Case-sensitive Typedef -
char No CSimpleIniA -
char Yes CSimpleIniCaseA -
wchar_t No CSimpleIniW -
wchar_t Yes CSimpleIniCaseW -
- - Note that using other types for the SI_CHAR is supported. For instance, - unsigned char, unsigned short, etc. Note that where the alternative type - is a different size to char/wchar_t you may need to supply new helper - classes for SI_STRLESS and SI_CONVERTER. - */ -template -class CSimpleIniTempl -{ -public: - using SI_CHAR_T = SI_CHAR; - - /** key entry */ - struct Entry { - const SI_CHAR * pItem; - const SI_CHAR * pComment; - int nOrder; - - Entry(const SI_CHAR * a_pszItem = NULL, int a_nOrder = 0) - : pItem(a_pszItem) - , pComment(NULL) - , nOrder(a_nOrder) - { } - Entry(const SI_CHAR * a_pszItem, const SI_CHAR * a_pszComment, int a_nOrder) - : pItem(a_pszItem) - , pComment(a_pszComment) - , nOrder(a_nOrder) - { } - Entry(const Entry & rhs) { operator=(rhs); } - Entry & operator=(const Entry & rhs) { - pItem = rhs.pItem; - pComment = rhs.pComment; - nOrder = rhs.nOrder; - return *this; - } - -#if defined(_MSC_VER) && _MSC_VER <= 1200 - /** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */ - bool operator<(const Entry & rhs) const { return LoadOrder()(*this, rhs); } - bool operator>(const Entry & rhs) const { return LoadOrder()(rhs, *this); } -#endif - - /** Strict less ordering by name of key only */ - struct KeyOrder { - bool operator()(const Entry & lhs, const Entry & rhs) const { - const static SI_STRLESS isLess = SI_STRLESS(); - return isLess(lhs.pItem, rhs.pItem); - } - }; - - /** Strict less ordering by order, and then name of key */ - struct LoadOrder { - bool operator()(const Entry & lhs, const Entry & rhs) const { - if (lhs.nOrder != rhs.nOrder) { - return lhs.nOrder < rhs.nOrder; - } - return KeyOrder()(lhs.pItem, rhs.pItem); - } - }; - }; - - /** map keys to values */ - using TKeyVal = std::multimap; - - /** map sections to key/value map */ - using TSection = std::map; - - /** set of dependent string pointers. Note that these pointers are - dependent on memory owned by CSimpleIni. - */ - using TNamesDepend = std::list; - - /** interface definition for the OutputWriter object to pass to Save() - in order to output the INI file data. - */ - class OutputWriter { - public: - OutputWriter() { } - virtual ~OutputWriter() { } - virtual void Write(const char * a_pBuf) = 0; - private: - OutputWriter(const OutputWriter &); // disable - OutputWriter & operator=(const OutputWriter &); // disable - }; - - /** OutputWriter class to write the INI data to a file */ - class FileWriter : public OutputWriter { - FILE * m_file; - public: - explicit FileWriter(FILE * a_file) : m_file(a_file) { } - ~FileWriter() { fflush(m_file); } - void Write(const char * a_pBuf) override { - fputs(a_pBuf, m_file); - } - private: - FileWriter() = delete; - FileWriter(const FileWriter &); // disable - FileWriter & operator=(const FileWriter &); // disable - }; - -#ifdef SI_USE_LOCKING_WIDE_FILE - class FileHndlWriter : public OutputWriter { - HANDLE m_file; - public: - FileHndlWriter(HANDLE a_file) : m_file(a_file) { SetFilePointer(m_file, 0, 0, FILE_BEGIN); SetEndOfFile(m_file); }; - ~FileHndlWriter() { FlushFileBuffers(m_file); } - void Write(const char* a_pBuf) override { - DWORD nBytesWritten = 0UL; - DWORD const nBytesToWrite = static_cast(strlen(a_pBuf)); - WriteFile(m_file, a_pBuf, nBytesToWrite, &nBytesWritten, NULL); - } - private: - FileHndlWriter() = delete; - FileHndlWriter(const FileHndlWriter&) = delete; // disable - FileHndlWriter& operator=(const FileHndlWriter&) = delete; // disable - }; -#endif - - /** OutputWriter class to write the INI data to a string */ - class StringWriter : public OutputWriter { - std::string & m_string; - public: - explicit StringWriter(std::string & a_string) : m_string(a_string) { } - void Write(const char * a_pBuf) override { - m_string.append(a_pBuf); - } - private: - StringWriter(const StringWriter &); // disable - StringWriter & operator=(const StringWriter &); // disable - }; - -#ifdef SI_SUPPORT_IOSTREAMS - /** OutputWriter class to write the INI data to an ostream */ - class StreamWriter : public OutputWriter { - std::ostream & m_ostream; - public: - explicit StreamWriter(std::ostream & a_ostream) : m_ostream(a_ostream) { } - void Write(const char * a_pBuf) override { - m_ostream << a_pBuf; - } - private: - StreamWriter(const StreamWriter &); // disable - StreamWriter & operator=(const StreamWriter &); // disable - }; -#endif // SI_SUPPORT_IOSTREAMS - - /** Characterset conversion utility class to convert strings to the - same format as is used for the storage. - */ - class Converter : private SI_CONVERTER { - public: - explicit Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { - m_scratch.resize(1024); - } - Converter(const Converter & rhs) { operator=(rhs); } - Converter & operator=(const Converter & rhs) { - m_scratch = rhs.m_scratch; - return *this; - } - bool ConvertToStore(const SI_CHAR * a_pszString) { - size_t uLen = SI_CONVERTER::SizeToStore(a_pszString); - if (uLen == (size_t)(-1)) { - return false; - } - while (uLen > m_scratch.size()) { - m_scratch.resize(m_scratch.size() * 2); - } - return SI_CONVERTER::ConvertToStore( - a_pszString, - const_cast(m_scratch.data()), - m_scratch.size()); - } - const char * Data() { return m_scratch.data(); } - private: - std::string m_scratch; - }; - -public: - /*-----------------------------------------------------------------------*/ - - /** Default constructor. - - @param a_bIsUtf8 See the method SetUnicode() for details. - @param a_bAllowMultiKey See the method SetMultiKey() for details. - @param a_bAllowMultiLine See the method SetMultiLine() for details. - */ - CSimpleIniTempl( - bool a_bIsUtf8 = false, - bool a_bAllowMultiKey = false, - bool a_bAllowMultiLine = false - ); - - /** Destructor */ - ~CSimpleIniTempl(); - - /** Deallocate all memory stored by this object */ - void Reset(); - - /** Has any data been loaded */ - bool IsEmpty() const { return m_data.empty(); } - - /*-----------------------------------------------------------------------*/ - /** @{ @name Settings */ - - /** Set the storage format of the INI data. This affects both the loading - and saving of the INI data using all of the Load/Save API functions. - This value cannot be changed after any INI data has been loaded. - - If the file is not set to Unicode (UTF-8), then the data encoding is - assumed to be the OS native encoding. This encoding is the system - locale on Linux/Unix and the legacy MBCS encoding on Windows NT/2K/XP. - If the storage format is set to Unicode then the file will be loaded - as UTF-8 encoded data regardless of the native file encoding. If - SI_CHAR == char then all of the char* parameters take and return UTF-8 - encoded data regardless of the system locale. - - \param a_bIsUtf8 Assume UTF-8 encoding for the source? - */ - void SetUnicode(bool a_bIsUtf8 = true) { - if (!m_pData) m_bStoreIsUtf8 = a_bIsUtf8; - } - - /** Get the storage format of the INI data. */ - bool IsUnicode() const { return m_bStoreIsUtf8; } - - /** Should multiple identical keys be permitted in the file. If set to false - then the last value encountered will be used as the value of the key. - If set to true, then all values will be available to be queried. For - example, with the following input: - -
-        [section]
-        test=value1
-        test=value2
-        
- - Then with SetMultiKey(true), both of the values "value1" and "value2" - will be returned for the key test. If SetMultiKey(false) is used, then - the value for "test" will only be "value2". This value may be changed - at any time. - - \param a_bAllowMultiKey Allow multi-keys in the source? - */ - void SetMultiKey(bool a_bAllowMultiKey = true) { - m_bAllowMultiKey = a_bAllowMultiKey; - } - - /** Get the storage format of the INI data. */ - bool IsMultiKey() const { return m_bAllowMultiKey; } - - /** Should data values be permitted to span multiple lines in the file. If - set to false then the multi-line construct << - SI_CHAR FORMAT - char same format as when loaded (MBCS or UTF-8) - wchar_t UTF-8 - other UTF-8 - - - Note that comments from the original data is preserved as per the - documentation on comments. The order of the sections and values - from the original file will be preserved. - - Any data prepended or appended to the output device must use the the - same format (MBCS or UTF-8). You may use the GetConverter() method to - convert text to the correct format regardless of the output format - being used by SimpleIni. - - To add a BOM to UTF-8 data, write it out manually at the very beginning - like is done in SaveFile when a_bAddSignature is true. - - @param a_oOutput Output writer to write the data to. - - @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in - UTF-8 format. If it is not UTF-8 then this value is - ignored. Do not set this to true if anything has - already been written to the OutputWriter. - - @return SI_Error See error definitions - */ - SI_Error Save( - OutputWriter & a_oOutput, - bool a_bAddSignature = false - ) const; - -#ifdef SI_SUPPORT_IOSTREAMS - /** Save the INI data to an ostream. See Save() for details. - - @param a_ostream String to have the INI data appended to. - - @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in - UTF-8 format. If it is not UTF-8 then this value is - ignored. Do not set this to true if anything has - already been written to the stream. - - @return SI_Error See error definitions - */ - SI_Error Save( - std::ostream & a_ostream, - bool a_bAddSignature = false - ) const - { - StreamWriter writer(a_ostream); - return Save(writer, a_bAddSignature); - } -#endif // SI_SUPPORT_IOSTREAMS - - /** Append the INI data to a string. See Save() for details. - - @param a_sBuffer String to have the INI data appended to. - - @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in - UTF-8 format. If it is not UTF-8 then this value is - ignored. Do not set this to true if anything has - already been written to the string. - - @return SI_Error See error definitions - */ - SI_Error Save( - std::string & a_sBuffer, - bool a_bAddSignature = false - ) const - { - StringWriter writer(a_sBuffer); - return Save(writer, a_bAddSignature); - } - - /*-----------------------------------------------------------------------*/ - /** @} - @{ @name Accessing INI Data */ - - /** Retrieve all section names. The list is returned as an STL vector of - names and can be iterated or searched as necessary. Note that the - sort order of the returned strings is NOT DEFINED. You can sort - the names into the load order if desired. Search this file for ".sort" - for an example. - - NOTE! This structure contains only pointers to strings. The actual - string data is stored in memory owned by CSimpleIni. Ensure that the - CSimpleIni object is not destroyed or Reset() while these pointers - are in use! - - @param a_names Vector that will receive all of the section - names. See note above! - */ - void GetAllSections( - TNamesDepend & a_names - ) const; - - /** Retrieve all unique key names in a section. The sort order of the - returned strings is NOT DEFINED. You can sort the names into the load - order if desired. Search this file for ".sort" for an example. Only - unique key names are returned. - - NOTE! This structure contains only pointers to strings. The actual - string data is stored in memory owned by CSimpleIni. Ensure that the - CSimpleIni object is not destroyed or Reset() while these strings - are in use! - - @param a_pSection Section to request data for - @param a_names List that will receive all of the key - names. See note above! - - @return true Section was found. - @return false Matching section was not found. - */ - bool GetAllKeys( - const SI_CHAR * a_pSection, - TNamesDepend & a_names - ) const; - - /** Retrieve all values for a specific key. This method can be used when - multiple keys are both enabled and disabled. Note that the sort order - of the returned strings is NOT DEFINED. You can sort the names into - the load order if desired. Search this file for ".sort" for an example. - - NOTE! The returned values are pointers to string data stored in memory - owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed - or Reset while you are using this pointer! - - @param a_pSection Section to search - @param a_pKey Key to search for - @param a_values List to return if the key is not found - - @return true Key was found. - @return false Matching section/key was not found. - */ - bool GetAllValues( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - TNamesDepend & a_values - ) const; - - /** Query the number of keys in a specific section. Note that if multiple - keys are enabled, then this value may be different to the number of - keys returned by GetAllKeys. - - @param a_pSection Section to request data for - - @return -1 Section does not exist in the file - @return >=0 Number of keys in the section - */ - int GetSectionSize( - const SI_CHAR * a_pSection - ) const; - - /** Retrieve all key and value pairs for a section. The data is returned - as a pointer to an STL map and can be iterated or searched as - desired. Note that multiple entries for the same key may exist when - multiple keys have been enabled. - - NOTE! This structure contains only pointers to strings. The actual - string data is stored in memory owned by CSimpleIni. Ensure that the - CSimpleIni object is not destroyed or Reset() while these strings - are in use! - - @param a_pSection Name of the section to return - @return boolean Was a section matching the supplied - name found. - */ - const TKeyVal * GetSection( - const SI_CHAR * a_pSection - ) const; - - /** Retrieve the value for a specific key. If multiple keys are enabled - (see SetMultiKey) then only the first value associated with that key - will be returned, see GetAllValues for getting all values with multikey. - - NOTE! The returned value is a pointer to string data stored in memory - owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed - or Reset while you are using this pointer! - - @param a_pSection Section to search - @param a_pKey Key to search for - @param a_pDefault Value to return if the key is not found - @param a_pHasMultiple Optionally receive notification of if there are - multiple entries for this key. - - @return a_pDefault Key was not found in the section - @return other Value of the key - */ - const SI_CHAR * GetValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pDefault = nullptr, - bool * a_pHasMultiple = nullptr - ) const; - - /** Retrieve a numeric value for a specific key. If multiple keys are enabled - (see SetMultiKey) then only the first value associated with that key - will be returned, see GetAllValues for getting all values with multikey. - - @param a_pSection Section to search - @param a_pKey Key to search for - @param a_nDefault Value to return if the key is not found - @param a_pHasMultiple Optionally receive notification of if there are - multiple entries for this key. - - @return a_nDefault Key was not found in the section - @return other Value of the key - */ - long GetLongValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - long a_nDefault = 0, - bool * a_pHasMultiple = nullptr - ) const; - - long long GetLongLongValue( - const SI_CHAR* a_pSection, - const SI_CHAR* a_pKey, - long long a_nDefault = 0, - bool* a_pHasMultiple = nullptr - ) const; - - /** Retrieve a numeric value for a specific key. If multiple keys are enabled - (see SetMultiKey) then only the first value associated with that key - will be returned, see GetAllValues for getting all values with multikey. - - @param a_pSection Section to search - @param a_pKey Key to search for - @param a_nDefault Value to return if the key is not found - @param a_pHasMultiple Optionally receive notification of if there are - multiple entries for this key. - - @return a_nDefault Key was not found in the section - @return other Value of the key - */ - double GetDoubleValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - double a_nDefault = 0, - bool * a_pHasMultiple = nullptr - ) const; - - /** Retrieve a boolean value for a specific key. If multiple keys are enabled - (see SetMultiKey) then only the first value associated with that key - will be returned, see GetAllValues for getting all values with multikey. - - Strings starting with "t", "y", "on" or "1" are returned as logically true. - Strings starting with "f", "n", "of" or "0" are returned as logically false. - For all other values the default is returned. Character comparisons are - case-insensitive. - - @param a_pSection Section to search - @param a_pKey Key to search for - @param a_bDefault Value to return if the key is not found - @param a_pHasMultiple Optionally receive notification of if there are - multiple entries for this key. - - @return a_nDefault Key was not found in the section - @return other Value of the key - */ - bool GetBoolValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bDefault = false, - bool * a_pHasMultiple = nullptr - ) const; - - /** Add or update a section or value. This will always insert - when multiple keys are enabled. - - @param a_pSection Section to add or update - @param a_pKey Key to add or update. Set to NULL to - create an empty section. - @param a_pValue Value to set. Set to NULL to create an - empty section. - @param a_pComment Comment to be associated with the section or the - key. If a_pKey is NULL then it will be associated - with the section, otherwise the key. Note that a - comment may be set ONLY when the section or key is - first created (i.e. when this function returns the - value SI_INSERTED). If you wish to create a section - with a comment then you need to create the section - separately to the key. The comment string must be - in full comment form already (have a comment - character starting every line). - @param a_bForceReplace Should all existing values in a multi-key INI - file be replaced with this entry. This option has - no effect if not using multi-key files. The - difference between Delete/SetValue and SetValue - with a_bForceReplace = true, is that the load - order and comment will be preserved this way. - - @return SI_Error See error definitions - @return SI_UPDATED Value was updated - @return SI_INSERTED Value was inserted - */ - SI_Error SetValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - const SI_CHAR * a_pComment = NULL, - bool a_bForceReplace = false - ) - { - return AddEntry(a_pSection, a_pKey, a_pValue, a_pComment, a_bForceReplace, true); - } - - /** Add or update a numeric value. This will always insert - when multiple keys are enabled. - - @param a_pSection Section to add or update - @param a_pKey Key to add or update. - @param a_nValue Value to set. - @param a_pComment Comment to be associated with the key. See the - notes on SetValue() for comments. - @param a_bUseHex By default the value will be written to the file - in decimal format. Set this to true to write it - as hexadecimal. - @param a_bForceReplace Should all existing values in a multi-key INI - file be replaced with this entry. This option has - no effect if not using multi-key files. The - difference between Delete/SetLongValue and - SetLongValue with a_bForceReplace = true, is that - the load order and comment will be preserved this - way. - - @return SI_Error See error definitions - @return SI_UPDATED Value was updated - @return SI_INSERTED Value was inserted - */ - SI_Error SetLongValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - long a_nValue, - const SI_CHAR * a_pComment = NULL, - bool a_bUseHex = false, - bool a_bForceReplace = false - ); - - SI_Error SetLongLongValue( - const SI_CHAR* a_pSection, - const SI_CHAR* a_pKey, - long long a_nValue, - const SI_CHAR* a_pComment = NULL, - bool a_bUseHex = false, - bool a_bForceReplace = false - ); - - /** Add or update a double value. This will always insert - when multiple keys are enabled. - - @param a_pSection Section to add or update - @param a_pKey Key to add or update. - @param a_nValue Value to set. - @param a_pComment Comment to be associated with the key. See the - notes on SetValue() for comments. - @param a_bForceReplace Should all existing values in a multi-key INI - file be replaced with this entry. This option has - no effect if not using multi-key files. The - difference between Delete/SetDoubleValue and - SetDoubleValue with a_bForceReplace = true, is that - the load order and comment will be preserved this - way. - - @return SI_Error See error definitions - @return SI_UPDATED Value was updated - @return SI_INSERTED Value was inserted - */ - SI_Error SetDoubleValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - double a_nValue, - const SI_CHAR * a_pComment = NULL, - bool a_bForceReplace = false - ); - - /** Add or update a boolean value. This will always insert - when multiple keys are enabled. - - @param a_pSection Section to add or update - @param a_pKey Key to add or update. - @param a_bValue Value to set. - @param a_pComment Comment to be associated with the key. See the - notes on SetValue() for comments. - @param a_bForceReplace Should all existing values in a multi-key INI - file be replaced with this entry. This option has - no effect if not using multi-key files. The - difference between Delete/SetBoolValue and - SetBoolValue with a_bForceReplace = true, is that - the load order and comment will be preserved this - way. - - @return SI_Error See error definitions - @return SI_UPDATED Value was updated - @return SI_INSERTED Value was inserted - */ - SI_Error SetBoolValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bValue, - const SI_CHAR * a_pComment = NULL, - bool a_bForceReplace = false - ); - - /** Delete an entire section, or a key from a section. Note that the - data returned by GetSection is invalid and must not be used after - anything has been deleted from that section using this method. - Note when multiple keys is enabled, this will delete all keys with - that name; to selectively delete individual key/values, use - DeleteValue. - - @param a_pSection Section to delete key from, or if - a_pKey is NULL, the section to remove. - @param a_pKey Key to remove from the section. Set to - NULL to remove the entire section. - @param a_bRemoveEmpty If the section is empty after this key has - been deleted, should the empty section be - removed? - - @return true Key or section was deleted. - @return false Key or section was not found. - */ - bool Delete( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bRemoveEmpty = false - ); - - /** Delete an entire section, or a key from a section. If value is - provided, only remove keys with the value. Note that the data - returned by GetSection is invalid and must not be used after - anything has been deleted from that section using this method. - Note when multiple keys is enabled, all keys with the value will - be deleted. - - @param a_pSection Section to delete key from, or if - a_pKey is NULL, the section to remove. - @param a_pKey Key to remove from the section. Set to - NULL to remove the entire section. - @param a_pValue Value of key to remove from the section. - Set to NULL to remove all keys. - @param a_bRemoveEmpty If the section is empty after this key has - been deleted, should the empty section be - removed? - - @return true Key/value or section was deleted. - @return false Key/value or section was not found. - */ - bool DeleteValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - bool a_bRemoveEmpty = false - ); - - /*-----------------------------------------------------------------------*/ - /** @} - @{ @name Converter */ - - /** Return a conversion object to convert text to the same encoding - as is used by the Save(), SaveFile() and SaveString() functions. - Use this to prepare the strings that you wish to append or prepend - to the output INI data. - */ - Converter GetConverter() const { - return Converter(m_bStoreIsUtf8); - } - - /*-----------------------------------------------------------------------*/ - /** @} */ - -private: - // copying is not permitted - CSimpleIniTempl(const CSimpleIniTempl &); // disabled - CSimpleIniTempl & operator=(const CSimpleIniTempl &); // disabled - - /** Parse the data looking for a file comment and store it if found. - */ - SI_Error FindFileComment( - SI_CHAR *& a_pData, - bool a_bCopyStrings - ); - - /** Parse the data looking for the next valid entry. The memory pointed to - by a_pData is modified by inserting NULL characters. The pointer is - updated to the current location in the block of text. - */ - bool FindEntry( - SI_CHAR *& a_pData, - const SI_CHAR *& a_pSection, - const SI_CHAR *& a_pKey, - const SI_CHAR *& a_pVal, - const SI_CHAR *& a_pComment - ) const; - - /** Add the section/key/value to our data. - - @param a_pSection Section name. Sections will be created if they - don't already exist. - @param a_pKey Key name. May be NULL to create an empty section. - Existing entries will be updated. New entries will - be created. - @param a_pValue Value for the key. - @param a_pComment Comment to be associated with the section or the - key. If a_pKey is NULL then it will be associated - with the section, otherwise the key. This must be - a string in full comment form already (have a - comment character starting every line). - @param a_bForceReplace Should all existing values in a multi-key INI - file be replaced with this entry. This option has - no effect if not using multi-key files. The - difference between Delete/AddEntry and AddEntry - with a_bForceReplace = true, is that the load - order and comment will be preserved this way. - @param a_bCopyStrings Should copies of the strings be made or not. - If false then the pointers will be used as is. - */ - SI_Error AddEntry( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - const SI_CHAR * a_pComment, - bool a_bForceReplace, - bool a_bCopyStrings - ); - - /** Is the supplied character a whitespace character? */ - inline bool IsSpace(SI_CHAR ch) const { - return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); - } - - /** Does the supplied character start a comment line? */ - inline bool IsComment(SI_CHAR ch) const { - return (ch == ';' || ch == '#'); - } - - - /** Skip over a newline character (or characters) for either DOS or UNIX */ - inline void SkipNewLine(SI_CHAR *& a_pData) const { - a_pData += (*a_pData == '\r' && *(a_pData+1) == '\n') ? 2 : 1; - } - - /** Make a copy of the supplied string, replacing the original pointer */ - SI_Error CopyString(const SI_CHAR *& a_pString); - - /** Delete a string from the copied strings buffer if necessary */ - void DeleteString(const SI_CHAR * a_pString); - - /** Internal use of our string comparison function */ - bool IsLess(const SI_CHAR * a_pLeft, const SI_CHAR * a_pRight) const { - const static SI_STRLESS isLess = SI_STRLESS(); - return isLess(a_pLeft, a_pRight); - } - - bool IsMultiLineTag(const SI_CHAR * a_pVal) const; - bool IsMultiLineData(const SI_CHAR * a_pData) const; - bool LoadMultiLineText( - SI_CHAR *& a_pData, - const SI_CHAR *& a_pVal, - const SI_CHAR * a_pTagName, - bool a_bAllowBlankLinesInComment = false - ) const; - bool IsNewLineChar(SI_CHAR a_c) const; - - bool OutputMultiLineText( - OutputWriter & a_oOutput, - Converter & a_oConverter, - const SI_CHAR * a_pText - ) const; - -private: - /** Copy of the INI file data in our character format. This will be - modified when parsed to have NULL characters added after all - interesting string entries. All of the string pointers to sections, - keys and values point into this block of memory. - */ - SI_CHAR * m_pData; - - /** Length of the data that we have stored. Used when deleting strings - to determine if the string is stored here or in the allocated string - buffer. - */ - size_t m_uDataLen; - - /** File comment for this data, if one exists. */ - const SI_CHAR * m_pFileComment; - - /** Parsed INI data. Section -> (Key -> Value). */ - TSection m_data; - - /** This vector stores allocated memory for copies of strings that have - been supplied after the file load. It will be empty unless SetValue() - has been called. - */ - TNamesDepend m_strings; - - /** Is the format of our datafile UTF-8 or MBCS? */ - bool m_bStoreIsUtf8; - - /** Are multiple values permitted for the same key? */ - bool m_bAllowMultiKey; - - /** Are data values permitted to span multiple lines? */ - bool m_bAllowMultiLine; - - /** Should spaces be written out surrounding the equals sign? */ - bool m_bSpaces; - - /** Next order value, used to ensure sections and keys are output in the - same order that they are loaded/added. - */ - int m_nOrder; -}; - -// --------------------------------------------------------------------------- -// IMPLEMENTATION -// --------------------------------------------------------------------------- - -template -CSimpleIniTempl::CSimpleIniTempl( - bool a_bIsUtf8, - bool a_bAllowMultiKey, - bool a_bAllowMultiLine - ) - : m_pData(0) - , m_uDataLen(0) - , m_pFileComment(NULL) - , m_bStoreIsUtf8(a_bIsUtf8) - , m_bAllowMultiKey(a_bAllowMultiKey) - , m_bAllowMultiLine(a_bAllowMultiLine) - , m_bSpaces(true) - , m_nOrder(0) -{ } - -template -CSimpleIniTempl::~CSimpleIniTempl() -{ - Reset(); -} - -template -void -CSimpleIniTempl::Reset() -{ - // remove all data - delete[] m_pData; - m_pData = NULL; - m_uDataLen = 0; - m_pFileComment = NULL; - if (!m_data.empty()) { - m_data.erase(m_data.begin(), m_data.end()); - } - - // remove all strings - if (!m_strings.empty()) { - typename TNamesDepend::iterator i = m_strings.begin(); - for (; i != m_strings.end(); ++i) { - delete[] const_cast(i->pItem); - } - m_strings.erase(m_strings.begin(), m_strings.end()); - } -} - -template -SI_Error -CSimpleIniTempl::LoadFile( - const char * a_pszFile - ) -{ - strcpy_s(m_FilePathA, _countof(m_FilePathA), a_pszFile); - FILE * fp = nullptr; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - fopen_s(&fp, a_pszFile, "rb"); -#else // !__STDC_WANT_SECURE_LIB__ - fp = fopen(a_pszFile, "rb"); -#endif // __STDC_WANT_SECURE_LIB__ - if (!fp) { - return SI_FILE; - } - SI_Error rc = LoadFile(fp); - fclose(fp); - return rc; -} - -#ifdef SI_HAS_WIDE_FILE - -#ifdef SI_USE_LOCKING_WIDE_FILE - -template -SI_Error -CSimpleIniTempl::LoadFile( - const SI_WCHAR_T* a_pwszFile -) -{ - HANDLE hFile = CreateFile(a_pwszFile, - GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - - if (hFile == INVALID_HANDLE_VALUE) { - return SI_Error::SI_FILE; - } - - SI_Error rc = LoadFile(hFile); - - CloseHandle(hFile); - return rc; -} - -#else - -template -SI_Error -CSimpleIniTempl::LoadFile( - const SI_WCHAR_T * a_pwszFile, - bool a_bUseLocking - ) -{ -#ifdef _WIN32 - FILE * fp = nullptr; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - _wfopen_s(&fp, a_pwszFile, L"rb"); -#else // !__STDC_WANT_SECURE_LIB__ - fp = _wfopen(a_pwszFile, L"rb"); -#endif // __STDC_WANT_SECURE_LIB__ - if (!fp) return SI_Error::SI_FILE; - SI_Error rc = LoadFile(fp); - fclose(fp); - return rc; -#else // !_WIN32 (therefore SI_CONVERT_ICU) - char szFile[256]; - u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); - return LoadFile(szFile); -#endif // _WIN32 -} - -#endif // SI_USE_LOCKING_WIDE_FILE - -#endif // SI_HAS_WIDE_FILE - - -#ifdef SI_USE_LOCKING_WIDE_FILE - -template -SI_Error -CSimpleIniTempl::LoadFile( - HANDLE a_hFile -) -{ - DWORD dwFileSizeHigh = 0UL; - DWORD const dwFileSize = GetFileSize(a_hFile, &dwFileSizeHigh); - if ((dwFileSize == INVALID_FILE_SIZE) || (dwFileSizeHigh != 0UL)) { - return SI_Error::SI_FILE; - } - - // allocate and ensure NULL terminated - auto* pData = new(std::nothrow) char[dwFileSize + 1]; - if (!pData) { - return SI_Error::SI_NOMEM; - } - pData[dwFileSize] = '\0'; - - // load the raw file data - DWORD nBytesRead = 0UL; - BOOL const result = ReadFile(a_hFile, pData, dwFileSize, &nBytesRead, nullptr); - if ((result == FALSE) || (nBytesRead != dwFileSize)) { - delete[] pData; - return SI_Error::SI_FILE; - } - - // convert the raw data to unicode - SI_Error const rc = LoadData(pData, (size_t)nBytesRead); - delete[] pData; - return rc; -} - -#else - -template -SI_Error -CSimpleIniTempl::LoadFile( - FILE * a_fpFile - ) -{ - // load the raw file data - int retval = fseek(a_fpFile, 0, SEEK_END); - if (retval != 0) { - return SI_Error::SI_FILE; - } - long lSize = ftell(a_fpFile); - if (lSize < 0) { - return SI_Error::SI_FILE; - } - if (lSize == 0) { - return SI_Error::SI_OK; - } - - // allocate and ensure NULL terminated - auto * pData = new(std::nothrow) char[lSize+1]; - if (!pData) { - return SI_Error::SI_NOMEM; - } - pData[lSize] = 0; - - // load data into buffer - fseek(a_fpFile, 0, SEEK_SET); - size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); - if (uRead != (size_t) lSize) { - delete[] pData; - return SI_Error::SI_FILE; - } - - // convert the raw data to unicode - SI_Error rc = LoadData(pData, uRead); - delete[] pData; - return rc; -} - -#endif - -template -SI_Error -CSimpleIniTempl::LoadData( - const char * a_pData, - size_t a_uDataLen - ) -{ - if (!a_pData) { - return SI_Error::SI_OK; - } - - char* pDataUTF16toUTF8 = nullptr; - - // if the UTF-8 BOM exists, consume it and set mode to unicode, if we have - // already loaded data and try to change mode half-way through then this will - // be ignored and we will assert in debug versions - if (a_uDataLen >= 3 && memcmp(a_pData, SI_UTF8_SIGNATURE, 3) == 0) { - a_pData += 3; - a_uDataLen -= 3; - SI_ASSERT(m_bStoreIsUtf8 || !m_pData); // we don't expect mixed mode data - SetUnicode(); - } - else { // check for UTF-16 LE/BE - - if (a_uDataLen >= 2 && memcmp(a_pData, SI_UTF16BE_SIGNATURE, 2) == 0) - { - _swab((char*)a_pData, (char*)a_pData, (int)a_uDataLen); - } - if (a_uDataLen >= 2 && memcmp(a_pData, SI_UTF16LE_SIGNATURE, 2) == 0) - { - // convert to UTF-8 - pDataUTF16toUTF8 = new(std::nothrow) char[a_uDataLen * 3 + 1]; - if (!pDataUTF16toUTF8) { - return SI_Error::SI_NOMEM; - } - - auto const convCnt = (size_t)::WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)a_pData + 1, (int)(a_uDataLen / sizeof(WCHAR) - 1), - (LPSTR)pDataUTF16toUTF8, (int)(a_uDataLen * 3 + 1), nullptr, nullptr); - if (convCnt == 0) { - delete[] pDataUTF16toUTF8; - return SI_Error::SI_FAIL; - } - pDataUTF16toUTF8[convCnt] = '\0'; - - a_pData = pDataUTF16toUTF8; - a_uDataLen = convCnt; - SI_ASSERT(m_bStoreIsUtf8 || !m_pData); // we don't expect mixed mode data - SetUnicode(); - } - } - - if (a_uDataLen == 0) { - delete[] pDataUTF16toUTF8; - return SI_Error::SI_OK; - } - - // determine the length of the converted data - SI_CONVERTER converter(m_bStoreIsUtf8); - size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen); - if (uLen == (size_t)(-1)) { - delete[] pDataUTF16toUTF8; - return SI_Error::SI_FAIL; - } - - // allocate memory for the data, ensure that there is a NULL - // terminator wherever the converted data ends - auto * pData = new(std::nothrow) SI_CHAR[uLen+1]; - if (!pData) { - delete[] pDataUTF16toUTF8; - return SI_Error::SI_NOMEM; - } - memset(pData, 0, sizeof(SI_CHAR)*(uLen+1)); - - // convert the data - if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { - delete[] pDataUTF16toUTF8; - delete[] pData; - return SI_Error::SI_FAIL; - } - - delete[] pDataUTF16toUTF8; // done - pDataUTF16toUTF8 = nullptr; - - // parse it - const static SI_CHAR empty = 0; - SI_CHAR * pWork = pData; - const SI_CHAR * pSection = ∅ - const SI_CHAR * pItem = NULL; - const SI_CHAR * pVal = NULL; - const SI_CHAR * pComment = NULL; - - // We copy the strings if we are loading data into this class when we - // already have stored some. - bool bCopyStrings = (m_pData != NULL); - - // find a file comment if it exists, this is a comment that starts at the - // beginning of the file and continues until the first blank line. - SI_Error rc = FindFileComment(pWork, bCopyStrings); - if (rc < SI_Error::SI_OK) return rc; - - // add every entry in the file to the data table - while (FindEntry(pWork, pSection, pItem, pVal, pComment)) { - rc = AddEntry(pSection, pItem, pVal, pComment, false, bCopyStrings); - if (rc < SI_Error::SI_OK) return rc; - } - - // store these strings if we didn't copy them - if (bCopyStrings) { - delete[] pData; - } - else { - m_pData = pData; - m_uDataLen = uLen+1; - } - return SI_Error::SI_OK; -} - -#ifdef SI_SUPPORT_IOSTREAMS -template -SI_Error -CSimpleIniTempl::LoadData( - std::istream & a_istream - ) -{ - std::string strData; - char szBuf[512]; - do { - a_istream.get(szBuf, sizeof(szBuf), '\0'); - strData.append(szBuf); - } - while (a_istream.good()); - return LoadData(strData); -} -#endif // SI_SUPPORT_IOSTREAMS - -template -SI_Error -CSimpleIniTempl::FindFileComment( - SI_CHAR *& a_pData, - bool a_bCopyStrings - ) -{ - // there can only be a single file comment - if (m_pFileComment) { - return SI_Error::SI_OK; - } - - // Load the file comment as multi-line text, this will modify all of - // the newline characters to be single \n chars - if (!LoadMultiLineText(a_pData, m_pFileComment, NULL, false)) { - return SI_Error::SI_OK; - } - - // copy the string if necessary - if (a_bCopyStrings) { - SI_Error rc = CopyString(m_pFileComment); - if (rc < SI_Error::SI_OK) return rc; - } - - return SI_Error::SI_OK; -} - -template -bool -CSimpleIniTempl::FindEntry( - SI_CHAR *& a_pData, - const SI_CHAR *& a_pSection, - const SI_CHAR *& a_pKey, - const SI_CHAR *& a_pVal, - const SI_CHAR *& a_pComment - ) const -{ - a_pComment = NULL; - - SI_CHAR * pTrail = NULL; - while (*a_pData) { - // skip spaces and empty lines - while (*a_pData && IsSpace(*a_pData)) { - ++a_pData; - } - if (!*a_pData) { - break; - } - - // skip processing of comment lines but keep a pointer to - // the start of the comment. - if (IsComment(*a_pData)) { - LoadMultiLineText(a_pData, a_pComment, NULL, true); - continue; - } - - // process section names - if (*a_pData == '[') { - // skip leading spaces - ++a_pData; - while (*a_pData && IsSpace(*a_pData)) { - ++a_pData; - } - - // find the end of the section name (it may contain spaces) - // and convert it to lowercase as necessary - a_pSection = a_pData; - while (*a_pData && *a_pData != ']' && !IsNewLineChar(*a_pData)) { - ++a_pData; - } - - // if it's an invalid line, just skip it - if (*a_pData != ']') { - continue; - } - - // remove trailing spaces from the section - pTrail = a_pData - 1; - while (pTrail >= a_pSection && IsSpace(*pTrail)) { - --pTrail; - } - ++pTrail; - *pTrail = 0; - - // skip to the end of the line - ++a_pData; // safe as checked that it == ']' above - while (*a_pData && !IsNewLineChar(*a_pData)) { - ++a_pData; - } - - a_pKey = NULL; - a_pVal = NULL; - return true; - } - - // find the end of the key name (it may contain spaces) - // and convert it to lowercase as necessary - a_pKey = a_pData; - while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData)) { - ++a_pData; - } - - // if it's an invalid line, just skip it - if (*a_pData != '=') { - continue; - } - - // empty keys are invalid - if (a_pKey == a_pData) { - while (*a_pData && !IsNewLineChar(*a_pData)) { - ++a_pData; - } - continue; - } - - // remove trailing spaces from the key - pTrail = a_pData - 1; - while (pTrail >= a_pKey && IsSpace(*pTrail)) { - --pTrail; - } - ++pTrail; - *pTrail = 0; - - // skip leading whitespace on the value - ++a_pData; // safe as checked that it == '=' above - while (*a_pData && !IsNewLineChar(*a_pData) && IsSpace(*a_pData)) { - ++a_pData; - } - - // find the end of the value which is the end of this line - a_pVal = a_pData; - while (*a_pData && !IsNewLineChar(*a_pData)) { - ++a_pData; - } - - // remove trailing spaces from the value - pTrail = a_pData - 1; - if (*a_pData) { // prepare for the next round - SkipNewLine(a_pData); - } - while (pTrail >= a_pVal && IsSpace(*pTrail)) { - --pTrail; - } - ++pTrail; - *pTrail = 0; - - // check for multi-line entries - if (m_bAllowMultiLine && IsMultiLineTag(a_pVal)) { - // skip the "<<<" to get the tag that will end the multiline - const SI_CHAR * pTagName = a_pVal + 3; - return LoadMultiLineText(a_pData, a_pVal, pTagName); - } - - // return the standard entry - return true; - } - - return false; -} - -template -bool -CSimpleIniTempl::IsMultiLineTag( - const SI_CHAR * a_pVal - ) const -{ - // check for the "<<<" prefix for a multi-line entry - if (*a_pVal++ != '<') return false; - if (*a_pVal++ != '<') return false; - if (*a_pVal++ != '<') return false; - return true; -} - -template -bool -CSimpleIniTempl::IsMultiLineData( - const SI_CHAR * a_pData - ) const -{ - // data is multi-line if it has any of the following features: - // * whitespace prefix - // * embedded newlines - // * whitespace suffix - - // empty string - if (!*a_pData) { - return false; - } - - // check for prefix - if (IsSpace(*a_pData)) { - return true; - } - - // embedded newlines - while (*a_pData) { - if (IsNewLineChar(*a_pData)) { - return true; - } - ++a_pData; - } - - // check for suffix - if (IsSpace(*--a_pData)) { - return true; - } - - return false; -} - -template -bool -CSimpleIniTempl::IsNewLineChar( - SI_CHAR a_c - ) const -{ - return (a_c == '\n' || a_c == '\r'); -} - -template -bool -CSimpleIniTempl::LoadMultiLineText( - SI_CHAR *& a_pData, - const SI_CHAR *& a_pVal, - const SI_CHAR * a_pTagName, - bool a_bAllowBlankLinesInComment - ) const -{ - // we modify this data to strip all newlines down to a single '\n' - // character. This means that on Windows we need to strip out some - // characters which will make the data shorter. - // i.e. LINE1-LINE1\r\nLINE2-LINE2\0 will become - // LINE1-LINE1\nLINE2-LINE2\0 - // The pDataLine entry is the pointer to the location in memory that - // the current line needs to start to run following the existing one. - // This may be the same as pCurrLine in which case no move is needed. - SI_CHAR * pDataLine = a_pData; - SI_CHAR * pCurrLine; - - // value starts at the current line - a_pVal = a_pData; - - // find the end tag. This tag must start in column 1 and be - // followed by a newline. No whitespace removal is done while - // searching for this tag. - SI_CHAR cEndOfLineChar = *a_pData; - for(;;) { - // if we are loading comments then we need a comment character as - // the first character on every line - if (!a_pTagName && !IsComment(*a_pData)) { - // if we aren't allowing blank lines then we're done - if (!a_bAllowBlankLinesInComment) { - break; - } - - // if we are allowing blank lines then we only include them - // in this comment if another comment follows, so read ahead - // to find out. - SI_CHAR * pCurr = a_pData; - int nNewLines = 0; - while (IsSpace(*pCurr)) { - if (IsNewLineChar(*pCurr)) { - ++nNewLines; - SkipNewLine(pCurr); - } - else { - ++pCurr; - } - } - - // we have a comment, add the blank lines to the output - // and continue processing from here - if (IsComment(*pCurr)) { - for (; nNewLines > 0; --nNewLines) *pDataLine++ = '\n'; - a_pData = pCurr; - continue; - } - - // the comment ends here - break; - } - - // find the end of this line - pCurrLine = a_pData; - while (*a_pData && !IsNewLineChar(*a_pData)) ++a_pData; - - // move this line down to the location that it should be if necessary - if (pDataLine < pCurrLine) { - auto nLen = (size_t) (a_pData - pCurrLine); - memmove(pDataLine, pCurrLine, nLen * sizeof(SI_CHAR)); - pDataLine[nLen] = '\0'; - } - - // end the line with a NULL - cEndOfLineChar = *a_pData; - *a_pData = 0; - - // if are looking for a tag then do the check now. This is done before - // checking for end of the data, so that if we have the tag at the end - // of the data then the tag is removed correctly. - if (a_pTagName && - (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine))) - { - break; - } - - // if we are at the end of the data then we just automatically end - // this entry and return the current data. - if (!cEndOfLineChar) { - return true; - } - - // otherwise we need to process this newline to ensure that it consists - // of just a single \n character. - pDataLine += (a_pData - pCurrLine); - *a_pData = cEndOfLineChar; - SkipNewLine(a_pData); - *pDataLine++ = '\n'; - } - - // if we didn't find a comment at all then return false - if (a_pVal == a_pData) { - a_pVal = NULL; - return false; - } - - // the data (which ends at the end of the last line) needs to be - // null-terminated BEFORE before the newline character(s). If the - // user wants a new line in the multi-line data then they need to - // add an empty line before the tag. - *--pDataLine = '\0'; - - // if looking for a tag and if we aren't at the end of the data, - // then move a_pData to the start of the next line. - if (a_pTagName && cEndOfLineChar) { - SI_ASSERT(IsNewLineChar(cEndOfLineChar)); - *a_pData = cEndOfLineChar; - SkipNewLine(a_pData); - } - - return true; -} - -template -SI_Error -CSimpleIniTempl::CopyString( - const SI_CHAR *& a_pString - ) -{ - size_t uLen = 0; - if (sizeof(SI_CHAR) == sizeof(char)) { - uLen = strlen((const char *)a_pString); - } - else if (sizeof(SI_CHAR) == sizeof(wchar_t)) { - uLen = wcslen((const wchar_t *)a_pString); - } - else { - for ( ; a_pString[uLen]; ++uLen) /*loop*/ ; - } - ++uLen; // NULL character - auto * pCopy = new(std::nothrow) SI_CHAR[uLen]; - if (!pCopy) { - return SI_Error::SI_NOMEM; - } - memcpy(pCopy, a_pString, sizeof(SI_CHAR)*uLen); - m_strings.push_back(pCopy); - a_pString = pCopy; - return SI_Error::SI_OK; -} - -template -SI_Error -CSimpleIniTempl::AddEntry( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - const SI_CHAR * a_pComment, - bool a_bForceReplace, - bool a_bCopyStrings - ) -{ - SI_Error rc; - bool bInserted = false; - - SI_ASSERT(!a_pComment || IsComment(*a_pComment)); - - // if we are copying strings then make a copy of the comment now - // because we will need it when we add the entry. - if (a_bCopyStrings && a_pComment) { - rc = CopyString(a_pComment); - if (rc < SI_Error::SI_OK) return rc; - } - - // create the section entry if necessary - typename TSection::iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - // if the section doesn't exist then we need a copy as the - // string needs to last beyond the end of this function - if (a_bCopyStrings) { - rc = CopyString(a_pSection); - if (rc < SI_Error::SI_OK) return rc; - } - - // only set the comment if this is a section only entry - Entry oSection(a_pSection, ++m_nOrder); - if (a_pComment && (!a_pKey || !a_pValue)) { - oSection.pComment = a_pComment; - } - - typename TSection::value_type oEntry(oSection, TKeyVal()); - using SectionIterator = typename TSection::iterator; - std::pair i = m_data.insert(oEntry); - iSection = i.first; - bInserted = true; - } - if (!a_pKey || !a_pValue) { - // section only entries are specified with pItem and pVal as NULL - return bInserted ? SI_Error::SI_INSERTED : SI_Error::SI_UPDATED; - } - - // check for existence of the key - TKeyVal & keyval = iSection->second; - typename TKeyVal::iterator iKey = keyval.find(a_pKey); - - // remove all existing entries but save the load order and - // comment of the first entry - int nLoadOrder = ++m_nOrder; - if (iKey != keyval.end() && m_bAllowMultiKey && a_bForceReplace) { - const SI_CHAR * pComment = NULL; - while (iKey != keyval.end() && !IsLess(a_pKey, iKey->first.pItem)) { - if (iKey->first.nOrder < nLoadOrder) { - nLoadOrder = iKey->first.nOrder; - pComment = iKey->first.pComment; - } - ++iKey; - } - if (pComment) { - DeleteString(a_pComment); - a_pComment = pComment; - CopyString(a_pComment); - } - Delete(a_pSection, a_pKey); - iKey = keyval.end(); - } - - // make string copies if necessary - bool bForceCreateNewKey = m_bAllowMultiKey && !a_bForceReplace; - if (a_bCopyStrings) { - if (bForceCreateNewKey || iKey == keyval.end()) { - // if the key doesn't exist then we need a copy as the - // string needs to last beyond the end of this function - // because we will be inserting the key next - rc = CopyString(a_pKey); - if (rc < SI_Error::SI_OK) return rc; - } - - // we always need a copy of the value - rc = CopyString(a_pValue); - if (rc < SI_Error::SI_OK) return rc; - } - - // create the key entry - if (iKey == keyval.end() || bForceCreateNewKey) { - Entry oKey(a_pKey, nLoadOrder); - if (a_pComment) { - oKey.pComment = a_pComment; - } - typename TKeyVal::value_type oEntry(oKey, static_cast(NULL)); - iKey = keyval.insert(oEntry); - bInserted = true; - } - iKey->second = a_pValue; - return bInserted ? SI_Error::SI_INSERTED : SI_Error::SI_UPDATED; -} - -template -const SI_CHAR * -CSimpleIniTempl::GetValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pDefault, - bool * a_pHasMultiple - ) const -{ - if (a_pHasMultiple) { - *a_pHasMultiple = false; - } - if (!a_pSection || !a_pKey) { - return a_pDefault; - } - typename TSection::const_iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return a_pDefault; - } - typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); - if (iKeyVal == iSection->second.end()) { - return a_pDefault; - } - - // check for multiple entries with the same key - if (m_bAllowMultiKey && a_pHasMultiple) { - typename TKeyVal::const_iterator iTemp = iKeyVal; - if (++iTemp != iSection->second.end()) { - if (!IsLess(a_pKey, iTemp->first.pItem)) { - *a_pHasMultiple = true; - } - } - } - - return iKeyVal->second; -} - -template -long -CSimpleIniTempl::GetLongValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - long a_nDefault, - bool * a_pHasMultiple - ) const -{ - // return the default if we don't have a value - const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); - if (!pszValue || !*pszValue) return a_nDefault; - - // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII - char szValue[64] = { 0 }; - SI_CONVERTER c(m_bStoreIsUtf8); - if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { - return a_nDefault; - } - - // handle the value as hex if prefaced with "0x" - long nValue = a_nDefault; - char * pszSuffix = szValue; - if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { - if (!szValue[2]) return a_nDefault; - nValue = strtol(&szValue[2], &pszSuffix, 16); - } - else { - nValue = strtol(szValue, &pszSuffix, 10); - } - - // any invalid strings will return the default value - if (*pszSuffix) { - return a_nDefault; - } - - return nValue; -} - -template -SI_Error -CSimpleIniTempl::SetLongValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - long a_nValue, - const SI_CHAR * a_pComment, - bool a_bUseHex, - bool a_bForceReplace - ) -{ - // use SetValue to create sections - if (!a_pSection || !a_pKey) return SI_Error::SI_FAIL; - - // convert to an ASCII string - char szInput[64]; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); -#else // !__STDC_WANT_SECURE_LIB__ - sprintf(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); -#endif // __STDC_WANT_SECURE_LIB__ - - // convert to output text - SI_CHAR szOutput[64]; - SI_CONVERTER c(m_bStoreIsUtf8); - c.ConvertFromStore(szInput, strlen(szInput) + 1, - szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); - - // actually add it - return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); -} - -template -long long -CSimpleIniTempl::GetLongLongValue( - const SI_CHAR* a_pSection, - const SI_CHAR* a_pKey, - long long a_nDefault, - bool* a_pHasMultiple -) const -{ - // return the default if we don't have a value - const SI_CHAR* pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); - if (!pszValue || !*pszValue) return a_nDefault; - - // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII - char szValue[128] = { 0 }; - SI_CONVERTER c(m_bStoreIsUtf8); - if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { - return a_nDefault; - } - - // handle the value as hex if prefaced with "0x" - long long nValue = a_nDefault; - char* pszSuffix = szValue; - if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { - if (!szValue[2]) return a_nDefault; - nValue = strtoll(&szValue[2], &pszSuffix, 16); - } - else { - nValue = strtoll(szValue, &pszSuffix, 10); - } - - // any invalid strings will return the default value - if (*pszSuffix) { - return a_nDefault; - } - - return nValue; -} - -template -SI_Error -CSimpleIniTempl::SetLongLongValue( - const SI_CHAR* a_pSection, - const SI_CHAR* a_pKey, - long long a_nValue, - const SI_CHAR* a_pComment, - bool a_bUseHex, - bool a_bForceReplace -) -{ - // use SetValue to create sections - if (!a_pSection || !a_pKey) return SI_Error::SI_FAIL; - - // convert to an ASCII string - char szInput[128]; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - sprintf_s(szInput, a_bUseHex ? "0x%llx" : "%lld", a_nValue); -#else // !__STDC_WANT_SECURE_LIB__ - sprintf(szInput, a_bUseHex ? "0x%llx" : "%lld", a_nValue); -#endif // __STDC_WANT_SECURE_LIB__ - - // convert to output text - SI_CHAR szOutput[64]; - SI_CONVERTER c(m_bStoreIsUtf8); - c.ConvertFromStore(szInput, strlen(szInput) + 1, - szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); - - // actually add it - return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); -} - -template -double -CSimpleIniTempl::GetDoubleValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - double a_nDefault, - bool * a_pHasMultiple - ) const -{ - // return the default if we don't have a value - const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); - if (!pszValue || !*pszValue) return a_nDefault; - - // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII - char szValue[64] = { 0 }; - SI_CONVERTER c(m_bStoreIsUtf8); - if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { - return a_nDefault; - } - - char * pszSuffix = nullptr; - double nValue = strtod(szValue, &pszSuffix); - - // any invalid strings will return the default value - if (!pszSuffix || *pszSuffix) { - return a_nDefault; - } - - return nValue; -} - -template -SI_Error -CSimpleIniTempl::SetDoubleValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - double a_nValue, - const SI_CHAR * a_pComment, - bool a_bForceReplace - ) -{ - // use SetValue to create sections - if (!a_pSection || !a_pKey) return SI_Error::SI_FAIL; - - // convert to an ASCII string - char szInput[64]; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - sprintf_s(szInput, "%f", a_nValue); -#else // !__STDC_WANT_SECURE_LIB__ - sprintf(szInput, "%f", a_nValue); -#endif // __STDC_WANT_SECURE_LIB__ - - // convert to output text - SI_CHAR szOutput[64]; - SI_CONVERTER c(m_bStoreIsUtf8); - c.ConvertFromStore(szInput, strlen(szInput) + 1, - szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); - - // actually add it - return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); -} - -template -bool -CSimpleIniTempl::GetBoolValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bDefault, - bool * a_pHasMultiple - ) const -{ - // return the default if we don't have a value - const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); - if (!pszValue || !*pszValue) return a_bDefault; - - // we only look at the minimum number of characters - switch (pszValue[0]) { - case 't': case 'T': // true - case 'y': case 'Y': // yes - case '9': case '8': case '7': case '6': // != 0 - case '5': case '4': case '3': case '2': // != 0 - case '1': // 1 (one) - return true; - - case 'f': case 'F': // false - case 'n': case 'N': // no - case '0': // 0 (zero) - return false; - - case 'o': case 'O': - if (pszValue[1] == 'n' || pszValue[1] == 'N') return true; // on - if (pszValue[1] == 'f' || pszValue[1] == 'F') return false; // off - break; - } - - // no recognized value, return the default - return a_bDefault; -} - -template -SI_Error -CSimpleIniTempl::SetBoolValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bValue, - const SI_CHAR * a_pComment, - bool a_bForceReplace - ) -{ - // use SetValue to create sections - if (!a_pSection || !a_pKey) return SI_Error::SI_FAIL; - - // convert to an ASCII string - const char * pszInput = a_bValue ? "true" : "false"; - - // convert to output text - SI_CHAR szOutput[64]; - SI_CONVERTER c(m_bStoreIsUtf8); - c.ConvertFromStore(pszInput, strlen(pszInput) + 1, - szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); - - // actually add it - return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); -} - -template -bool -CSimpleIniTempl::GetAllValues( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - TNamesDepend & a_values - ) const -{ - a_values.clear(); - - if (!a_pSection || !a_pKey) { - return false; - } - typename TSection::const_iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return false; - } - typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); - if (iKeyVal == iSection->second.end()) { - return false; - } - - // insert all values for this key - a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); - if (m_bAllowMultiKey) { - ++iKeyVal; - while (iKeyVal != iSection->second.end() && !IsLess(a_pKey, iKeyVal->first.pItem)) { - a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); - ++iKeyVal; - } - } - - return true; -} - -template -int -CSimpleIniTempl::GetSectionSize( - const SI_CHAR * a_pSection - ) const -{ - if (!a_pSection) { - return -1; - } - - typename TSection::const_iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return -1; - } - const TKeyVal & section = iSection->second; - - // if multi-key isn't permitted then the section size is - // the number of keys that we have. - if (!m_bAllowMultiKey || section.empty()) { - return (int) section.size(); - } - - // otherwise we need to count them - int nCount = 0; - const SI_CHAR * pLastKey = NULL; - typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { - if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { - ++nCount; - pLastKey = iKeyVal->first.pItem; - } - } - return nCount; -} - -template -const typename CSimpleIniTempl::TKeyVal * -CSimpleIniTempl::GetSection( - const SI_CHAR * a_pSection - ) const -{ - if (a_pSection) { - typename TSection::const_iterator i = m_data.find(a_pSection); - if (i != m_data.end()) { - return &(i->second); - } - } - return 0; -} - -template -void -CSimpleIniTempl::GetAllSections( - TNamesDepend & a_names - ) const -{ - a_names.clear(); - typename TSection::const_iterator i = m_data.begin(); - for (int n = 0; i != m_data.end(); ++i, ++n ) { - a_names.push_back(i->first); - } -} - -template -bool -CSimpleIniTempl::GetAllKeys( - const SI_CHAR * a_pSection, - TNamesDepend & a_names - ) const -{ - a_names.clear(); - - if (!a_pSection) { - return false; - } - - typename TSection::const_iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return false; - } - - const TKeyVal & section = iSection->second; - const SI_CHAR * pLastKey = NULL; - typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) { - if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { - a_names.push_back(iKeyVal->first); - pLastKey = iKeyVal->first.pItem; - } - } - - return true; -} - -template -SI_Error -CSimpleIniTempl::SaveFile( - const char * a_pszFile, - bool a_bAddSignature - ) const -{ - FILE * fp = nullptr; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - fopen_s(&fp, a_pszFile, "wb"); -#else // !__STDC_WANT_SECURE_LIB__ - fp = fopen(a_pszFile, "wb"); -#endif // __STDC_WANT_SECURE_LIB__ - if (!fp) return SI_FILE; - SI_Error rc = SaveFile(fp, a_bAddSignature); - fflush(fp); - fclose(fp); - return rc; -} - -#ifdef SI_HAS_WIDE_FILE - -#ifdef SI_USE_LOCKING_WIDE_FILE - -template -SI_Error -CSimpleIniTempl::SaveFile( - const SI_WCHAR_T* a_pwszFile, - bool a_bAddSignature -) const -{ - HANDLE hFile = CreateFile(a_pwszFile, - FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - - if (hFile == INVALID_HANDLE_VALUE) { - return SI_Error::SI_FILE; - } - - SI_Error rc = SaveFile(hFile, a_bAddSignature); - - CloseHandle(hFile); - return rc; -} - -#else - -template -SI_Error -CSimpleIniTempl::SaveFile( - const SI_WCHAR_T * a_pwszFile, - bool a_bAddSignature - ) const -{ -#ifdef _WIN32 - FILE * fp = nullptr; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - _wfopen_s(&fp, a_pwszFile, L"wbc"); -#else // !__STDC_WANT_SECURE_LIB__ - fp = _wfopen(a_pwszFile, L"wbc"); -#endif // __STDC_WANT_SECURE_LIB__ - if (!fp) return SI_Error::SI_FILE; - SI_Error rc = SaveFile(fp, a_bAddSignature); - fflush(fp); - fclose(fp); - return rc; -#else // !_WIN32 (therefore SI_CONVERT_ICU) - char szFile[256]; - u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); - return SaveFile(szFile, a_bAddSignature); -#endif // _WIN32 -} - -#endif // SI_USE_LOCKING_WIDE_FILE - -#endif // SI_HAS_WIDE_FILE - - -#ifdef SI_USE_LOCKING_WIDE_FILE -template -SI_Error -CSimpleIniTempl::SaveFile( - HANDLE a_hFile, - bool a_bAddSignature -) const -{ - FileHndlWriter writer(a_hFile); - return Save(writer, a_bAddSignature); -} -#else -template -SI_Error -CSimpleIniTempl::SaveFile( - FILE * a_pFile, - bool a_bAddSignature - ) const -{ - FileWriter writer(a_pFile); - return Save(writer, a_bAddSignature); -} -#endif - -template -SI_Error -CSimpleIniTempl::Save( - OutputWriter & a_oOutput, - bool a_bAddSignature - ) const -{ - Converter convert(m_bStoreIsUtf8); - - // add the UTF-8 signature if it is desired - if (m_bStoreIsUtf8 && a_bAddSignature) { - a_oOutput.Write(SI_UTF8_SIGNATURE); - } - - // get all of the sections sorted in load order - TNamesDepend oSections; - GetAllSections(oSections); -#if defined(_MSC_VER) && _MSC_VER <= 1200 - oSections.sort(); -#elif defined(__BORLANDC__) - oSections.sort(Entry::LoadOrder()); -#else - oSections.sort(typename Entry::LoadOrder()); -#endif - - // write the file comment if we have one - bool bNeedNewLine = false; - if (m_pFileComment) { - if (!OutputMultiLineText(a_oOutput, convert, m_pFileComment)) { - return SI_Error::SI_FAIL; - } - bNeedNewLine = true; - } - - // iterate through our sections and output the data - typename TNamesDepend::const_iterator iSection = oSections.begin(); - for ( ; iSection != oSections.end(); ++iSection ) { - // write out the comment if there is one - if (iSection->pComment) { - if (bNeedNewLine) { - a_oOutput.Write(SI_NEWLINE_A); - //§§§a_oOutput.Write(SI_NEWLINE_A); - bNeedNewLine = false; - } - if (!OutputMultiLineText(a_oOutput, convert, iSection->pComment)) { - return SI_Error::SI_FAIL; - } - } - - if (bNeedNewLine) { - a_oOutput.Write(SI_NEWLINE_A); - //§§§a_oOutput.Write(SI_NEWLINE_A); - bNeedNewLine = false; - } - - // write the section (unless there is no section name) - if (*iSection->pItem) { - if (!convert.ConvertToStore(iSection->pItem)) { - return SI_Error::SI_FAIL; - } - //a_oOutput.Write(SI_NEWLINE_A); // before new section - a_oOutput.Write("["); - a_oOutput.Write(convert.Data()); - a_oOutput.Write("]"); - a_oOutput.Write(SI_NEWLINE_A); - bNeedNewLine = false; - } - - // get all of the keys sorted in load order - TNamesDepend oKeys; - GetAllKeys(iSection->pItem, oKeys); -#if defined(_MSC_VER) && _MSC_VER <= 1200 - oKeys.sort(); -#elif defined(__BORLANDC__) - oKeys.sort(Entry::LoadOrder()); -#else - oKeys.sort(typename Entry::LoadOrder()); -#endif - - // write all keys and values - typename TNamesDepend::const_iterator iKey = oKeys.begin(); - for ( ; iKey != oKeys.end(); ++iKey) { - // get all values for this key - TNamesDepend oValues; - GetAllValues(iSection->pItem, iKey->pItem, oValues); - - typename TNamesDepend::const_iterator iValue = oValues.begin(); - for ( ; iValue != oValues.end(); ++iValue) { - // write out the comment if there is one - if (iValue->pComment) { - if (bNeedNewLine) { - a_oOutput.Write(SI_NEWLINE_A); - bNeedNewLine = false; - } - if (!OutputMultiLineText(a_oOutput, convert, iValue->pComment)) { - return SI_Error::SI_FAIL; - } - } - - // write the key - if (!convert.ConvertToStore(iKey->pItem)) { - return SI_Error::SI_FAIL; - } - a_oOutput.Write(convert.Data()); - - // write the value - if (!convert.ConvertToStore(iValue->pItem)) { - return SI_Error::SI_FAIL; - } - a_oOutput.Write(m_bSpaces ? " = " : "="); - if (m_bAllowMultiLine && IsMultiLineData(iValue->pItem)) { - // multi-line data needs to be processed specially to ensure - // that we use the correct newline format for the current system - a_oOutput.Write("<<pItem)) { - return SI_Error::SI_FAIL; - } - a_oOutput.Write("END_OF_TEXT"); - } - else { - a_oOutput.Write(convert.Data()); - } - a_oOutput.Write(SI_NEWLINE_A); - bNeedNewLine = false; - } - } - //§§§bNeedNewLine = true; - } - - return SI_Error::SI_OK; -} - -template -bool -CSimpleIniTempl::OutputMultiLineText( - OutputWriter & a_oOutput, - Converter & a_oConverter, - const SI_CHAR * a_pText - ) const -{ - SI_CHAR cEndOfLineChar = *a_pText; - while (cEndOfLineChar) { - // find the end of this line - const SI_CHAR * pEndOfLine = a_pText; - for (; *pEndOfLine && *pEndOfLine != '\n'; ++pEndOfLine) /*loop*/ ; - cEndOfLineChar = *pEndOfLine; - - // temporarily null terminate, convert and output the line - *const_cast(pEndOfLine) = 0; - if (!a_oConverter.ConvertToStore(a_pText)) { - return false; - } - *const_cast(pEndOfLine) = cEndOfLineChar; - a_pText += (pEndOfLine - a_pText) + 1; - a_oOutput.Write(a_oConverter.Data()); - a_oOutput.Write(SI_NEWLINE_A); - } - return true; -} - -template -bool -CSimpleIniTempl::Delete( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bRemoveEmpty - ) -{ - return DeleteValue(a_pSection, a_pKey, NULL, a_bRemoveEmpty); -} - -template -bool -CSimpleIniTempl::DeleteValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - bool a_bRemoveEmpty - ) -{ - if (!a_pSection) { - return false; - } - - typename TSection::iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return false; - } - - // remove a single key if we have a keyname - if (a_pKey) { - typename TKeyVal::iterator iKeyVal = iSection->second.find(a_pKey); - if (iKeyVal == iSection->second.end()) { - return false; - } - - const static SI_STRLESS isLess = SI_STRLESS(); - - // remove any copied strings and then the key - typename TKeyVal::iterator iDelete; - bool bDeleted = false; - do { - iDelete = iKeyVal++; - - if(a_pValue == NULL || - (isLess(a_pValue, iDelete->second) == false && - isLess(iDelete->second, a_pValue) == false)) { - DeleteString(iDelete->first.pItem); - DeleteString(iDelete->second); - iSection->second.erase(iDelete); - bDeleted = true; - } - } - while (iKeyVal != iSection->second.end() - && !IsLess(a_pKey, iKeyVal->first.pItem)); - - if(!bDeleted) { - return false; - } - - // done now if the section is not empty or we are not pruning away - // the empty sections. Otherwise let it fall through into the section - // deletion code - if (!a_bRemoveEmpty || !iSection->second.empty()) { - return true; - } - } - else { - // delete all copied strings from this section. The actual - // entries will be removed when the section is removed. - typename TKeyVal::iterator iKeyVal = iSection->second.begin(); - for ( ; iKeyVal != iSection->second.end(); ++iKeyVal) { - DeleteString(iKeyVal->first.pItem); - DeleteString(iKeyVal->second); - } - } - - // delete the section itself - DeleteString(iSection->first.pItem); - m_data.erase(iSection); - - return true; -} - -template -void -CSimpleIniTempl::DeleteString( - const SI_CHAR * a_pString - ) -{ - // strings may exist either inside the data block, or they will be - // individually allocated and stored in m_strings. We only physically - // delete those stored in m_strings. - if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { - typename TNamesDepend::iterator i = m_strings.begin(); - for (;i != m_strings.end(); ++i) { - if (a_pString == i->pItem) { - delete[] const_cast(i->pItem); - m_strings.erase(i); - break; - } - } - } -} - -// --------------------------------------------------------------------------- -// CONVERSION FUNCTIONS -// --------------------------------------------------------------------------- - -/** - * Generic case-sensitive less than comparison. This class returns numerically - * ordered ASCII case-sensitive text for all possible sizes and types of - * SI_CHAR. - */ -template -struct SI_GenericCase { - bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { - for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { - long const cmp = (long) *pLeft - (long) *pRight; - if (cmp != 0) { - return cmp < 0; - } - } - return *pRight != 0; - } -}; - -/** - * Generic ASCII case-insensitive less than comparison. This class returns - * numerically ordered ASCII case-insensitive text for all possible sizes - * and types of SI_CHAR. It is not safe for MBCS text comparison where - * ASCII A-Z characters are used in the encoding of multi-byte characters. - */ -template -struct SI_GenericNoCase { - inline SI_CHAR locase(SI_CHAR ch) const { - return (ch < 'A' || ch > 'Z') ? ch : (ch - 'A' + 'a'); - } - bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { - for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { - long const cmp = (long) locase(*pLeft) - (long) locase(*pRight); - if (cmp != 0) { - return cmp < 0; - } - } - return *pRight != 0; - } -}; - -/** - * Null conversion class for MBCS/UTF-8 to char (or equivalent). - */ -template -class SI_ConvertA { - bool m_bStoreIsUtf8; -protected: - SI_ConvertA() : m_bStoreIsUtf8(true) { } -public: - explicit SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } - - /* copy and assignment */ - SI_ConvertA(const SI_ConvertA & rhs) { operator=(rhs); } - SI_ConvertA & operator=(const SI_ConvertA & rhs) { - m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; - return *this; - } - - /** Calculate the number of SI_CHAR required for converting the input - * from the storage format. The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData Data in storage format to be converted to SI_CHAR. - * @param a_uInputDataLen Length of storage format data in bytes. This - * must be the actual length of the data, including - * NULL byte if NULL terminated string is required. - * @return Number of SI_CHAR required by the string when - * converted. If there are embedded NULL bytes in the - * input data, only the string up and not including - * the NULL byte will be converted. - * @return -1 cast to size_t on a conversion error. - */ - size_t SizeFromStore( - const char * a_pInputData, - size_t a_uInputDataLen) - { - (void)a_pInputData; - SI_ASSERT(a_uInputDataLen != (size_t) -1); - - // ASCII/MBCS/UTF-8 needs no conversion - return a_uInputDataLen; - } - - /** Convert the input string from the storage format to SI_CHAR. - * The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData Data in storage format to be converted to SI_CHAR. - * @param a_uInputDataLen Length of storage format data in bytes. This - * must be the actual length of the data, including - * NULL byte if NULL terminated string is required. - * @param a_pOutputData Pointer to the output buffer to received the - * converted data. - * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. - * @return true if all of the input data was successfully - * converted. - */ - bool ConvertFromStore( - const char * a_pInputData, - size_t a_uInputDataLen, - SI_CHAR * a_pOutputData, - size_t a_uOutputDataSize) - { - // ASCII/MBCS/UTF-8 needs no conversion - if (a_uInputDataLen > a_uOutputDataSize) { - return false; - } - memcpy(a_pOutputData, a_pInputData, a_uInputDataLen); - return true; - } - - /** Calculate the number of char required by the storage format of this - * data. The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData NULL terminated string to calculate the number of - * bytes required to be converted to storage format. - * @return Number of bytes required by the string when - * converted to storage format. This size always - * includes space for the terminating NULL character. - * @return -1 cast to size_t on a conversion error. - */ - size_t SizeToStore( - const SI_CHAR * a_pInputData) - { - // ASCII/MBCS/UTF-8 needs no conversion - return strlen((const char *)a_pInputData) + 1; - } - - /** Convert the input string to the storage format of this data. - * The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData NULL terminated source string to convert. All of - * the data will be converted including the - * terminating NULL character. - * @param a_pOutputData Pointer to the buffer to receive the converted - * string. - * @param a_uOutputDataSize Size of the output buffer in char. - * @return true if all of the input data, including the - * terminating NULL character was successfully - * converted. - */ - bool ConvertToStore( - const SI_CHAR * a_pInputData, - char * a_pOutputData, - size_t a_uOutputDataSize) - { - // calc input string length (SI_CHAR type and size independent) - size_t uInputLen = strlen((const char *)a_pInputData) + 1; - if (uInputLen > a_uOutputDataSize) { - return false; - } - - // ascii/UTF-8 needs no conversion - memcpy(a_pOutputData, a_pInputData, uInputLen); - return true; - } -}; - - -// --------------------------------------------------------------------------- -// SI_CONVERT_GENERIC -// --------------------------------------------------------------------------- -#ifdef SI_CONVERT_GENERIC - -#define SI_Case SI_GenericCase -#define SI_NoCase SI_GenericNoCase - -#include -#include "ConvertUTF.h" - -/** - * Converts UTF-8 to a wchar_t (or equivalent) using the Unicode reference - * library functions. This can be used on all platforms. - */ -template -class SI_ConvertW { - bool m_bStoreIsUtf8; -protected: - SI_ConvertW() : m_bStoreIsUtf8(true) { } -public: - explicit SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } - - /* copy and assignment */ - SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } - SI_ConvertW & operator=(const SI_ConvertW & rhs) { - m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; - return *this; - } - - /** Calculate the number of SI_CHAR required for converting the input - * from the storage format. The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData Data in storage format to be converted to SI_CHAR. - * @param a_uInputDataLen Length of storage format data in bytes. This - * must be the actual length of the data, including - * NULL byte if NULL terminated string is required. - * @return Number of SI_CHAR required by the string when - * converted. If there are embedded NULL bytes in the - * input data, only the string up and not including - * the NULL byte will be converted. - * @return -1 cast to size_t on a conversion error. - */ - size_t SizeFromStore( - const char * a_pInputData, - size_t a_uInputDataLen) const - { - SI_ASSERT(a_uInputDataLen != (size_t) -1); - - if (m_bStoreIsUtf8) { - // worst case scenario for UTF-8 to wchar_t is 1 char -> 1 wchar_t - // so we just return the same number of characters required as for - // the source text. - return a_uInputDataLen; - } - -#if defined(SI_NO_MBSTOWCS_NULL) || (!defined(_MSC_VER) && !defined(_linux)) - // fall back processing for platforms that don't support a NULL dest to mbstowcs - // worst case scenario is 1:1, this will be a sufficient buffer size - (void)a_pInputData; - return a_uInputDataLen; -#else - // get the actual required buffer size - return mbstowcs(NULL, a_pInputData, a_uInputDataLen); -#endif - } - - /** Convert the input string from the storage format to SI_CHAR. - * The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData Data in storage format to be converted to SI_CHAR. - * @param a_uInputDataLen Length of storage format data in bytes. This - * must be the actual length of the data, including - * NULL byte if NULL terminated string is required. - * @param a_pOutputData Pointer to the output buffer to received the - * converted data. - * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. - * @return true if all of the input data was successfully - * converted. - */ - bool ConvertFromStore( - const char * a_pInputData, - size_t a_uInputDataLen, - SI_CHAR * a_pOutputData, - size_t a_uOutputDataSize) const - { - if (m_bStoreIsUtf8) { - // This uses the Unicode reference implementation to do the - // conversion from UTF-8 to wchar_t. The required files are - // ConvertUTF.h and ConvertUTF.c which should be included in - // the distribution but are publically available from unicode.org - // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ - ConversionResult retval; - const UTF8 * pUtf8 = (const UTF8 *) a_pInputData; - if (sizeof(wchar_t) == sizeof(UTF32)) { - UTF32 * pUtf32 = (UTF32 *) a_pOutputData; - retval = ConvertUTF8toUTF32( - &pUtf8, pUtf8 + a_uInputDataLen, - &pUtf32, pUtf32 + a_uOutputDataSize, - lenientConversion); - } - else if (sizeof(wchar_t) == sizeof(UTF16)) { - UTF16 * pUtf16 = (UTF16 *) a_pOutputData; - retval = ConvertUTF8toUTF16( - &pUtf8, pUtf8 + a_uInputDataLen, - &pUtf16, pUtf16 + a_uOutputDataSize, - lenientConversion); - } - return retval == conversionOK; - } - - // convert to wchar_t - size_t retval = mbstowcs(a_pOutputData, - a_pInputData, a_uOutputDataSize); - return retval != (size_t)(-1); - } - - /** Calculate the number of char required by the storage format of this - * data. The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData NULL terminated string to calculate the number of - * bytes required to be converted to storage format. - * @return Number of bytes required by the string when - * converted to storage format. This size always - * includes space for the terminating NULL character. - * @return -1 cast to size_t on a conversion error. - */ - size_t SizeToStore( - const SI_CHAR * a_pInputData) const - { - if (m_bStoreIsUtf8) { - // worst case scenario for wchar_t to UTF-8 is 1 wchar_t -> 6 char - size_t uLen = 0; - while (a_pInputData[uLen]) { - ++uLen; - } - return (6 * uLen) + 1; - } - else { - size_t uLen = wcstombs(NULL, a_pInputData, 0); - if (uLen == (size_t)(-1)) { - return uLen; - } - return uLen + 1; // include NULL terminator - } - } - - /** Convert the input string to the storage format of this data. - * The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData NULL terminated source string to convert. All of - * the data will be converted including the - * terminating NULL character. - * @param a_pOutputData Pointer to the buffer to receive the converted - * string. - * @param a_uOutputDataSize Size of the output buffer in char. - * @return true if all of the input data, including the - * terminating NULL character was successfully - * converted. - */ - bool ConvertToStore( - const SI_CHAR * a_pInputData, - char * a_pOutputData, - size_t a_uOutputDataSize) const - { - if (m_bStoreIsUtf8) { - // calc input string length (SI_CHAR type and size independent) - size_t uInputLen = 0; - while (a_pInputData[uInputLen]) { - ++uInputLen; - } - ++uInputLen; // include the NULL char - - // This uses the Unicode reference implementation to do the - // conversion from wchar_t to UTF-8. The required files are - // ConvertUTF.h and ConvertUTF.c which should be included in - // the distribution but are publically available from unicode.org - // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ - ConversionResult retval; - UTF8 * pUtf8 = (UTF8 *) a_pOutputData; - if (sizeof(wchar_t) == sizeof(UTF32)) { - const UTF32 * pUtf32 = (const UTF32 *) a_pInputData; - retval = ConvertUTF32toUTF8( - &pUtf32, pUtf32 + uInputLen, - &pUtf8, pUtf8 + a_uOutputDataSize, - lenientConversion); - } - else if (sizeof(wchar_t) == sizeof(UTF16)) { - const UTF16 * pUtf16 = (const UTF16 *) a_pInputData; - retval = ConvertUTF16toUTF8( - &pUtf16, pUtf16 + uInputLen, - &pUtf8, pUtf8 + a_uOutputDataSize, - lenientConversion); - } - return retval == conversionOK; - } - else { - size_t retval = wcstombs(a_pOutputData, - a_pInputData, a_uOutputDataSize); - return retval != (size_t) -1; - } - } -}; - -#endif // SI_CONVERT_GENERIC - - -// --------------------------------------------------------------------------- -// SI_CONVERT_ICU -// --------------------------------------------------------------------------- -#ifdef SI_CONVERT_ICU - -#define SI_Case SI_GenericCase -#define SI_NoCase SI_GenericNoCase - -#include - -/** - * Converts MBCS/UTF-8 to UChar using ICU. This can be used on all platforms. - */ -template -class SI_ConvertW { - const char * m_pEncoding; - UConverter * m_pConverter; -protected: - SI_ConvertW() : m_pEncoding(NULL), m_pConverter(NULL) { } -public: - explicit SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { - m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; - } - - /* copy and assignment */ - SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } - SI_ConvertW & operator=(const SI_ConvertW & rhs) { - m_pEncoding = rhs.m_pEncoding; - m_pConverter = NULL; - return *this; - } - ~SI_ConvertW() { if (m_pConverter) ucnv_close(m_pConverter); } - - /** Calculate the number of UChar required for converting the input - * from the storage format. The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData Data in storage format to be converted to UChar. - * @param a_uInputDataLen Length of storage format data in bytes. This - * must be the actual length of the data, including - * NULL byte if NULL terminated string is required. - * @return Number of UChar required by the string when - * converted. If there are embedded NULL bytes in the - * input data, only the string up and not including - * the NULL byte will be converted. - * @return -1 cast to size_t on a conversion error. - */ - size_t SizeFromStore( - const char * a_pInputData, - size_t a_uInputDataLen) - { - SI_ASSERT(a_uInputDataLen != (size_t) -1); - - UErrorCode nError; - - if (!m_pConverter) { - nError = U_ZERO_ERROR; - m_pConverter = ucnv_open(m_pEncoding, &nError); - if (U_FAILURE(nError)) { - return (size_t) -1; - } - } - - nError = U_ZERO_ERROR; - int32_t nLen = ucnv_toUChars(m_pConverter, NULL, 0, - a_pInputData, (int32_t) a_uInputDataLen, &nError); - if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { - return (size_t) -1; - } - - return (size_t) nLen; - } - - /** Convert the input string from the storage format to UChar. - * The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData Data in storage format to be converted to UChar. - * @param a_uInputDataLen Length of storage format data in bytes. This - * must be the actual length of the data, including - * NULL byte if NULL terminated string is required. - * @param a_pOutputData Pointer to the output buffer to received the - * converted data. - * @param a_uOutputDataSize Size of the output buffer in UChar. - * @return true if all of the input data was successfully - * converted. - */ - bool ConvertFromStore( - const char * a_pInputData, - size_t a_uInputDataLen, - UChar * a_pOutputData, - size_t a_uOutputDataSize) - { - UErrorCode nError; - - if (!m_pConverter) { - nError = U_ZERO_ERROR; - m_pConverter = ucnv_open(m_pEncoding, &nError); - if (U_FAILURE(nError)) { - return false; - } - } - - nError = U_ZERO_ERROR; - ucnv_toUChars(m_pConverter, - a_pOutputData, (int32_t) a_uOutputDataSize, - a_pInputData, (int32_t) a_uInputDataLen, &nError); - if (U_FAILURE(nError)) { - return false; - } - - return true; - } - - /** Calculate the number of char required by the storage format of this - * data. The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData NULL terminated string to calculate the number of - * bytes required to be converted to storage format. - * @return Number of bytes required by the string when - * converted to storage format. This size always - * includes space for the terminating NULL character. - * @return -1 cast to size_t on a conversion error. - */ - size_t SizeToStore( - const UChar * a_pInputData) - { - UErrorCode nError; - - if (!m_pConverter) { - nError = U_ZERO_ERROR; - m_pConverter = ucnv_open(m_pEncoding, &nError); - if (U_FAILURE(nError)) { - return (size_t) -1; - } - } - - nError = U_ZERO_ERROR; - int32_t nLen = ucnv_fromUChars(m_pConverter, NULL, 0, - a_pInputData, -1, &nError); - if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { - return (size_t) -1; - } - - return (size_t) nLen + 1; - } - - /** Convert the input string to the storage format of this data. - * The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData NULL terminated source string to convert. All of - * the data will be converted including the - * terminating NULL character. - * @param a_pOutputData Pointer to the buffer to receive the converted - * string. - * @param a_pOutputDataSize Size of the output buffer in char. - * @return true if all of the input data, including the - * terminating NULL character was successfully - * converted. - */ - bool ConvertToStore( - const UChar * a_pInputData, - char * a_pOutputData, - size_t a_uOutputDataSize) - { - UErrorCode nError; - - if (!m_pConverter) { - nError = U_ZERO_ERROR; - m_pConverter = ucnv_open(m_pEncoding, &nError); - if (U_FAILURE(nError)) { - return false; - } - } - - nError = U_ZERO_ERROR; - ucnv_fromUChars(m_pConverter, - a_pOutputData, (int32_t) a_uOutputDataSize, - a_pInputData, -1, &nError); - if (U_FAILURE(nError)) { - return false; - } - - return true; - } -}; - -#endif // SI_CONVERT_ICU - - -// --------------------------------------------------------------------------- -// SI_CONVERT_WIN32 -// --------------------------------------------------------------------------- -#ifdef SI_CONVERT_WIN32 - -#define SI_Case SI_GenericCase - -// Windows CE doesn't have errno or MBCS libraries -#ifdef _WIN32_WCE -# ifndef SI_NO_MBCS -# define SI_NO_MBCS -# endif -#endif - -#ifdef SI_NO_MBCS -# define SI_NoCase SI_GenericNoCase -#else // !SI_NO_MBCS -/** - * Case-insensitive comparison class using Win32 MBCS functions. This class - * returns a case-insensitive semi-collation order for MBCS text. It may not - * be safe for UTF-8 text returned in char format as we don't know what - * characters will be folded by the function! Therefore, if you are using - * SI_CHAR == char and SetUnicode(true), then you need to use the generic - * SI_NoCase class instead. - */ -#include -template -struct SI_NoCase { - bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { - if (sizeof(SI_CHAR) == sizeof(char)) { - return _mbsicmp((const unsigned char *)pLeft, - (const unsigned char *)pRight) < 0; - } - if (sizeof(SI_CHAR) == sizeof(wchar_t)) { - return _wcsicmp((const wchar_t *)pLeft, - (const wchar_t *)pRight) < 0; - } - return SI_GenericNoCase()(pLeft, pRight); - } -}; -#endif // SI_NO_MBCS - -/** - * Converts MBCS and UTF-8 to a wchar_t (or equivalent) on Windows. This uses - * only the Win32 functions and doesn't require the external Unicode UTF-8 - * conversion library. It will not work on Windows 95 without using Microsoft - * Layer for Unicode in your application. - */ -template -class SI_ConvertW { - UINT m_uCodePage; -protected: - SI_ConvertW() : m_uCodePage(CP_UTF8) { } -public: - explicit SI_ConvertW(bool a_bStoreIsUtf8) - : m_uCodePage(a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP) - {} - - /* copy and assignment */ - SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } - SI_ConvertW & operator=(const SI_ConvertW & rhs) { - m_uCodePage = rhs.m_uCodePage; - return *this; - } - - /** Calculate the number of SI_CHAR required for converting the input - * from the storage format. The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData Data in storage format to be converted to SI_CHAR. - * @param a_uInputDataLen Length of storage format data in bytes. This - * must be the actual length of the data, including - * NULL byte if NULL terminated string is required. - * @return Number of SI_CHAR required by the string when - * converted. If there are embedded NULL bytes in the - * input data, only the string up and not including - * the NULL byte will be converted. - * @return -1 cast to size_t on a conversion error. - */ - size_t SizeFromStore( - const char * a_pInputData, - size_t a_uInputDataLen) - { - SI_ASSERT(a_uInputDataLen != (size_t) -1); - - int retval = ::MultiByteToWideChar( - m_uCodePage, 0, - a_pInputData, (int) a_uInputDataLen, - 0, 0); - return (size_t)(retval > 0 ? retval : -1); - } - - /** Convert the input string from the storage format to SI_CHAR. - * The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData Data in storage format to be converted to SI_CHAR. - * @param a_uInputDataLen Length of storage format data in bytes. This - * must be the actual length of the data, including - * NULL byte if NULL terminated string is required. - * @param a_pOutputData Pointer to the output buffer to received the - * converted data. - * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. - * @return true if all of the input data was successfully - * converted. - */ - bool ConvertFromStore( - const char * a_pInputData, - size_t a_uInputDataLen, - SI_CHAR * a_pOutputData, - size_t a_uOutputDataSize) - { - int nSize = ::MultiByteToWideChar( - m_uCodePage, 0, - a_pInputData, (int) a_uInputDataLen, - (wchar_t *) a_pOutputData, (int) a_uOutputDataSize); - return (nSize > 0); - } - - /** Calculate the number of char required by the storage format of this - * data. The storage format is always UTF-8. - * - * @param a_pInputData NULL terminated string to calculate the number of - * bytes required to be converted to storage format. - * @return Number of bytes required by the string when - * converted to storage format. This size always - * includes space for the terminating NULL character. - * @return -1 cast to size_t on a conversion error. - */ - size_t SizeToStore( - const SI_CHAR * a_pInputData) - { - int retval = ::WideCharToMultiByte( - m_uCodePage, 0, - (const wchar_t *) a_pInputData, -1, - 0, 0, 0, 0); - return (size_t) (retval > 0 ? retval : -1); - } - - /** Convert the input string to the storage format of this data. - * The storage format is always UTF-8 or MBCS. - * - * @param a_pInputData NULL terminated source string to convert. All of - * the data will be converted including the - * terminating NULL character. - * @param a_pOutputData Pointer to the buffer to receive the converted - * string. - * @param a_pOutputDataSize Size of the output buffer in char. - * @return true if all of the input data, including the - * terminating NULL character was successfully - * converted. - */ - bool ConvertToStore( - const SI_CHAR * a_pInputData, - char * a_pOutputData, - size_t a_uOutputDataSize) - { - int retval = ::WideCharToMultiByte( - m_uCodePage, 0, - (const wchar_t *) a_pInputData, -1, - a_pOutputData, (int) a_uOutputDataSize, 0, 0); - return retval > 0; - } -}; - -#endif // SI_CONVERT_WIN32 - - -// --------------------------------------------------------------------------- -// TYPE DEFINITIONS -// --------------------------------------------------------------------------- - -using CSimpleIniA = CSimpleIniTempl,SI_ConvertA >; -using CSimpleIniCaseA = CSimpleIniTempl,SI_ConvertA >; - -#if defined(SI_CONVERT_ICU) -typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; -typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; -#else -using CSimpleIniW = CSimpleIniTempl,SI_ConvertW >; -using CSimpleIniCaseW = CSimpleIniTempl,SI_ConvertW >; -#endif - -#ifdef _UNICODE -# define CSimpleIni CSimpleIniW -# define CSimpleIniCase CSimpleIniCaseW -# define SI_NEWLINE SI_NEWLINE_W -#else // !_UNICODE -# define CSimpleIni CSimpleIniA -# define CSimpleIniCase CSimpleIniCaseA -# define SI_NEWLINE SI_NEWLINE_A -#endif // _UNICODE - -#ifdef _MSC_VER -# pragma warning (pop) -#endif - -#endif // INCLUDED_SimpleIni_h - diff --git a/minipath/src/minipath.c b/minipath/src/minipath.c index 94e230e78..2f74b53d4 100644 --- a/minipath/src/minipath.c +++ b/minipath/src/minipath.c @@ -1162,37 +1162,41 @@ void CreateBars(HWND hwnd,HINSTANCE hInstance) DeleteObject(hbmpCopy); // Load toolbar labels - LoadIniFile(g_wchIniFile); - const WCHAR* const ToolbarLabels_Section = L"Toolbar Labels"; + __try { + LoadIniFile(g_wchIniFile); + const WCHAR* const ToolbarLabels_Section = L"Toolbar Labels"; - n = 0; - for (i = 0; i < COUNTOF(tbbMainWnd); i++) { + n = 0; + for (i = 0; i < COUNTOF(tbbMainWnd); i++) { - if (tbbMainWnd[i].fsStyle == TBSTYLE_SEP) - continue; - else - n++; + if (tbbMainWnd[i].fsStyle == TBSTYLE_SEP) + continue; + else + n++; - wsprintf(tchIndex,L"%02i",n); + wsprintf(tchIndex, L"%02i", n); - if (IniSectionGetString(ToolbarLabels_Section,tchIndex,L"",tchDesc,COUNTOF(tchDesc)) && - lstrcmpi(tchDesc,L"(none)") != 0) { + if (IniSectionGetString(ToolbarLabels_Section, tchIndex, L"", tchDesc, COUNTOF(tchDesc)) && + lstrcmpi(tchDesc, L"(none)") != 0) { - tbbMainWnd[i].iString = SendMessage(hwndToolbar,TB_ADDSTRING,0,(LPARAM)tchDesc); - tbbMainWnd[i].fsStyle |= BTNS_AUTOSIZE | BTNS_SHOWTEXT; + tbbMainWnd[i].iString = SendMessage(hwndToolbar, TB_ADDSTRING, 0, (LPARAM)tchDesc); + tbbMainWnd[i].fsStyle |= BTNS_AUTOSIZE | BTNS_SHOWTEXT; + } + + else if ((n == 5 || n == 8) && lstrcmpi(tchDesc, L"(none)") != 0) { + + GetLngString(42000 + n, tchDesc, COUNTOF(tchDesc)); + tbbMainWnd[i].iString = SendMessage(hwndToolbar, TB_ADDSTRING, 0, (LPARAM)tchDesc); + tbbMainWnd[i].fsStyle |= BTNS_AUTOSIZE | BTNS_SHOWTEXT; + } + + else + tbbMainWnd[i].fsStyle &= ~(BTNS_AUTOSIZE | BTNS_SHOWTEXT); } - - else if ((n == 5 || n == 8) && lstrcmpi(tchDesc,L"(none)") != 0) { - - GetLngString(42000+n,tchDesc,COUNTOF(tchDesc)); - tbbMainWnd[i].iString = SendMessage(hwndToolbar,TB_ADDSTRING,0,(LPARAM)tchDesc); - tbbMainWnd[i].fsStyle |= BTNS_AUTOSIZE | BTNS_SHOWTEXT; - } - - else - tbbMainWnd[i].fsStyle &= ~(BTNS_AUTOSIZE | BTNS_SHOWTEXT); } - ReleaseIniFile(); + __finally { + ReleaseIniFile(); + } SendMessage(hwndToolbar,TB_SETEXTENDEDSTYLE,0, SendMessage(hwndToolbar,TB_GETEXTENDEDSTYLE,0,0) | TBSTYLE_EX_MIXEDBUTTONS); @@ -2112,12 +2116,14 @@ LRESULT MsgCommand(HWND hwnd,WPARAM wParam,LPARAM lParam) if (StrIsEmpty(g_wchIniFile)) { if (StrIsNotEmpty(g_wchIniFile2)) { - if (CreateIniFileEx(g_wchIniFile2)) { - lstrcpy(g_wchIniFile,g_wchIniFile2); + lstrcpy(g_wchIniFile, g_wchIniFile2); + if (CreateIniFile()) { lstrcpy(g_wchIniFile2,L""); } - else + else { + lstrcpy(g_wchIniFile, L""); // reset bCreateFailure = TRUE; + } } else break; @@ -3323,31 +3329,34 @@ void LoadTargetParamsOnce(void) if (fLoaded) return; - LoadIniFile(g_wchIniFile); - const WCHAR* const TargetApp_Section = L"Target Application"; + __try { + LoadIniFile(g_wchIniFile); + const WCHAR* const TargetApp_Section = L"Target Application"; - if (IniSectionGetInt(TargetApp_Section,L"UseTargetApplication",0xFB) != 0xFB) { - eUseTargetApplication = IniSectionGetInt(TargetApp_Section,L"UseTargetApplication",eUseTargetApplication); - eTargetApplicationMode = IniSectionGetInt(TargetApp_Section,L"TargetApplicationMode",eTargetApplicationMode); - IniSectionGetString(TargetApp_Section,L"TargetApplicationPath",szTargetApplication,szTargetApplication,COUNTOF(szTargetApplication)); - IniSectionGetString(TargetApp_Section,L"TargetApplicationParams",szTargetApplicationParams,szTargetApplicationParams,COUNTOF(szTargetApplicationParams)); - IniSectionGetString(TargetApp_Section,L"TargetApplicationWndClass",szTargetApplicationWndClass,szTargetApplicationWndClass,COUNTOF(szTargetApplicationWndClass)); - IniSectionGetString(TargetApp_Section,L"DDEMessage",szDDEMsg,szDDEMsg,COUNTOF(szDDEMsg)); - IniSectionGetString(TargetApp_Section,L"DDEApplication",szDDEApp,szDDEApp,COUNTOF(szDDEApp)); - IniSectionGetString(TargetApp_Section,L"DDETopic",szDDETopic,szDDETopic,COUNTOF(szDDETopic)); + if (IniSectionGetInt(TargetApp_Section, L"UseTargetApplication", 0xFB) != 0xFB) { + eUseTargetApplication = IniSectionGetInt(TargetApp_Section, L"UseTargetApplication", eUseTargetApplication); + eTargetApplicationMode = IniSectionGetInt(TargetApp_Section, L"TargetApplicationMode", eTargetApplicationMode); + IniSectionGetString(TargetApp_Section, L"TargetApplicationPath", szTargetApplication, szTargetApplication, COUNTOF(szTargetApplication)); + IniSectionGetString(TargetApp_Section, L"TargetApplicationParams", szTargetApplicationParams, szTargetApplicationParams, COUNTOF(szTargetApplicationParams)); + IniSectionGetString(TargetApp_Section, L"TargetApplicationWndClass", szTargetApplicationWndClass, szTargetApplicationWndClass, COUNTOF(szTargetApplicationWndClass)); + IniSectionGetString(TargetApp_Section, L"DDEMessage", szDDEMsg, szDDEMsg, COUNTOF(szDDEMsg)); + IniSectionGetString(TargetApp_Section, L"DDEApplication", szDDEApp, szDDEApp, COUNTOF(szDDEApp)); + IniSectionGetString(TargetApp_Section, L"DDETopic", szDDETopic, szDDETopic, COUNTOF(szDDETopic)); + } + else if ((eUseTargetApplication != UTA_UNDEFINED) && StrIsEmpty(szTargetApplication)) { + eUseTargetApplication = UTA_LAUNCH_TARGET; + eTargetApplicationMode = TAM_SEND_DROP_MSG; + lstrcpy(szTargetApplication, L"Notepad3.exe"); + lstrcpy(szTargetApplicationParams, L""); + lstrcpy(szTargetApplicationWndClass, L"Notepad3"); + lstrcpy(szDDEMsg, L""); + lstrcpy(szDDEApp, L""); + lstrcpy(szDDETopic, L""); + } } - else if ((eUseTargetApplication != UTA_UNDEFINED) && StrIsEmpty(szTargetApplication)) { - eUseTargetApplication = UTA_LAUNCH_TARGET; - eTargetApplicationMode = TAM_SEND_DROP_MSG; - lstrcpy(szTargetApplication,L"Notepad3.exe"); - lstrcpy(szTargetApplicationParams,L""); - lstrcpy(szTargetApplicationWndClass,L"Notepad3"); - lstrcpy(szDDEMsg,L""); - lstrcpy(szDDEApp,L""); - lstrcpy(szDDETopic,L""); + __finally { + ReleaseIniFile(); } - - ReleaseIniFile(); fLoaded = TRUE; } diff --git a/res/Notepad3.exe.manifest.conf b/res/Notepad3.exe.manifest.conf index f0d40f32e..b87753681 100644 --- a/res/Notepad3.exe.manifest.conf +++ b/res/Notepad3.exe.manifest.conf @@ -3,7 +3,7 @@ Notepad3 RC3 diff --git a/src/Config/Config.cpp b/src/Config/Config.cpp index c8de588be..7b64ae485 100644 --- a/src/Config/Config.cpp +++ b/src/Config/Config.cpp @@ -83,6 +83,12 @@ constexpr bool SI_Success(const SI_Error rc) noexcept { // ============================================================================ +// ---------------------------------------------------------------------------- +// No mechanism for EXCLUSIVE WRITE / SHARD READ: +// cause we need completely synchronized exclusive access for READ _and_ WRITE +// of complete file to preserve integrety of any transaction +// ---------------------------------------------------------------------------- + HANDLE AcquireFileLock(LPCWSTR lpIniFilePath, OVERLAPPED& rOvrLpd) { HANDLE hFile = CreateFile(lpIniFilePath, @@ -97,7 +103,6 @@ HANDLE AcquireFileLock(LPCWSTR lpIniFilePath, OVERLAPPED& rOvrLpd) // ---------------------------------------------------------------------------- - bool ReleaseFileLock(HANDLE hFile, OVERLAPPED& rOvrLpd) { bool bUnLocked = true; @@ -112,7 +117,6 @@ bool ReleaseFileLock(HANDLE hFile, OVERLAPPED& rOvrLpd) // ============================================================================ - // ============================================================================ static OVERLAPPED s_OvrLpd = { 0 }; @@ -308,7 +312,7 @@ extern "C" bool IniClearAllSections(LPCWSTR lpPrefix, bool bRemoveEmpty) { if (StringCchCompareNI(section.pItem, len, lpPrefix, len) == 0) { - s_INI.Delete(section.pItem, nullptr, bRemoveEmpty); + IniSectionClear(section.pItem, bRemoveEmpty); } } return true; @@ -1831,14 +1835,8 @@ bool SaveAllSettings(bool bForceSaveSettings) MRU_Save(Globals.pFileMRU); } else { - int const cnt = MRU_Count(Globals.pFileMRU); - if (cnt == 0) { - MRU_Empty(Globals.pFileMRU); - MRU_Save(Globals.pFileMRU); // intension is to destroy the multi-instance saved list - } - else { - MRU_MergeSave(Globals.pFileMRU, true, Flags.RelativeFileMRU, Flags.PortableMyDocs); - } + //int const cnt = MRU_Count(Globals.pFileMRU); + MRU_MergeSave(Globals.pFileMRU, true, Flags.RelativeFileMRU, Flags.PortableMyDocs); } if (!Settings.SaveFindReplace) { @@ -2040,34 +2038,33 @@ bool MRU_AddFile(LPMRULIST pmru, LPCWSTR pszFile, bool bRelativePath, bool bUnex bool MRU_Delete(LPMRULIST pmru, int iIndex) { if (pmru) { - int i; - if (iIndex < 0 || iIndex > pmru->iSize - 1) { - return false; - } - if (pmru->pszItems[iIndex]) { - LocalFree(pmru->pszItems[iIndex]); // StrDup() - } - if (pmru->pszBookMarks[iIndex]) { - LocalFree(pmru->pszBookMarks[iIndex]); // StrDup() - } - bool bZeroMoved = false; - for (i = iIndex; (i < pmru->iSize - 1) && !bZeroMoved; ++i) + if (iIndex >= 0 || iIndex < pmru->iSize) { - pmru->pszItems[i] = pmru->pszItems[i + 1]; - pmru->iEncoding[i] = pmru->iEncoding[i + 1]; - pmru->iCaretPos[i] = pmru->iCaretPos[i + 1]; - pmru->iSelAnchPos[i] = pmru->iSelAnchPos[i + 1]; - pmru->pszBookMarks[i] = pmru->pszBookMarks[i + 1]; + if (pmru->pszItems[iIndex]) { + LocalFree(pmru->pszItems[iIndex]); // StrDup() + } + if (pmru->pszBookMarks[iIndex]) { + LocalFree(pmru->pszBookMarks[iIndex]); // StrDup() + } + bool bZeroMoved = false; + for (int i = iIndex; (i < pmru->iSize - 1) && !bZeroMoved; ++i) + { + pmru->pszItems[i] = pmru->pszItems[i + 1]; + pmru->iEncoding[i] = pmru->iEncoding[i + 1]; + pmru->iCaretPos[i] = pmru->iCaretPos[i + 1]; + pmru->iSelAnchPos[i] = pmru->iSelAnchPos[i + 1]; + pmru->pszBookMarks[i] = pmru->pszBookMarks[i + 1]; - bZeroMoved = (NULL == pmru->pszItems[i + 1]); + bZeroMoved = (NULL == pmru->pszItems[i + 1]); - pmru->pszItems[i + 1] = NULL; - pmru->iEncoding[i + 1] = 0; - pmru->iCaretPos[i + 1] = -1; - pmru->iSelAnchPos[i + 1] = -1; - pmru->pszBookMarks[i + 1] = NULL; + pmru->pszItems[i + 1] = NULL; + pmru->iEncoding[i + 1] = 0; + pmru->iCaretPos[i + 1] = -1; + pmru->iSelAnchPos[i + 1] = -1; + pmru->pszBookMarks[i + 1] = NULL; + } + return true; } - return true; } return false; } @@ -2117,45 +2114,54 @@ bool MRU_Load(LPMRULIST pmru, bool bFileProps) { if (pmru) { - MRU_Empty(pmru); - //if (bFileProps) { ClearDestinationsOnRecentDocs(); } - - const WCHAR* const RegKey_Section = pmru->szRegKey; + bool const bOpendByMe = !IsIniFileLoaded() ? OpenSettingsFile() : false; int n = 0; - for (int i = 0; i < pmru->iSize; ++i) - { - WCHAR tchName[32] = { L'\0' }; - StringCchPrintf(tchName, COUNTOF(tchName), L"%.2i", i + 1); - WCHAR tchItem[2048] = { L'\0' }; - if (IniSectionGetString(RegKey_Section, tchName, L"", tchItem, COUNTOF(tchItem))) + if (IsIniFileLoaded()) { + + MRU_Empty(pmru); + //if (bFileProps) { ClearDestinationsOnRecentDocs(); } + + const WCHAR* const RegKey_Section = pmru->szRegKey; + + for (int i = 0; i < pmru->iSize; ++i) { - size_t const len = StringCchLen(tchItem, 0); - if ((len > 0) && (tchItem[0] == L'"') && (tchItem[len - 1] == L'"')) { - MoveMemory(tchItem, (tchItem + 1), len * sizeof(WCHAR)); - tchItem[len - 2] = L'\0'; // clear dangling '"' + WCHAR tchName[32] = { L'\0' }; + StringCchPrintf(tchName, COUNTOF(tchName), L"%.2i", i + 1); + WCHAR tchItem[2048] = { L'\0' }; + if (IniSectionGetString(RegKey_Section, tchName, L"", tchItem, COUNTOF(tchItem))) + { + size_t const len = StringCchLen(tchItem, 0); + if ((len > 0) && (tchItem[0] == L'"') && (tchItem[len - 1] == L'"')) { + MoveMemory(tchItem, (tchItem + 1), len * sizeof(WCHAR)); + tchItem[len - 2] = L'\0'; // clear dangling '"' + } + pmru->pszItems[n] = StrDup(tchItem); + + StringCchPrintf(tchName, COUNTOF(tchName), L"ENC%.2i", i + 1); + int const iCP = (cpi_enc_t)IniSectionGetInt(RegKey_Section, tchName, 0); + pmru->iEncoding[n] = bFileProps ? (cpi_enc_t)Encoding_MapIniSetting(true, iCP) : 0; + + StringCchPrintf(tchName, COUNTOF(tchName), L"POS%.2i", i + 1); + pmru->iCaretPos[n] = bFileProps ? ((Settings.PreserveCaretPos) ? IniSectionGetInt(RegKey_Section, tchName, 0) : -1) : -1; + + StringCchPrintf(tchName, COUNTOF(tchName), L"ANC%.2i", i + 1); + pmru->iSelAnchPos[n] = bFileProps ? ((Settings.PreserveCaretPos) ? IniSectionGetInt(RegKey_Section, tchName, 0) : -1) : -1; + + StringCchPrintf(tchName, COUNTOF(tchName), L"BMRK%.2i", i + 1); + + WCHAR wchBookMarks[MRU_BMRK_SIZE] = { L'\0' }; + IniSectionGetString(RegKey_Section, tchName, L"", wchBookMarks, COUNTOF(wchBookMarks)); + pmru->pszBookMarks[n] = bFileProps ? StrDup(wchBookMarks) : nullptr; + + ++n; } - pmru->pszItems[n] = StrDup(tchItem); - - StringCchPrintf(tchName, COUNTOF(tchName), L"ENC%.2i", i + 1); - int const iCP = (cpi_enc_t)IniSectionGetInt(RegKey_Section, tchName, 0); - pmru->iEncoding[n] = bFileProps ? (cpi_enc_t)Encoding_MapIniSetting(true, iCP) : 0; - - StringCchPrintf(tchName, COUNTOF(tchName), L"POS%.2i", i + 1); - pmru->iCaretPos[n] = bFileProps ? ((Settings.PreserveCaretPos) ? IniSectionGetInt(RegKey_Section, tchName, 0) : -1) : -1; - - StringCchPrintf(tchName, COUNTOF(tchName), L"ANC%.2i", i + 1); - pmru->iSelAnchPos[n] = bFileProps ? ((Settings.PreserveCaretPos) ? IniSectionGetInt(RegKey_Section, tchName, 0) : -1) : -1; - - StringCchPrintf(tchName, COUNTOF(tchName), L"BMRK%.2i", i + 1); - - WCHAR wchBookMarks[MRU_BMRK_SIZE] = { L'\0' }; - IniSectionGetString(RegKey_Section, tchName, L"", wchBookMarks, COUNTOF(wchBookMarks)); - pmru->pszBookMarks[n] = bFileProps ? StrDup(wchBookMarks) : nullptr; - - ++n; + } + if (bOpendByMe) { + CloseSettingsFile(true); } } + if (bFileProps) { WCHAR szFilePath[MAX_PATH + 1]; for (int i = n - 1; i >= 0; --i) @@ -2176,7 +2182,6 @@ bool MRU_Load(LPMRULIST pmru, bool bFileProps) void MRU_Save(LPMRULIST pmru) { if (pmru) { - bool const bOpendByMe = !IsIniFileLoaded() ? OpenSettingsFile() : false; if (IsIniFileLoaded()) { @@ -2211,9 +2216,9 @@ void MRU_Save(LPMRULIST pmru) } } } - } - if (bOpendByMe) { - CloseSettingsFile(true); + if (bOpendByMe) { + CloseSettingsFile(true); + } } } } diff --git a/src/Dialogs.c b/src/Dialogs.c index 48f69bd15..95185774e 100644 --- a/src/Dialogs.c +++ b/src/Dialogs.c @@ -1652,6 +1652,13 @@ static INT_PTR CALLBACK FileMRUDlgProc(HWND hwnd,UINT umsg,WPARAM wParam,LPARAM SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam); if (Globals.hDlgIcon) { SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)Globals.hDlgIcon); } + // sync with other instances + if (Settings.SaveRecentFiles) { + if (MRU_MergeSave(Globals.pFileMRU, true, Flags.RelativeFileMRU, Flags.PortableMyDocs)) { + MRU_Load(Globals.pFileMRU, true); + } + } + SHFILEINFO shfi; ZeroMemory(&shfi, sizeof(SHFILEINFO)); LVCOLUMN lvc = { LVCF_FMT|LVCF_TEXT, LVCFMT_LEFT, 0, L"", -1, 0, 0, 0 }; @@ -1716,6 +1723,10 @@ static INT_PTR CALLBACK FileMRUDlgProc(HWND hwnd,UINT umsg,WPARAM wParam,LPARAM RemoveProp(hwnd,L"it"); FreeMem(lpit); + if (Settings.SaveRecentFiles) { + MRU_Save(Globals.pFileMRU); // last instance on save wins + } + Settings.SaveRecentFiles = IsButtonChecked(hwnd, IDC_SAVEMRU); Settings.SaveFindReplace = IsButtonChecked(hwnd, IDC_REMEMBERSEARCHPATTERN); Settings.PreserveCaretPos = IsButtonChecked(hwnd, IDC_PRESERVECARET); @@ -1835,10 +1846,10 @@ static INT_PTR CALLBACK FileMRUDlgProc(HWND hwnd,UINT umsg,WPARAM wParam,LPARAM UINT const cnt = ListView_GetSelectedCount(GetDlgItem(hwnd, IDC_FILEMRU)); DialogEnableControl(hwnd, IDOK, (cnt > 0)); // can't discard current file (myself) - int iCur = 0; - if (!MRU_FindFile(Globals.pFileMRU, Globals.CurrentFile, &iCur)) { iCur = -1; } + int cur = 0; + if (!MRU_FindFile(Globals.pFileMRU, Globals.CurrentFile, &cur)) { cur = -1; } int const item = ListView_GetNextItem(GetDlgItem(hwnd, IDC_FILEMRU), -1, LVNI_ALL | LVNI_SELECTED); - DialogEnableControl(hwnd, IDC_REMOVE, (cnt > 0) && (iCur != item)); + DialogEnableControl(hwnd, IDC_REMOVE, (cnt > 0) && (cur != item)); } break; } @@ -1898,9 +1909,14 @@ static INT_PTR CALLBACK FileMRUDlgProc(HWND hwnd,UINT umsg,WPARAM wParam,LPARAM UINT cnt = ListView_GetItemCount(GetDlgItem(hwnd, IDC_FILEMRU)); if (cnt > 0) { UINT idx = ListView_GetTopIndex(GetDlgItem(hwnd, IDC_FILEMRU)); - ListView_SetItemState(GetDlgItem(hwnd, IDC_FILEMRU), idx, LVIS_FOCUSED, LVIS_FOCUSED); ListView_SetColumnWidth(GetDlgItem(hwnd, IDC_FILEMRU), idx, LVSCW_AUTOSIZE_USEHEADER); - ListView_SetItemState(GetDlgItem(hwnd, IDC_FILEMRU), idx, LVIS_SELECTED, LVIS_SELECTED); + ListView_SetItemState(GetDlgItem(hwnd, IDC_FILEMRU), ((cnt > 1) ? idx + 1 : idx), LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); + //int cur = 0; + //if (!MRU_FindFile(Globals.pFileMRU, Globals.CurrentFile, &cur)) { cur = -1; } + //int const item = ListView_GetNextItem(GetDlgItem(hwnd, IDC_FILEMRU), -1, LVNI_ALL | LVNI_SELECTED); + //if ((cur == item) && (cnt > 1)) { + // ListView_SetItemState(GetDlgItem(hwnd, IDC_FILEMRU), idx + 1, LVIS_SELECTED, LVIS_SELECTED); + //} } lpit->hThread = CreateThread(NULL,0,FileMRUIconThread,(LPVOID)lpit,0,&dwtid); @@ -1921,8 +1937,8 @@ static INT_PTR CALLBACK FileMRUDlgProc(HWND hwnd,UINT umsg,WPARAM wParam,LPARAM case IDC_REMOVE: { WCHAR tchFileName[MAX_PATH] = { L'\0' }; + //int iItem; - //if ((iItem = SendDlgItemMessage(hwnd,IDC_FILEMRU,LB_GETCURSEL,0,0)) != LB_ERR) UINT cnt = ListView_GetSelectedCount(GetDlgItem(hwnd, IDC_FILEMRU)); diff --git a/src/TypeDefs.h b/src/TypeDefs.h index de58fdcfd..597d5111f 100644 --- a/src/TypeDefs.h +++ b/src/TypeDefs.h @@ -649,7 +649,6 @@ typedef struct _themeFiles //============================================================================= - // --------- common defines -------- #define NOTEPAD3_MODULE_DIR_ENV_VAR L"NOTEPAD3MODULEDIR" diff --git a/src/VersionEx.h b/src/VersionEx.h index ea99b77e3..6d273801e 100644 --- a/src/VersionEx.h +++ b/src/VersionEx.h @@ -8,12 +8,12 @@ #define SAPPNAME "Notepad3" #define VERSION_MAJOR 5 #define VERSION_MINOR 20 -#define VERSION_REV 311 -#define VERSION_BUILD 4 +#define VERSION_REV 312 +#define VERSION_BUILD 1 #define SCINTILLA_VER 432 #define ONIGURUMA_REGEX_VER 6.9.4 #define UCHARDET_VER 2018.09.27 #define TINYEXPR_VER 2018.05.11 #define UTHASH_VER 2.1.0 #define VERSION_PATCH RC3 -#define VERSION_COMMIT_ID dkt1-amr +#define VERSION_COMMIT_ID nebukadn