// This file is part of Notepad++ project // Copyright (C)2021 Don HO // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // at your option any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include #include #include #include #include #include #include "StaticDialog.h" #include "CustomFileDialog.h" #include "FileInterface.h" #include "Common.h" #include "Utf8.h" #include "Parameters.h" #include "Buffer.h" using namespace std; void printInt(int int2print) { wchar_t str[32]; wsprintf(str, L"%d", int2print); ::MessageBox(NULL, str, L"", MB_OK); } void printStr(const wchar_t *str2print) { ::MessageBox(NULL, str2print, L"", MB_OK); } wstring commafyInt(size_t n) { std::basic_stringstream ss; ss.imbue(std::locale("")); ss << n; return ss.str(); } std::string getFileContent(const wchar_t *file2read) { if (!doesFileExist(file2read)) return ""; const size_t blockSize = 1024; char data[blockSize]; std::string wholeFileContent = ""; FILE *fp = _wfopen(file2read, L"rb"); if (!fp) return ""; size_t lenFile = 0; do { lenFile = fread(data, 1, blockSize, fp); if (lenFile == 0) break; wholeFileContent.append(data, lenFile); } while (lenFile > 0); fclose(fp); return wholeFileContent; } char getDriveLetter() { char drive = '\0'; wchar_t current[MAX_PATH]; ::GetCurrentDirectory(MAX_PATH, current); int driveNbr = ::PathGetDriveNumber(current); if (driveNbr != -1) drive = 'A' + char(driveNbr); return drive; } wstring relativeFilePathToFullFilePath(const wchar_t *relativeFilePath) { wstring fullFilePathName; BOOL isRelative = ::PathIsRelative(relativeFilePath); if (isRelative) { wchar_t fullFileName[MAX_PATH]; ::GetFullPathName(relativeFilePath, MAX_PATH, fullFileName, NULL); fullFilePathName += fullFileName; } else { if ((relativeFilePath[0] == '\\' && relativeFilePath[1] != '\\') || relativeFilePath[0] == '/') { fullFilePathName += getDriveLetter(); fullFilePathName += ':'; } fullFilePathName += relativeFilePath; } return fullFilePathName; } void writeFileContent(const wchar_t *file2write, const char *content2write) { Win32_IO_File file(file2write); if (file.isOpened()) file.writeStr(content2write); } void writeLog(const wchar_t* logFileName, const char* log2write) { const DWORD accessParam{ GENERIC_READ | GENERIC_WRITE }; const DWORD shareParam{ FILE_SHARE_READ | FILE_SHARE_WRITE }; const DWORD dispParam{ OPEN_ALWAYS }; // Open existing file for writing without destroying it or create new const DWORD attribParam{ FILE_ATTRIBUTE_NORMAL }; HANDLE hFile = ::CreateFileW(logFileName, accessParam, shareParam, NULL, dispParam, attribParam, NULL); if (hFile != INVALID_HANDLE_VALUE) { LARGE_INTEGER offset{}; offset.QuadPart = 0; ::SetFilePointerEx(hFile, offset, NULL, FILE_END); SYSTEMTIME currentTime = {}; ::GetLocalTime(¤tTime); wstring dateTimeStrW = getDateTimeStrFrom(L"yyyy-MM-dd HH:mm:ss", currentTime); wstring_convert> converter; string log2writeStr = converter.to_bytes(dateTimeStrW); log2writeStr += " "; log2writeStr += log2write; log2writeStr += "\n"; DWORD bytes_written = 0; ::WriteFile(hFile, log2writeStr.c_str(), static_cast(log2writeStr.length()), &bytes_written, NULL); ::FlushFileBuffers(hFile); ::CloseHandle(hFile); } } void writeLog(const wchar_t* logFileName, const wchar_t* log2write) { string log2WriteA = wstring2string(log2write, CP_ACP); return writeLog(logFileName, log2WriteA.c_str()); } wstring folderBrowser(HWND parent, const wstring & title, int outputCtrlID, const wchar_t *defaultStr) { wstring folderName; CustomFileDialog dlg(parent); dlg.setTitle(title.c_str()); // Get an initial directory from the edit control or from argument provided wchar_t directory[MAX_PATH] = {}; if (outputCtrlID != 0) ::GetDlgItemText(parent, outputCtrlID, directory, _countof(directory)); directory[_countof(directory) - 1] = '\0'; if (!directory[0] && defaultStr) dlg.setFolder(defaultStr); else if (directory[0]) dlg.setFolder(directory); folderName = dlg.pickFolder(); if (!folderName.empty()) { // Send the result back to the edit control if (outputCtrlID != 0) ::SetDlgItemText(parent, outputCtrlID, folderName.c_str()); } return folderName; } wstring getFolderName(HWND parent, const wchar_t *defaultDir) { return folderBrowser(parent, L"Select a folder", 0, defaultDir); } void ClientRectToScreenRect(HWND hWnd, RECT* rect) { POINT pt{}; pt.x = rect->left; pt.y = rect->top; ::ClientToScreen( hWnd, &pt ); rect->left = pt.x; rect->top = pt.y; pt.x = rect->right; pt.y = rect->bottom; ::ClientToScreen( hWnd, &pt ); rect->right = pt.x; rect->bottom = pt.y; } std::vector tokenizeString(const wstring & tokenString, const char delim) { //Vector is created on stack and copied on return std::vector tokens; // Skip delimiters at beginning. wstring::size_type lastPos = tokenString.find_first_not_of(delim, 0); // Find first "non-delimiter". wstring::size_type pos = tokenString.find_first_of(delim, lastPos); while (pos != std::string::npos || lastPos != std::string::npos) { // Found a token, add it to the vector. tokens.push_back(tokenString.substr(lastPos, pos - lastPos)); // Skip delimiters. Note the "not_of" lastPos = tokenString.find_first_not_of(delim, pos); // Find next "non-delimiter" pos = tokenString.find_first_of(delim, lastPos); } return tokens; } void ScreenRectToClientRect(HWND hWnd, RECT* rect) { POINT pt{}; pt.x = rect->left; pt.y = rect->top; ::ScreenToClient( hWnd, &pt ); rect->left = pt.x; rect->top = pt.y; pt.x = rect->right; pt.y = rect->bottom; ::ScreenToClient( hWnd, &pt ); rect->right = pt.x; rect->bottom = pt.y; } int filter(unsigned int code, struct _EXCEPTION_POINTERS *) { if (code == EXCEPTION_ACCESS_VIOLATION) return EXCEPTION_EXECUTE_HANDLER; return EXCEPTION_CONTINUE_SEARCH; } bool isInList(const wchar_t *token, const wchar_t *list) { if ((!token) || (!list)) return false; const size_t wordLen = 64; size_t listLen = lstrlen(list); wchar_t word[wordLen] = { '\0' }; size_t i = 0; size_t j = 0; for (; i <= listLen; ++i) { if ((list[i] == ' ')||(list[i] == '\0')) { if (j != 0) { word[j] = '\0'; j = 0; if (!wcsicmp(token, word)) return true; } } else { word[j] = list[i]; ++j; if (j >= wordLen) return false; } } return false; } wstring purgeMenuItemString(const wchar_t * menuItemStr, bool keepAmpersand) { const size_t cleanedNameLen = 64; wchar_t cleanedName[cleanedNameLen] = L""; size_t j = 0; size_t menuNameLen = lstrlen(menuItemStr); if (menuNameLen >= cleanedNameLen) menuNameLen = cleanedNameLen - 1; for (size_t k = 0 ; k < menuNameLen ; ++k) { if (menuItemStr[k] == '\t') { cleanedName[k] = 0; break; } else { if (menuItemStr[k] == '&') { if (keepAmpersand) cleanedName[j++] = menuItemStr[k]; //else skip } else cleanedName[j++] = menuItemStr[k]; } } cleanedName[j] = 0; return cleanedName; } const wchar_t * WcharMbcsConvertor::char2wchar(const char * mbcs2Convert, size_t codepage, int lenMbcs, int* pLenWc, int* pBytesNotProcessed) { // Do not process NULL pointer if (!mbcs2Convert) return nullptr; // Do not process empty strings if (lenMbcs == 0 || (lenMbcs == -1 && mbcs2Convert[0] == 0)) { _wideCharStr.empty(); return _wideCharStr; } UINT cp = static_cast(codepage); int bytesNotProcessed = 0; int lenWc = 0; // If length not specified, simply convert without checking if (lenMbcs == -1) { lenWc = MultiByteToWideChar(cp, 0, mbcs2Convert, lenMbcs, NULL, 0); } // Otherwise, test if we are cutting a multi-byte character at end of buffer else if (lenMbcs != -1 && cp == CP_UTF8) // For UTF-8, we know how to test it { int indexOfLastChar = Utf8::characterStart(mbcs2Convert, lenMbcs-1); // get index of last character if (indexOfLastChar != 0 && !Utf8::isValid(mbcs2Convert+indexOfLastChar, lenMbcs-indexOfLastChar)) // if it is not valid we do not process it right now (unless its the only character in string, to ensure that we always progress, e.g. that bytesNotProcessed < lenMbcs) { bytesNotProcessed = lenMbcs-indexOfLastChar; } lenWc = MultiByteToWideChar(cp, 0, mbcs2Convert, lenMbcs-bytesNotProcessed, NULL, 0); } else // For other encodings, ask system if there are any invalid characters; note that it will not correctly know if last character is cut when there are invalid characters inside the text { lenWc = MultiByteToWideChar(cp, (lenMbcs == -1) ? 0 : MB_ERR_INVALID_CHARS, mbcs2Convert, lenMbcs, NULL, 0); if (lenWc == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) { // Test without last byte if (lenMbcs > 1) lenWc = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, mbcs2Convert, lenMbcs-1, NULL, 0); if (lenWc == 0) // don't have to check that the error is still ERROR_NO_UNICODE_TRANSLATION, since only the length parameter changed { // TODO: should warn user about incorrect loading due to invalid characters // We still load the file, but the system will either strip or replace invalid characters (including the last character, if cut in half) lenWc = MultiByteToWideChar(cp, 0, mbcs2Convert, lenMbcs, NULL, 0); } else { // We found a valid text by removing one byte. bytesNotProcessed = 1; } } } if (lenWc > 0) { _wideCharStr.sizeTo(lenWc); MultiByteToWideChar(cp, 0, mbcs2Convert, lenMbcs-bytesNotProcessed, _wideCharStr, lenWc); } else _wideCharStr.empty(); if (pLenWc) *pLenWc = lenWc; if (pBytesNotProcessed) *pBytesNotProcessed = bytesNotProcessed; return _wideCharStr; } // "mstart" and "mend" are pointers to indexes in mbcs2Convert, // which are converted to the corresponding indexes in the returned wchar_t string. const wchar_t * WcharMbcsConvertor::char2wchar(const char * mbcs2Convert, size_t codepage, intptr_t* mstart, intptr_t* mend) { // Do not process NULL pointer if (!mbcs2Convert) return NULL; UINT cp = static_cast(codepage); int len = MultiByteToWideChar(cp, 0, mbcs2Convert, -1, NULL, 0); if (len > 0) { _wideCharStr.sizeTo(len); len = MultiByteToWideChar(cp, 0, mbcs2Convert, -1, _wideCharStr, len); if ((size_t)*mstart < strlen(mbcs2Convert) && (size_t)*mend <= strlen(mbcs2Convert)) { *mstart = MultiByteToWideChar(cp, 0, mbcs2Convert, static_cast(*mstart), _wideCharStr, 0); *mend = MultiByteToWideChar(cp, 0, mbcs2Convert, static_cast(*mend), _wideCharStr, 0); if (*mstart >= len || *mend >= len) { *mstart = 0; *mend = 0; } } } else { _wideCharStr.empty(); *mstart = 0; *mend = 0; } return _wideCharStr; } const char* WcharMbcsConvertor::wchar2char(const wchar_t * wcharStr2Convert, size_t codepage, int lenWc, int* pLenMbcs) { if (!wcharStr2Convert) return nullptr; UINT cp = static_cast(codepage); int lenMbcs = WideCharToMultiByte(cp, 0, wcharStr2Convert, lenWc, NULL, 0, NULL, NULL); if (lenMbcs > 0) { _multiByteStr.sizeTo(lenMbcs); WideCharToMultiByte(cp, 0, wcharStr2Convert, lenWc, _multiByteStr, lenMbcs, NULL, NULL); } else _multiByteStr.empty(); if (pLenMbcs) *pLenMbcs = lenMbcs; return _multiByteStr; } const char * WcharMbcsConvertor::wchar2char(const wchar_t * wcharStr2Convert, size_t codepage, intptr_t* mstart, intptr_t* mend) { if (!wcharStr2Convert) return nullptr; UINT cp = static_cast(codepage); int len = WideCharToMultiByte(cp, 0, wcharStr2Convert, -1, NULL, 0, NULL, NULL); if (len > 0) { _multiByteStr.sizeTo(len); len = WideCharToMultiByte(cp, 0, wcharStr2Convert, -1, _multiByteStr, len, NULL, NULL); // not needed? if (*mstart < lstrlenW(wcharStr2Convert) && *mend < lstrlenW(wcharStr2Convert)) { *mstart = WideCharToMultiByte(cp, 0, wcharStr2Convert, (int)*mstart, NULL, 0, NULL, NULL); *mend = WideCharToMultiByte(cp, 0, wcharStr2Convert, (int)*mend, NULL, 0, NULL, NULL); if (*mstart >= len || *mend >= len) { *mstart = 0; *mend = 0; } } } else _multiByteStr.empty(); return _multiByteStr; } std::wstring string2wstring(const std::string & rString, UINT codepage) { int len = MultiByteToWideChar(codepage, 0, rString.c_str(), -1, NULL, 0); if (len > 0) { std::vector vw(len); MultiByteToWideChar(codepage, 0, rString.c_str(), -1, &vw[0], len); return &vw[0]; } return std::wstring(); } std::string wstring2string(const std::wstring & rwString, UINT codepage) { int len = WideCharToMultiByte(codepage, 0, rwString.c_str(), -1, NULL, 0, NULL, NULL); if (len > 0) { std::vector vw(len); WideCharToMultiByte(codepage, 0, rwString.c_str(), -1, &vw[0], len, NULL, NULL); return &vw[0]; } return std::string(); } // Escapes ampersands in file name to use it in menu template wstring convertFileName(T beg, T end) { wstring strTmp; for (T it = beg; it != end; ++it) { if (*it == '&') strTmp.push_back('&'); strTmp.push_back(*it); } return strTmp; } wstring intToString(int val) { std::vector vt; bool isNegative = val < 0; // can't use abs here because std::numeric_limits::min() has no positive representation //val = std::abs(val); vt.push_back('0' + static_cast(std::abs(val % 10))); val /= 10; while (val != 0) { vt.push_back('0' + static_cast(std::abs(val % 10))); val /= 10; } if (isNegative) vt.push_back('-'); return wstring(vt.rbegin(), vt.rend()); } wstring uintToString(unsigned int val) { std::vector vt; vt.push_back('0' + static_cast(val % 10)); val /= 10; while (val != 0) { vt.push_back('0' + static_cast(val % 10)); val /= 10; } return wstring(vt.rbegin(), vt.rend()); } // Build Recent File menu entries from given wstring BuildMenuFileName(int filenameLen, unsigned int pos, const wstring &filename, bool ordinalNumber) { wstring strTemp; if (ordinalNumber) { if (pos < 9) { strTemp.push_back('&'); strTemp.push_back('1' + static_cast(pos)); } else if (pos == 9) { strTemp.append(L"1&0"); } else { div_t splitDigits = div(pos + 1, 10); strTemp.append(uintToString(splitDigits.quot)); strTemp.push_back('&'); strTemp.append(uintToString(splitDigits.rem)); } strTemp.append(L": "); } else { strTemp.push_back('&'); } if (filenameLen > 0) { std::vector vt(filenameLen + 1); // W removed from PathCompactPathExW due to compiler errors for ANSI version. PathCompactPathEx(&vt[0], filename.c_str(), filenameLen + 1, 0); strTemp.append(convertFileName(vt.begin(), vt.begin() + lstrlen(&vt[0]))); } else { // (filenameLen < 0) wstring::const_iterator it = filename.begin(); if (filenameLen == 0) it += PathFindFileName(filename.c_str()) - filename.c_str(); // MAX_PATH is still here to keep old trimming behaviour. if (filename.end() - it < MAX_PATH) { strTemp.append(convertFileName(it, filename.end())); } else { strTemp.append(convertFileName(it, it + MAX_PATH / 2 - 3)); strTemp.append(L"..."); strTemp.append(convertFileName(filename.end() - MAX_PATH / 2, filename.end())); } } return strTemp; } wstring PathRemoveFileSpec(wstring& path) { wstring::size_type lastBackslash = path.find_last_of(L'\\'); if (lastBackslash == wstring::npos) { if (path.size() >= 2 && path[1] == L':') // "C:foo.bar" becomes "C:" path.erase(2); else path.erase(); } else { if (lastBackslash == 2 && path[1] == L':' && path.size() >= 3) // "C:\foo.exe" becomes "C:\" path.erase(3); else if (lastBackslash == 0 && path.size() > 1) // "\foo.exe" becomes "\" path.erase(1); else path.erase(lastBackslash); } return path; } wstring pathAppend(wstring& strDest, const wstring& str2append) { if (strDest.empty() && str2append.empty()) // "" + "" { strDest = L"\\"; return strDest; } if (strDest.empty() && !str2append.empty()) // "" + titi { strDest = str2append; return strDest; } if (strDest[strDest.length() - 1] == '\\' && (!str2append.empty() && str2append[0] == '\\')) // toto\ + \titi { strDest.erase(strDest.length() - 1, 1); strDest += str2append; return strDest; } if ((strDest[strDest.length() - 1] == '\\' && (!str2append.empty() && str2append[0] != '\\')) // toto\ + titi || (strDest[strDest.length() - 1] != '\\' && (!str2append.empty() && str2append[0] == '\\'))) // toto + \titi { strDest += str2append; return strDest; } // toto + titi strDest += L"\\"; strDest += str2append; return strDest; } COLORREF getCtrlBgColor(HWND hWnd) { COLORREF crRet = CLR_INVALID; if (hWnd && IsWindow(hWnd)) { RECT rc; if (GetClientRect(hWnd, &rc)) { HDC hDC = GetDC(hWnd); if (hDC) { HDC hdcMem = CreateCompatibleDC(hDC); if (hdcMem) { HBITMAP hBmp = CreateCompatibleBitmap(hDC, rc.right, rc.bottom); if (hBmp) { HGDIOBJ hOld = SelectObject(hdcMem, hBmp); if (hOld) { if (SendMessage(hWnd, WM_ERASEBKGND, reinterpret_cast(hdcMem), 0)) { crRet = GetPixel(hdcMem, 2, 2); // 0, 0 is usually on the border } SelectObject(hdcMem, hOld); } DeleteObject(hBmp); } DeleteDC(hdcMem); } ReleaseDC(hWnd, hDC); } } } return crRet; } wstring stringToUpper(wstring strToConvert) { std::transform(strToConvert.begin(), strToConvert.end(), strToConvert.begin(), [](wchar_t ch){ return static_cast(towupper(ch)); } ); return strToConvert; } wstring stringToLower(wstring strToConvert) { std::transform(strToConvert.begin(), strToConvert.end(), strToConvert.begin(), ::towlower); return strToConvert; } wstring stringReplace(wstring subject, const wstring& search, const wstring& replace) { size_t pos = 0; while ((pos = subject.find(search, pos)) != std::string::npos) { subject.replace(pos, search.length(), replace); pos += replace.length(); } return subject; } void stringSplit(const wstring& input, const wstring& delimiter, std::vector& output) { size_t start = 0U; size_t end = input.find(delimiter); const size_t delimiterLength = delimiter.length(); while (end != std::string::npos) { output.push_back(input.substr(start, end - start)); start = end + delimiterLength; end = input.find(delimiter, start); } output.push_back(input.substr(start, end)); } bool str2numberVector(wstring str2convert, std::vector& numVect) { numVect.clear(); for (auto i : str2convert) { switch (i) { case ' ': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { // correct. do nothing } break; default: return false; } } std::vector v; stringSplit(str2convert, L" ", v); for (const auto& i : v) { // Don't treat empty string and the number greater than 9999 if (!i.empty() && i.length() < 5) { numVect.push_back(std::stoi(i)); } } return true; } void stringJoin(const std::vector& strings, const wstring& separator, wstring& joinedString) { size_t length = strings.size(); for (size_t i = 0; i < length; ++i) { joinedString += strings.at(i); if (i != length - 1) { joinedString += separator; } } } wstring stringTakeWhileAdmissable(const wstring& input, const wstring& admissable) { // Find first non-admissable character in "input", and remove everything after it. size_t idx = input.find_first_not_of(admissable); if (idx == std::string::npos) { return input; } else { return input.substr(0, idx); } } double stodLocale(const wstring& str, [[maybe_unused]] _locale_t loc, size_t* idx) { // Copied from the std::stod implementation but uses _wcstod_l instead of wcstod. const wchar_t* ptr = str.c_str(); errno = 0; wchar_t* eptr; #ifdef __MINGW32__ double ans = ::wcstod(ptr, &eptr); #else double ans = ::_wcstod_l(ptr, &eptr, loc); #endif if (ptr == eptr) throw std::invalid_argument("invalid stod argument"); if (errno == ERANGE) throw std::out_of_range("stod argument out of range"); if (idx != NULL) *idx = (size_t)(eptr - ptr); return ans; } bool str2Clipboard(const wstring &str2cpy, HWND hwnd) { size_t len2Allocate = (str2cpy.size() + 1) * sizeof(wchar_t); HGLOBAL hglbCopy = ::GlobalAlloc(GMEM_MOVEABLE, len2Allocate); if (hglbCopy == NULL) { return false; } if (!::OpenClipboard(hwnd)) { ::GlobalFree(hglbCopy); return false; } if (!::EmptyClipboard()) { ::GlobalFree(hglbCopy); ::CloseClipboard(); return false; } // Lock the handle and copy the text to the buffer. wchar_t *pStr = (wchar_t *)::GlobalLock(hglbCopy); if (!pStr) { ::GlobalFree(hglbCopy); ::CloseClipboard(); return false; } wcscpy_s(pStr, len2Allocate / sizeof(wchar_t), str2cpy.c_str()); ::GlobalUnlock(hglbCopy); // Place the handle on the clipboard. unsigned int clipBoardFormat = CF_UNICODETEXT; if (!::SetClipboardData(clipBoardFormat, hglbCopy)) { ::GlobalFree(hglbCopy); ::CloseClipboard(); return false; } if (!::CloseClipboard()) { return false; } return true; } bool buf2Clipboard(const std::vector& buffers, bool isFullPath, HWND hwnd) { const wstring crlf = L"\r\n"; wstring selection; for (auto&& buf : buffers) { if (buf) { const wchar_t* fileName = isFullPath ? buf->getFullPathName() : buf->getFileName(); if (fileName) selection += fileName; } if (!selection.empty() && !selection.ends_with(crlf)) selection += crlf; } if (!selection.empty()) return str2Clipboard(selection, hwnd); return false; } bool matchInList(const wchar_t *fileName, const std::vector & patterns) { bool is_matched = false; for (size_t i = 0, len = patterns.size(); i < len; ++i) { if (patterns[i].length() > 1 && patterns[i][0] == '!') { if (PathMatchSpec(fileName, patterns[i].c_str() + 1)) return false; continue; } if (PathMatchSpec(fileName, patterns[i].c_str())) is_matched = true; } return is_matched; } bool matchInExcludeDirList(const wchar_t* dirName, const std::vector& patterns, size_t level) { for (size_t i = 0, len = patterns.size(); i < len; ++i) { size_t patterLen = patterns[i].length(); if (patterLen > 3 && patterns[i][0] == '!' && patterns[i][1] == '+' && patterns[i][2] == '\\') // check for !+\folderPattern: for all levels - search this pattern recursively { if (PathMatchSpec(dirName, patterns[i].c_str() + 3)) return true; } else if (patterLen > 2 && patterns[i][0] == '!' && patterns[i][1] == '\\') // check for !\folderPattern: exclusive pattern for only the 1st level { if (level == 1) if (PathMatchSpec(dirName, patterns[i].c_str() + 2)) return true; } } return false; } bool allPatternsAreExclusion(const std::vector& patterns) { bool oneInclusionPatternFound = false; for (size_t i = 0, len = patterns.size(); i < len; ++i) { if (patterns[i][0] != '!') { oneInclusionPatternFound = true; break; } } return !oneInclusionPatternFound; } wstring GetLastErrorAsString(DWORD errorCode) { wstring errorMsg(L""); // Get the error message, if any. // If both error codes (passed error n GetLastError) are 0, then return empty if (errorCode == 0) errorCode = GetLastError(); if (errorCode == 0) return errorMsg; //No error message has been recorded LPWSTR messageBuffer = nullptr; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr); errorMsg += messageBuffer; //Free the buffer. LocalFree(messageBuffer); return errorMsg; } HWND CreateToolTip(int toolID, HWND hDlg, HINSTANCE hInst, const PTSTR pszText, bool isRTL) { if (!toolID || !hDlg || !pszText) { return NULL; } // Get the window of the tool. HWND hwndTool = GetDlgItem(hDlg, toolID); if (!hwndTool) { return NULL; } // Create the tooltip. g_hInst is the global instance handle. HWND hwndTip = CreateWindowEx(isRTL ? WS_EX_LAYOUTRTL : 0, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hDlg, NULL, hInst, NULL); if (!hwndTip) { return NULL; } NppDarkMode::setDarkTooltips(hwndTip, NppDarkMode::ToolTipsType::tooltip); // Associate the tooltip with the tool. TOOLINFO toolInfo = {}; toolInfo.cbSize = sizeof(toolInfo); toolInfo.hwnd = hDlg; toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; toolInfo.uId = (UINT_PTR)hwndTool; toolInfo.lpszText = pszText; if (!SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo)) { DestroyWindow(hwndTip); return NULL; } SendMessage(hwndTip, TTM_ACTIVATE, TRUE, 0); SendMessage(hwndTip, TTM_SETMAXTIPWIDTH, 0, 200); // Make tip stay 15 seconds SendMessage(hwndTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, MAKELPARAM((15000), (0))); return hwndTip; } HWND CreateToolTipRect(int toolID, HWND hWnd, HINSTANCE hInst, const PTSTR pszText, const RECT rc) { if (!toolID || !hWnd || !pszText) { return NULL; } // Create the tooltip. g_hInst is the global instance handle. HWND hwndTip = CreateWindowEx(0, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hWnd, NULL, hInst, NULL); if (!hwndTip) { return NULL; } // Associate the tooltip with the tool. TOOLINFO toolInfo = {}; toolInfo.cbSize = sizeof(toolInfo); toolInfo.hwnd = hWnd; toolInfo.uFlags = TTF_SUBCLASS; toolInfo.uId = toolID; toolInfo.lpszText = pszText; toolInfo.rect = rc; if (!SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo)) { DestroyWindow(hwndTip); return NULL; } SendMessage(hwndTip, TTM_ACTIVATE, TRUE, 0); SendMessage(hwndTip, TTM_SETMAXTIPWIDTH, 0, 200); // Make tip stay 15 seconds SendMessage(hwndTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, MAKELPARAM((15000), (0))); return hwndTip; } bool isCertificateValidated(const wstring & fullFilePath, const wstring & subjectName2check) { bool isOK = false; HCERTSTORE hStore = NULL; HCRYPTMSG hMsg = NULL; PCCERT_CONTEXT pCertContext = NULL; BOOL result = FALSE; DWORD dwEncoding = 0; DWORD dwContentType = 0; DWORD dwFormatType = 0; PCMSG_SIGNER_INFO pSignerInfo = NULL; DWORD dwSignerInfo = 0; CERT_INFO CertInfo{}; LPTSTR szName = NULL; try { // Get message handle and store handle from the signed file. result = CryptQueryObject(CERT_QUERY_OBJECT_FILE, fullFilePath.c_str(), CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_FORMAT_FLAG_BINARY, 0, &dwEncoding, &dwContentType, &dwFormatType, &hStore, &hMsg, NULL); if (!result) { wstring errorMessage = L"Check certificate of " + fullFilePath + L" : "; errorMessage += GetLastErrorAsString(GetLastError()); throw errorMessage; } // Get signer information size. result = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo); if (!result) { wstring errorMessage = L"CryptMsgGetParam first call: "; errorMessage += GetLastErrorAsString(GetLastError()); throw errorMessage; } // Allocate memory for signer information. pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo); if (!pSignerInfo) { wstring errorMessage = L"CryptMsgGetParam memory allocation problem: "; errorMessage += GetLastErrorAsString(GetLastError()); throw errorMessage; } // Get Signer Information. result = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfo); if (!result) { wstring errorMessage = L"CryptMsgGetParam: "; errorMessage += GetLastErrorAsString(GetLastError()); throw errorMessage; } // Search for the signer certificate in the temporary // certificate store. CertInfo.Issuer = pSignerInfo->Issuer; CertInfo.SerialNumber = pSignerInfo->SerialNumber; pCertContext = CertFindCertificateInStore(hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)&CertInfo, NULL); if (!pCertContext) { wstring errorMessage = L"Certificate context: "; errorMessage += GetLastErrorAsString(GetLastError()); throw errorMessage; } DWORD dwData; // Get Subject name size. dwData = CertGetNameString(pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0); if (dwData <= 1) { throw wstring(L"Certificate checking error: getting data size problem."); } // Allocate memory for subject name. szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(wchar_t)); if (!szName) { throw wstring(L"Certificate checking error: memory allocation problem."); } // Get subject name. if (CertGetNameString(pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, szName, dwData) <= 1) { throw wstring(L"Cannot get certificate info."); } // check Subject name. wstring subjectName = szName; if (subjectName != subjectName2check) { throw wstring(L"Certificate checking error: the certificate is not matched."); } isOK = true; } catch (const wstring& s) { // display error message MessageBox(NULL, s.c_str(), L"Certificate checking", MB_OK); } catch (...) { // Unknown error wstring errorMessage = L"Unknown exception occured. "; errorMessage += GetLastErrorAsString(GetLastError()); MessageBox(NULL, errorMessage.c_str(), L"Certificate checking", MB_OK); } // Clean up. if (pSignerInfo != NULL) LocalFree(pSignerInfo); if (pCertContext != NULL) CertFreeCertificateContext(pCertContext); if (hStore != NULL) CertCloseStore(hStore, 0); if (hMsg != NULL) CryptMsgClose(hMsg); if (szName != NULL) LocalFree(szName); return isOK; } bool isAssoCommandExisting(LPCTSTR FullPathName) { bool isAssoCmdExist = false; bool isFileExisting = doesFileExist(FullPathName); if (isFileExisting) { PTSTR ext = PathFindExtension(FullPathName); HRESULT hres; wchar_t buffer[MAX_PATH] = L""; DWORD bufferLen = MAX_PATH; // check if association exist hres = AssocQueryString(ASSOCF_VERIFY|ASSOCF_INIT_IGNOREUNKNOWN, ASSOCSTR_COMMAND, ext, NULL, buffer, &bufferLen); isAssoCmdExist = (hres == S_OK) // check if association exist and no error && (wcsstr(buffer, L"notepad++.exe")) == NULL; // check association with notepad++ } return isAssoCmdExist; } std::wstring s2ws(const std::string& str) { using convert_typeX = std::codecvt_utf8; std::wstring_convert converterX("Error in Notepad++ string conversion s2ws!", L"Error in Notepad++ string conversion s2ws!"); return converterX.from_bytes(str); } std::string ws2s(const std::wstring& wstr) { using convert_typeX = std::codecvt_utf8; std::wstring_convert converterX("Error in Notepad++ string conversion ws2s!", L"Error in Notepad++ string conversion ws2s!"); return converterX.to_bytes(wstr); } bool deleteFileOrFolder(const wstring& f2delete) { auto len = f2delete.length(); wchar_t* actionFolder = new wchar_t[len + 2]; wcscpy_s(actionFolder, len + 2, f2delete.c_str()); actionFolder[len] = 0; actionFolder[len + 1] = 0; SHFILEOPSTRUCT fileOpStruct = {}; fileOpStruct.hwnd = NULL; fileOpStruct.pFrom = actionFolder; fileOpStruct.pTo = NULL; fileOpStruct.wFunc = FO_DELETE; fileOpStruct.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_ALLOWUNDO; fileOpStruct.fAnyOperationsAborted = false; fileOpStruct.hNameMappings = NULL; fileOpStruct.lpszProgressTitle = NULL; int res = SHFileOperation(&fileOpStruct); delete[] actionFolder; return (res == 0); } // Get a vector of full file paths in a given folder. File extension type filter should be *.*, *.xml, *.dll... according the type of file you want to get. void getFilesInFolder(std::vector& files, const wstring& extTypeFilter, const wstring& inFolder) { wstring filter = inFolder; pathAppend(filter, extTypeFilter); WIN32_FIND_DATA foundData; HANDLE hFindFile = ::FindFirstFile(filter.c_str(), &foundData); if (hFindFile != INVALID_HANDLE_VALUE) { do { if (!(foundData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { wstring foundFullPath = inFolder; pathAppend(foundFullPath, foundData.cFileName); files.push_back(foundFullPath); } } while (::FindNextFile(hFindFile, &foundData)); ::FindClose(hFindFile); } } // remove any leading or trailing spaces from str void trim(std::wstring& str) { std::wstring::size_type pos = str.find_last_not_of(' '); if (pos != std::wstring::npos) { str.erase(pos + 1); pos = str.find_first_not_of(' '); if (pos != std::wstring::npos) str.erase(0, pos); } else str.erase(str.begin(), str.end()); } int nbDigitsFromNbLines(size_t nbLines) { int nbDigits = 0; // minimum number of digit should be 4 if (nbLines < 10) nbDigits = 1; else if (nbLines < 100) nbDigits = 2; else if (nbLines < 1000) nbDigits = 3; else if (nbLines < 10000) nbDigits = 4; else if (nbLines < 100000) nbDigits = 5; else if (nbLines < 1000000) nbDigits = 6; else // rare case { nbDigits = 7; nbLines /= 10000000; while (nbLines) { nbLines /= 10; ++nbDigits; } } return nbDigits; } namespace { constexpr wchar_t timeFmtEscapeChar = 0x1; constexpr wchar_t middayFormat[] = L"tt"; // Returns AM/PM string defined by the system locale for the specified time. // This string may be empty or customized. wstring getMiddayString(const wchar_t* localeName, const SYSTEMTIME& st) { wstring midday; midday.resize(MAX_PATH); int ret = GetTimeFormatEx(localeName, 0, &st, middayFormat, &midday[0], static_cast(midday.size())); if (ret > 0) midday.resize(ret - 1); // Remove the null-terminator. else midday.clear(); return midday; } // Replaces conflicting time format specifiers by a special character. bool escapeTimeFormat(wstring& format) { bool modified = false; for (auto& ch : format) { if (ch == middayFormat[0]) { ch = timeFmtEscapeChar; modified = true; } } return modified; } // Replaces special time format characters by actual AM/PM string. void unescapeTimeFormat(wstring& format, const wstring& midday) { if (midday.empty()) { auto it = std::remove(format.begin(), format.end(), timeFmtEscapeChar); if (it != format.end()) format.erase(it, format.end()); } else { size_t i = 0; while ((i = format.find(timeFmtEscapeChar, i)) != wstring::npos) { if (i + 1 < format.size() && format[i + 1] == timeFmtEscapeChar) { // 'tt' => AM/PM format.erase(i, std::size(middayFormat) - 1); format.insert(i, midday); } else { // 't' => A/P format[i] = midday[0]; } } } } } wstring getDateTimeStrFrom(const wstring& dateTimeFormat, const SYSTEMTIME& st) { const wchar_t* localeName = LOCALE_NAME_USER_DEFAULT; const DWORD flags = 0; constexpr int bufferSize = MAX_PATH; wchar_t buffer[bufferSize] = {}; int ret = 0; // 1. Escape 'tt' that means AM/PM or 't' that means A/P. // This is needed to avoid conflict with 'M' date format that stands for month. wstring newFormat = dateTimeFormat; const bool hasMiddayFormat = escapeTimeFormat(newFormat); // 2. Format the time (h/m/s/t/H). ret = GetTimeFormatEx(localeName, flags, &st, newFormat.c_str(), buffer, bufferSize); if (ret != 0) { // 3. Format the date (d/y/g/M). // Now use the buffer as a format string to process the format specifiers not recognized by GetTimeFormatEx(). ret = GetDateFormatEx(localeName, flags, &st, buffer, buffer, bufferSize, nullptr); } if (ret != 0) { if (hasMiddayFormat) { // 4. Now format only the AM/PM string. const wstring midday = getMiddayString(localeName, st); wstring result = buffer; unescapeTimeFormat(result, midday); return result; } return buffer; } return {}; } // Don't forget to use DeleteObject(createdFont) before leaving the program HFONT createFont(const wchar_t* fontName, int fontSize, bool isBold, HWND hDestParent) { HDC hdc = GetDC(hDestParent); LOGFONT logFont{}; logFont.lfHeight = DPIManagerV2::scaleFont(fontSize, hDestParent); if (isBold) logFont.lfWeight = FW_BOLD; wcscpy_s(logFont.lfFaceName, fontName); HFONT newFont = CreateFontIndirect(&logFont); ReleaseDC(hDestParent, hdc); return newFont; } bool removeReadOnlyFlagFromFileAttributes(const wchar_t* fileFullPath) { DWORD dwFileAttribs = ::GetFileAttributes(fileFullPath); if (dwFileAttribs == INVALID_FILE_ATTRIBUTES || (dwFileAttribs & FILE_ATTRIBUTE_DIRECTORY)) return false; dwFileAttribs &= ~FILE_ATTRIBUTE_READONLY; return (::SetFileAttributes(fileFullPath, dwFileAttribs) != FALSE); } // "For file I/O, the "\\?\" prefix to a path string tells the Windows APIs to disable all string parsing // and to send the string that follows it straight to the file system..." // Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces bool isWin32NamespacePrefixedFileName(const wstring& fileName) { // TODO: // ?! how to handle similar NT Object Manager path style prefix case \??\... // (the \??\ prefix instructs the NT Object Manager to search in the caller's local device directory for an alias...) // the following covers the \\?\... raw Win32-filenames or the \\?\UNC\... UNC equivalents // and also its *nix like forward slash equivalents return (fileName.starts_with(L"\\\\?\\") || fileName.starts_with(L"//?/")); } bool isWin32NamespacePrefixedFileName(const wchar_t* szFileName) { const wstring fileName = szFileName; return isWin32NamespacePrefixedFileName(fileName); } bool isUnsupportedFileName(const wstring& fileName) { bool isUnsupported = true; // until the Notepad++ (and its plugins) will not be prepared for filenames longer than the MAX_PATH, // we have to limit also the maximum supported length below if ((fileName.size() > 0) && (fileName.size() < MAX_PATH)) { // possible raw filenames can contain space(s) or dot(s) at its end (e.g. "\\?\C:\file."), but the Notepad++ advanced // Open/SaveAs IFileOpenDialog/IFileSaveDialog COM-interface based dialogs currently do not handle this well // (but e.g. direct Notepad++ Ctrl+S works ok even with these filenames) // // Exception for the standard filenames ending with the dot-char: // - when someone tries to open e.g. the 'C:\file.', we will accept that as this is the way how to work with filenames // without an extension (some of the WINAPI calls used later trim that dot-char automatically ...) if (!(fileName.ends_with(L'.') && isWin32NamespacePrefixedFileName(fileName)) && !fileName.ends_with(L' ')) { bool invalidASCIIChar = false; for (size_t pos = 0; pos < fileName.size(); ++pos) { wchar_t c = fileName.at(pos); if (c <= 31) { invalidASCIIChar = true; } else { // as this could be also a complete filename with path and there could be also a globbing used, // we tolerate here some other reserved Win32-filename chars: /, \, :, ?, * switch (c) { case '<': case '>': case '"': case '|': invalidASCIIChar = true; break; } } if (invalidASCIIChar) break; } if (!invalidASCIIChar) { // strip input string to a filename without a possible path and extension(s) wstring fileNameOnly; size_t pos = fileName.find_first_of(L"."); if (pos != std::string::npos) fileNameOnly = fileName.substr(0, pos); else fileNameOnly = fileName; pos = fileNameOnly.find_last_of(L"\\"); if (pos == std::string::npos) pos = fileNameOnly.find_last_of(L"/"); if (pos != std::string::npos) fileNameOnly = fileNameOnly.substr(pos + 1); // upperize because the std::find is case sensitive unlike the Windows OS filesystem std::transform(fileNameOnly.begin(), fileNameOnly.end(), fileNameOnly.begin(), ::towupper); const std::vector reservedWin32NamespaceDeviceList { L"CON", L"PRN", L"AUX", L"NUL", L"COM1", L"COM2", L"COM3", L"COM4", L"COM5", L"COM6", L"COM7", L"COM8", L"COM9", L"LPT1", L"LPT2", L"LPT3", L"LPT4", L"LPT5", L"LPT6", L"LPT7", L"LPT8", L"LPT9" }; // last check is for all the old reserved Windows OS filenames if (std::find(reservedWin32NamespaceDeviceList.begin(), reservedWin32NamespaceDeviceList.end(), fileNameOnly) == reservedWin32NamespaceDeviceList.end()) { // ok, the current filename tested is not even on the blacklist isUnsupported = false; } } } } return isUnsupported; } bool isUnsupportedFileName(const wchar_t* szFileName) { const wstring fileName = szFileName; return isUnsupportedFileName(fileName); } Version::Version(const wstring& versionStr) { try { auto ss = tokenizeString(versionStr, '.'); if (ss.size() > 4) { std::wstring msg(L"\""); msg += versionStr; msg += L"\""; msg += L": Version parts are more than 4. The string to parse is not a valid version format. Let's make it default value in catch block."; throw msg; } int i = 0; std::vector v = { &_major, &_minor, &_patch, &_build }; for (const auto& s : ss) { if (!isNumber(s)) { std::wstring msg(L"\""); msg += versionStr; msg += L"\""; msg += L": One of version character is not number. The string to parse is not a valid version format. Let's make it default value in catch block."; throw msg; } *(v[i]) = std::stoi(s); ++i; } } #ifdef DEBUG catch (const std::wstring& s) { _major = 0; _minor = 0; _patch = 0; _build = 0; throw s; } #endif catch (...) { _major = 0; _minor = 0; _patch = 0; _build = 0; #ifdef DEBUG throw std::wstring(L"Unknown exception from \"Version::Version(const wstring& versionStr)\""); #endif } } void Version::setVersionFrom(const wstring& filePath) { if (filePath.empty() || !doesFileExist(filePath.c_str())) return; DWORD uselessArg = 0; // this variable is for passing the ignored argument to the functions DWORD bufferSize = ::GetFileVersionInfoSize(filePath.c_str(), &uselessArg); if (bufferSize <= 0) return; unsigned char* buffer = new unsigned char[bufferSize]; ::GetFileVersionInfo(filePath.c_str(), 0, bufferSize, buffer); VS_FIXEDFILEINFO* lpFileInfo = nullptr; UINT cbFileInfo = 0; VerQueryValue(buffer, L"\\", reinterpret_cast(&lpFileInfo), &cbFileInfo); if (cbFileInfo) { _major = (lpFileInfo->dwFileVersionMS & 0xFFFF0000) >> 16; _minor = lpFileInfo->dwFileVersionMS & 0x0000FFFF; _patch = (lpFileInfo->dwFileVersionLS & 0xFFFF0000) >> 16; _build = lpFileInfo->dwFileVersionLS & 0x0000FFFF; } delete[] buffer; } wstring Version::toString() { if (_build == 0 && _patch == 0 && _minor == 0 && _major == 0) // "" { return L""; } else if (_build == 0 && _patch == 0 && _minor == 0) // "major" { return std::to_wstring(_major); } else if (_build == 0 && _patch == 0) // "major.minor" { std::wstring v = std::to_wstring(_major); v += L"."; v += std::to_wstring(_minor); return v; } else if (_build == 0) // "major.minor.patch" { std::wstring v = std::to_wstring(_major); v += L"."; v += std::to_wstring(_minor); v += L"."; v += std::to_wstring(_patch); return v; } // "major.minor.patch.build" std::wstring ver = std::to_wstring(_major); ver += L"."; ver += std::to_wstring(_minor); ver += L"."; ver += std::to_wstring(_patch); ver += L"."; ver += std::to_wstring(_build); return ver; } int Version::compareTo(const Version& v2c) const { if (_major > v2c._major) return 1; else if (_major < v2c._major) return -1; else // (_major == v2c._major) { if (_minor > v2c._minor) return 1; else if (_minor < v2c._minor) return -1; else // (_minor == v2c._minor) { if (_patch > v2c._patch) return 1; else if (_patch < v2c._patch) return -1; else // (_patch == v2c._patch) { if (_build > v2c._build) return 1; else if (_build < v2c._build) return -1; else // (_build == v2c._build) { return 0; } } } } } bool Version::isCompatibleTo(const Version& from, const Version& to) const { // This method determinates if Version object is in between "from" version and "to" version, it's useful for testing compatibility of application. // test in versions example: // 1. <0.0.0.0, 0.0.0.0>: both from to versions are empty, so it's // 2. <6.9, 6.9>: plugin is compatible to only v6.9 // 3. <4.2, 6.6.6>: from v4.2 (included) to v6.6.6 (included) // 4. <0.0.0.0, 8.2.1>: all version until v8.2.1 (included) // 5. <8.3, 0.0.0.0>: from v8.3 (included) to the latest verrsion if (empty()) // if this version is empty, then no compatible to all version return false; if (from.empty() && to.empty()) // both versions "from" and "to" are empty: it's considered compatible, whatever this version is (match to 1) { return true; } if (from <= *this && to >= *this) // from_ver <= this_ver <= to_ver (match to 2, 3 and 4) { return true; } if (from <= *this && to.empty()) // from_ver <= this_ver (match to 5) { return true; } return false; } #define DEFAULT_MILLISEC 1000 //---------------------------------------------------- struct GetAttrParamResult { wstring _filePath; DWORD _fileAttr = INVALID_FILE_ATTRIBUTES; bool _isNetworkFailure = true; }; DWORD WINAPI getFileAttributesWorker(void* data) { GetAttrParamResult* inAndOut = static_cast(data); inAndOut->_fileAttr = ::GetFileAttributesW(inAndOut->_filePath.c_str()); inAndOut->_isNetworkFailure = false; return ERROR_SUCCESS; }; DWORD getFileAttrWaitSec(const wchar_t* filePath, DWORD milliSec2wait, bool* isNetWorkProblem) { GetAttrParamResult data(filePath); HANDLE hThread = ::CreateThread(NULL, 0, getFileAttributesWorker, &data, 0, NULL); if (!hThread) { return INVALID_FILE_ATTRIBUTES; } // wait for our worker thread to complete or terminate it when the required timeout has elapsed DWORD dwWaitStatus = ::WaitForSingleObject(hThread, milliSec2wait == 0 ? DEFAULT_MILLISEC : milliSec2wait); switch (dwWaitStatus) { case WAIT_OBJECT_0: // Ok, the state of our worker thread is signaled, so it finished itself in the timeout given // - nothing else to do here, except the thread handle closing later break; case WAIT_TIMEOUT: // the timeout interval elapsed, but the worker's state is still non-signaled default: // any other dwWaitStatus is a BAD one here // WAIT_FAILED or WAIT_ABANDONED ::TerminateThread(hThread, dwWaitStatus); break; } CloseHandle(hThread); if (isNetWorkProblem != nullptr) *isNetWorkProblem = data._isNetworkFailure; return data._fileAttr; }; bool doesFileExist(const wchar_t* filePath, DWORD milliSec2wait, bool* isNetWorkProblem) { DWORD attr = getFileAttrWaitSec(filePath, milliSec2wait, isNetWorkProblem); return (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)); } bool doesDirectoryExist(const wchar_t* dirPath, DWORD milliSec2wait, bool* isNetWorkProblem) { DWORD attr = getFileAttrWaitSec(dirPath, milliSec2wait, isNetWorkProblem); return (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)); } bool doesPathExist(const wchar_t* path, DWORD milliSec2wait, bool* isNetWorkProblem) { DWORD attr = getFileAttrWaitSec(path, milliSec2wait, isNetWorkProblem); return (attr != INVALID_FILE_ATTRIBUTES); } //---------------------------------------------------- struct GetDiskFreeSpaceParamResult { std::wstring _dirPath; ULARGE_INTEGER _freeBytesForUser {}; DWORD _result = FALSE; bool _isNetworkFailure = true; GetDiskFreeSpaceParamResult(wstring dirPath) : _dirPath(dirPath) {}; }; DWORD WINAPI getDiskFreeSpaceExWorker(void* data) { GetDiskFreeSpaceParamResult* inAndOut = static_cast(data); inAndOut->_result = ::GetDiskFreeSpaceExW(inAndOut->_dirPath.c_str(), &(inAndOut->_freeBytesForUser), nullptr, nullptr); inAndOut->_isNetworkFailure = false; return ERROR_SUCCESS; }; DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milliSec2wait, bool* isNetWorkProblem) { GetDiskFreeSpaceParamResult data(dirPath); HANDLE hThread = ::CreateThread(NULL, 0, getDiskFreeSpaceExWorker, &data, 0, NULL); if (!hThread) { return FALSE; } // wait for our worker thread to complete or terminate it when the required timeout has elapsed DWORD dwWaitStatus = ::WaitForSingleObject(hThread, milliSec2wait == 0 ? DEFAULT_MILLISEC : milliSec2wait); switch (dwWaitStatus) { case WAIT_OBJECT_0: // Ok, the state of our worker thread is signaled, so it finished itself in the timeout given // - nothing else to do here, except the thread handle closing later break; case WAIT_TIMEOUT: // the timeout interval elapsed, but the worker's state is still non-signaled default: // any other dwWaitStatus is a BAD one here // WAIT_FAILED or WAIT_ABANDONED ::TerminateThread(hThread, dwWaitStatus); break; } CloseHandle(hThread); *freeBytesForUser = data._freeBytesForUser; if (isNetWorkProblem != nullptr) *isNetWorkProblem = data._isNetworkFailure; return data._result; } //---------------------------------------------------- struct GetAttrExParamResult { wstring _filePath; WIN32_FILE_ATTRIBUTE_DATA _attributes{}; DWORD _result = FALSE; bool _isNetworkFailure = true; GetAttrExParamResult(wstring filePath): _filePath(filePath) { _attributes.dwFileAttributes = INVALID_FILE_ATTRIBUTES; } }; DWORD WINAPI getFileAttributesExWorker(void* data) { GetAttrExParamResult* inAndOut = static_cast(data); inAndOut->_result = ::GetFileAttributesEx(inAndOut->_filePath.c_str(), GetFileExInfoStandard, &(inAndOut->_attributes)); inAndOut->_isNetworkFailure = false; return ERROR_SUCCESS; }; DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milliSec2wait, bool* isNetWorkProblem) { GetAttrExParamResult data(filePath); HANDLE hThread = ::CreateThread(NULL, 0, getFileAttributesExWorker, &data, 0, NULL); if (!hThread) { return FALSE; } // wait for our worker thread to complete or terminate it when the required timeout has elapsed DWORD dwWaitStatus = ::WaitForSingleObject(hThread, milliSec2wait == 0 ? DEFAULT_MILLISEC : milliSec2wait); switch (dwWaitStatus) { case WAIT_OBJECT_0: // Ok, the state of our worker thread is signaled, so it finished itself in the timeout given // - nothing else to do here, except the thread handle closing later break; case WAIT_TIMEOUT: // the timeout interval elapsed, but the worker's state is still non-signaled default: // any other dwWaitStatus is a BAD one here // WAIT_FAILED or WAIT_ABANDONED ::TerminateThread(hThread, dwWaitStatus); break; } CloseHandle(hThread); *fileAttr = data._attributes; if (isNetWorkProblem != nullptr) *isNetWorkProblem = data._isNetworkFailure; return data._result; } //---------------------------------------------------- struct CreateFileParamResult { wstring _filePath; HANDLE _hFile = INVALID_HANDLE_VALUE; DWORD _accessParam = GENERIC_READ | GENERIC_WRITE; DWORD _shareParam = FILE_SHARE_READ | FILE_SHARE_WRITE; DWORD _dispParam = CREATE_ALWAYS; DWORD _attribParam = FILE_ATTRIBUTE_NORMAL; bool _isNetworkFailure = true; CreateFileParamResult(wstring filePath, DWORD accessParam, DWORD shareParam, DWORD dispParam, DWORD attribParam) : _filePath(filePath), _accessParam(accessParam), _shareParam(shareParam), _dispParam(dispParam), _attribParam(attribParam) {}; }; DWORD WINAPI createFileWorker(void* data) { CreateFileParamResult* inAndOut = static_cast(data); inAndOut->_hFile = ::CreateFileW(inAndOut->_filePath.c_str(), inAndOut->_accessParam, inAndOut->_shareParam, NULL, inAndOut->_dispParam, inAndOut->_attribParam, NULL); inAndOut->_isNetworkFailure = false; return ERROR_SUCCESS; }; HANDLE createFileWaitSec(const wchar_t* filePath, DWORD accessParam, DWORD shareParam, DWORD dispParam, DWORD attribParam, DWORD milliSec2wait, bool* isNetWorkProblem) { CreateFileParamResult data(filePath, accessParam, shareParam, dispParam, attribParam); HANDLE hThread = ::CreateThread(NULL, 0, createFileWorker, &data, 0, NULL); if (!hThread) { return INVALID_HANDLE_VALUE; } // wait for our worker thread to complete or terminate it when the required timeout has elapsed DWORD dwWaitStatus = ::WaitForSingleObject(hThread, milliSec2wait == 0 ? DEFAULT_MILLISEC : milliSec2wait); switch (dwWaitStatus) { case WAIT_OBJECT_0: // Ok, the state of our worker thread is signaled, so it finished itself in the timeout given // - nothing else to do here, except the thread handle closing later break; case WAIT_TIMEOUT: // the timeout interval elapsed, but the worker's state is still non-signaled default: // Timeout reached, or WAIT_FAILED or WAIT_ABANDONED // attempt to cancel the operation ::CancelIoEx(data._hFile, NULL); ::TerminateThread(hThread, dwWaitStatus); break; } CloseHandle(hThread); if (isNetWorkProblem != nullptr) *isNetWorkProblem = data._isNetworkFailure; return data._hFile; }