From f884a39dd422cda908b36e9439fb00c340c4d85e Mon Sep 17 00:00:00 2001 From: Don Ho Date: Sat, 28 Sep 2024 00:01:57 +0200 Subject: [PATCH] Fix opened network files hanging issue while the network disconnected The issue is due to WinAPI's GetFileAttributes function can take a huge amount of time in various situations, like when the target server being offline (usalally). The current solution is to wait at least 3 seconds for each GetFileAttributes function, then return the default value in case of the problem (or the returned value of the function, in case of normal situation - if it has been executed completely during the 3 seconds). So there'll still be the hanging time (3 or 6 seconds) while the problem of network connection, but the hanging time (it could be 30 seconds more) is reduced considerablly. "Wow64EnableWow64FsRedirection" call is also removed from x64 build (in which this call is unnecessary) in this commit to reduce the IO calls. Fix #4306, fix #6178, fix #8055, fix #11388, fix #12553, fix #15540, close #15658 --- PowerEditor/src/MISC/Common/Common.cpp | 189 +++++++++++++++-- PowerEditor/src/MISC/Common/Common.h | 12 +- PowerEditor/src/Notepad_plus.cpp | 12 +- PowerEditor/src/NppCommands.cpp | 2 +- PowerEditor/src/NppIO.cpp | 196 ++++++++++-------- PowerEditor/src/Parameters.cpp | 4 +- PowerEditor/src/Parameters.h | 2 + PowerEditor/src/ScintillaComponent/Buffer.cpp | 27 ++- PowerEditor/src/ScintillaComponent/Buffer.h | 5 +- .../src/ScintillaComponent/FindReplaceDlg.cpp | 2 +- .../WinControls/FileBrowser/fileBrowser.cpp | 4 +- .../OpenSaveFileDialog/CustomFileDialog.cpp | 5 +- 12 files changed, 333 insertions(+), 127 deletions(-) diff --git a/PowerEditor/src/MISC/Common/Common.cpp b/PowerEditor/src/MISC/Common/Common.cpp index 679ce6293..77a50ca4e 100644 --- a/PowerEditor/src/MISC/Common/Common.cpp +++ b/PowerEditor/src/MISC/Common/Common.cpp @@ -1213,7 +1213,7 @@ bool isCertificateValidated(const wstring & fullFilePath, const wstring & subjec bool isAssoCommandExisting(LPCTSTR FullPathName) { - bool isAssoCommandExisting = false; + bool isAssoCmdExist = false; bool isFileExisting = doesFileExist(FullPathName); @@ -1228,11 +1228,11 @@ bool isAssoCommandExisting(LPCTSTR FullPathName) // check if association exist hres = AssocQueryString(ASSOCF_VERIFY|ASSOCF_INIT_IGNOREUNKNOWN, ASSOCSTR_COMMAND, ext, NULL, buffer, &bufferLen); - isAssoCommandExisting = (hres == S_OK) // check if association exist and no error - && (wcsstr(buffer, L"notepad++.exe")) == NULL; // check association with notepad++ + isAssoCmdExist = (hres == S_OK) // check if association exist and no error + && (wcsstr(buffer, L"notepad++.exe")) == NULL; // check association with notepad++ } - return isAssoCommandExisting; + return isAssoCmdExist; } std::wstring s2ws(const std::string& str) @@ -1764,20 +1764,183 @@ bool Version::isCompatibleTo(const Version& from, const Version& to) const return false; } -bool doesFileExist(const wchar_t* filePath) +//---------------------------------------------------- +struct GetAttrParamResult { + std::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 TRUE; +}; + +DWORD getFileAttrWaitSec(const wchar_t* filePath, DWORD milleSec2wait, bool* isNetWorkProblem) { - DWORD dwAttrib = ::GetFileAttributesW(filePath); - return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); + GetAttrParamResult data; + data._fileAttr = INVALID_FILE_ATTRIBUTES; + data._filePath = filePath; + data._isNetworkFailure = true; + + 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, milleSec2wait == 0 ? 1000 : milleSec2wait); + 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 milleSec2wait, bool* isNetWorkProblem) +{ + DWORD attr = getFileAttrWaitSec(filePath, milleSec2wait, isNetWorkProblem); + return (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)); +} + +bool doesDirectoryExist(const wchar_t* dirPath, DWORD milleSec2wait, bool* isNetWorkProblem) +{ + DWORD attr = getFileAttrWaitSec(dirPath, milleSec2wait, isNetWorkProblem); + return (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)); } -bool doesDirectoryExist(const wchar_t* dirPath) +bool doesPathExist(const wchar_t* path, DWORD milleSec2wait, bool* isNetWorkProblem) { - DWORD dwAttrib = ::GetFileAttributesW(dirPath); - return (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); + DWORD attr = getFileAttrWaitSec(path, milleSec2wait, isNetWorkProblem); + return (attr != INVALID_FILE_ATTRIBUTES); } -bool doesPathExist(const wchar_t* path) +//---------------------------------------------------- + +struct GetDiskFreeSpaceParamResult +{ + std::wstring _dirPath; + ULARGE_INTEGER _freeBytesForUser {}; + DWORD _result = FALSE; + bool _isNetworkFailure = true; +}; + +DWORD WINAPI getDiskFreeSpaceExWorker(void* data) { - DWORD dwAttrib = ::GetFileAttributesW(path); - return (dwAttrib != INVALID_FILE_ATTRIBUTES); + GetDiskFreeSpaceParamResult* inAndOut = static_cast(data); + inAndOut->_result = ::GetDiskFreeSpaceExW(inAndOut->_dirPath.c_str(), &(inAndOut->_freeBytesForUser), nullptr, nullptr); + inAndOut->_isNetworkFailure = false; + return TRUE; +}; + +DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milleSec2wait, bool* isNetWorkProblem) +{ + GetDiskFreeSpaceParamResult data; + data._dirPath = dirPath; + data._freeBytesForUser = {}; + data._result = FALSE; + data._isNetworkFailure = true; + + 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, milleSec2wait == 0 ? 1000 : milleSec2wait); + 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 +{ + std::wstring _filePath; + WIN32_FILE_ATTRIBUTE_DATA _attributes{}; + DWORD _result = FALSE; + bool _isNetworkFailure = true; +}; + +DWORD WINAPI getFileAttributesExWorker(void* data) +{ + GetAttrExParamResult* inAndOut = static_cast(data); + inAndOut->_result = ::GetFileAttributesEx(inAndOut->_filePath.c_str(), GetFileExInfoStandard, &(inAndOut->_attributes)); + inAndOut->_isNetworkFailure = false; + return TRUE; +}; + +DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milleSec2wait, bool* isNetWorkProblem) +{ + GetAttrExParamResult data; + data._filePath = filePath; + data._attributes = {}; + data._result = FALSE; + data._isNetworkFailure = true; + + 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, milleSec2wait == 0 ? 1000 : milleSec2wait); + 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; } diff --git a/PowerEditor/src/MISC/Common/Common.h b/PowerEditor/src/MISC/Common/Common.h index 7717f80fd..d3318acff 100644 --- a/PowerEditor/src/MISC/Common/Common.h +++ b/PowerEditor/src/MISC/Common/Common.h @@ -25,7 +25,8 @@ #include #include -#pragma deprecated(PathFileExists) // Use doesFileExist, doesDirectoryExist or doesPathExist (for file or directory) instead. +#pragma deprecated(PathFileExists) // Use doesFileExist, doesDirectoryExist or doesPathExist (for file or directory) instead. +#pragma deprecated(PathIsDirectory) // Use doesDirectoryExist instead. const bool dirUp = true; @@ -280,6 +281,9 @@ private: unsigned long _build = 0; }; -bool doesFileExist(const wchar_t* filePath); -bool doesDirectoryExist(const wchar_t* dirPath); -bool doesPathExist(const wchar_t* path); \ No newline at end of file +bool doesFileExist(const wchar_t* filePath, DWORD milleSec2wait = 0, bool* isNetWorkProblem = nullptr); +bool doesDirectoryExist(const wchar_t* dirPath, DWORD milleSec2wait = 0, bool* isNetWorkProblem = nullptr); +bool doesPathExist(const wchar_t* path, DWORD milleSec2wait = 0, bool* isNetWorkProblem = nullptr); + +DWORD getDiskFreeSpaceWaitSec(const wchar_t* dirPath, ULARGE_INTEGER* freeBytesForUser, DWORD milleSec2wait = 0, bool* isNetWorkProblem = nullptr); +DWORD getFileAttributesExWaitSec(const wchar_t* filePath, WIN32_FILE_ATTRIBUTE_DATA* fileAttr, DWORD milleSec2wait = 0, bool* isNetWorkProblem = nullptr); diff --git a/PowerEditor/src/Notepad_plus.cpp b/PowerEditor/src/Notepad_plus.cpp index 77a8ee21a..a961e03c9 100644 --- a/PowerEditor/src/Notepad_plus.cpp +++ b/PowerEditor/src/Notepad_plus.cpp @@ -2579,7 +2579,8 @@ void Notepad_plus::checkDocState() bool isCurrentDirty = curBuf->isDirty(); bool isSeveralDirty = isCurrentDirty; - bool isFileExisting = doesFileExist(curBuf->getFullPathName()); + bool isNetworkProblem; + bool isFileExisting = doesFileExist(curBuf->getFullPathName(), 1000, &isNetworkProblem); if (!isCurrentDirty) { for (size_t i = 0; i < MainFileManager.getNbBuffers(); ++i) @@ -2613,7 +2614,7 @@ void Notepad_plus::checkDocState() enableCommand(IDM_FILE_RELOAD, isFileExisting, MENU); enableCommand(IDM_FILE_CONTAININGFOLDERASWORKSPACE, isFileExisting, MENU); - enableCommand(IDM_FILE_OPEN_DEFAULT_VIEWER, isAssoCommandExisting(curBuf->getFullPathName()), MENU); + enableCommand(IDM_FILE_OPEN_DEFAULT_VIEWER, isFileExisting ? isAssoCommandExisting(curBuf->getFullPathName()) : false, MENU); enableCommand(IDM_VIEW_IN_FIREFOX, isFileExisting, MENU); enableCommand(IDM_VIEW_IN_CHROME, isFileExisting, MENU); @@ -4435,7 +4436,7 @@ void Notepad_plus::dropFiles(HDROP hdrop) { wchar_t pathDropped[MAX_PATH]; ::DragQueryFile(hdrop, i, pathDropped, MAX_PATH); - if (::PathIsDirectory(pathDropped)) + if (doesDirectoryExist(pathDropped)) { size_t len = lstrlen(pathDropped); if (len > 0 && pathDropped[len - 1] != wchar_t('\\')) @@ -7040,7 +7041,7 @@ void Notepad_plus::setWorkingDir(const wchar_t *dir) { params.setWorkingDir(NULL); } - else if (dir && PathIsDirectory(dir)) + else if (dir && doesDirectoryExist(dir)) { params.setWorkingDir(dir); } @@ -7795,6 +7796,7 @@ static const QuoteParams quotes[] = {L"Don Ho #3", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"The smartphone is the best invention of the 21st century for avoiding eye contact with people you know while crossing the street."}, {L"Don Ho #4", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"Poor countries' museums vs. rich countries' museums:\nThe first show what they have left.\nThe second show what they have stolen."}, {L"Don Ho #5", QuoteParams::slow, false, SC_CP_UTF8, L_TEXT, L"With great refactoring comes great regressions."}, + {L"Don Ho #6", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"Naming a variable always reminds me the effort I put into my existence,\nfor giving some sense to my meaningless life."}, {L"Anonymous #1", QuoteParams::slow, false, SC_CP_UTF8, L_TEXT, L"An opinion without 3.14 is just an onion."}, {L"Anonymous #2", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"Before sex, you help each other get naked, after sex you only dress yourself.\nMoral of the story: in life no one helps you once you're fucked."}, {L"Anonymous #3", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"I'm not totally useless. I can be used as a bad example."}, @@ -7817,7 +7819,7 @@ static const QuoteParams quotes[] = {L"Anonymous #20", QuoteParams::slow, false, SC_CP_UTF8, L_TEXT, L"Never make eye contact while eating a banana."}, {L"Anonymous #21", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"I love my sixpack so much, I protect it with a layer of fat."}, {L"Anonymous #22", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"\"It's impossible.\" said pride.\n\"It's risky.\" said experience.\n\"It's pointless.\" said reason.\n\"Give it a try.\" whispered the heart.\n...\n\"What the hell was that?!?!?!?!?!\" shouted the anus two minutes later."}, - {L"Anonymous #23", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"A programmer is told to \"go to hell\".\nHe finds the worst part of that statement is the \"go to\"."}, + {L"Anonymous #23", QuoteParams::rapid, false, SC_CP_UTF8, L_TEXT, L"A C++ programmer is told to \"go to hell\".\nHe finds the most offensive part of that statement is the \"go to\"."}, {L"Anonymous #24", QuoteParams::slow, false, SC_CP_UTF8, L_TEXT, L"An Architect's dream is an Engineer's nightmare."}, {L"Anonymous #25", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"In a way, I feel sorry for the kids of this generation.\nThey'll have parents who know how to check browser history."}, {L"Anonymous #26", QuoteParams::rapid, true, SC_CP_UTF8, L_TEXT, L"Q: What's the difference between git and github?\nA: It's the difference between porn and pornhub.\n"}, diff --git a/PowerEditor/src/NppCommands.cpp b/PowerEditor/src/NppCommands.cpp index 5edf7cd1c..51c12b407 100644 --- a/PowerEditor/src/NppCommands.cpp +++ b/PowerEditor/src/NppCommands.cpp @@ -595,7 +595,7 @@ void Notepad_plus::command(int id) fullFilePath += L"\""; if (id == IDM_EDIT_OPENINFOLDER || - (id == IDM_EDIT_OPENASFILE && !::PathIsDirectory(curentWord))) + (id == IDM_EDIT_OPENASFILE && !doesDirectoryExist(curentWord))) ::ShellExecute(hwnd, L"open", cmd2Exec, fullFilePath.c_str(), L".", SW_SHOW); } else // Relative file path - need concatenate with current full file path diff --git a/PowerEditor/src/NppIO.cpp b/PowerEditor/src/NppIO.cpp index 689b47615..68d075ca7 100644 --- a/PowerEditor/src/NppIO.cpp +++ b/PowerEditor/src/NppIO.cpp @@ -262,7 +262,8 @@ BufferID Notepad_plus::doOpen(const wstring& fileName, bool isRecursive, bool is } bool isSnapshotMode = backupFileName != NULL && doesFileExist(backupFileName); - if (isSnapshotMode && !doesFileExist(longFileName)) // UNTITLED + bool longFileNameExists = doesFileExist(longFileName); + if (isSnapshotMode && !longFileNameExists) // UNTITLED { wcscpy_s(longFileName, targetFileName.c_str()); } @@ -301,13 +302,13 @@ BufferID Notepad_plus::doOpen(const wstring& fileName, bool isRecursive, bool is return foundBufID; } - if (isFileSession(longFileName) && doesFileExist(longFileName)) + if (isFileSession(longFileName) && longFileNameExists) { fileLoadSession(longFileName); return BUFFER_INVALID; } - if (isFileWorkspace(longFileName) && doesFileExist(longFileName)) + if (isFileWorkspace(longFileName) && longFileNameExists) { nppParam.setWorkSpaceFilePath(0, longFileName); // This line switches to Project Panel 1 while starting up Npp @@ -316,12 +317,14 @@ BufferID Notepad_plus::doOpen(const wstring& fileName, bool isRecursive, bool is return BUFFER_INVALID; } +#ifndef _WIN64 bool isWow64Off = false; - if (!doesFileExist(longFileName)) + if (!longFileNameExists) { nppParam.safeWow64EnableWow64FsRedirection(FALSE); isWow64Off = true; } +#endif bool globbing; if (isRawFileName) @@ -388,11 +391,13 @@ BufferID Notepad_plus::doOpen(const wstring& fileName, bool isRecursive, bool is if (!isCreateFileSuccessful) { +#ifndef _WIN64 if (isWow64Off) { nppParam.safeWow64EnableWow64FsRedirection(TRUE); isWow64Off = false; } +#endif return BUFFER_INVALID; } } @@ -477,7 +482,7 @@ BufferID Notepad_plus::doOpen(const wstring& fileName, bool isRecursive, bool is } else { - if (globbing || ::PathIsDirectory(targetFileName.c_str())) + if (globbing || doesDirectoryExist(targetFileName.c_str())) { vector fileNames; vector patterns; @@ -542,12 +547,13 @@ BufferID Notepad_plus::doOpen(const wstring& fileName, bool isRecursive, bool is _pluginsManager.notify(&scnN); } } - +#ifndef _WIN64 if (isWow64Off) { nppParam.safeWow64EnableWow64FsRedirection(TRUE); //isWow64Off = false; } +#endif return buffer; } @@ -659,98 +665,109 @@ bool Notepad_plus::doSave(BufferID id, const wchar_t * filename, bool isCopy) } else if (res == SavingStatus::SaveOpenFailed) { - if (_isAdministrator) + Buffer* buf = MainFileManager.getBufferByID(id); + if (buf->isFromNetwork()) { - // Already in admin mode? File is probably locked. _nativeLangSpeaker.messageBox("FileLockedWarning", _pPublicInterface->getHSelf(), - L"Please check whether if this file is opened in another program", + L"Please check whether the network where the file is located is connected.", L"Save failed", MB_OK | MB_ICONWARNING); } else { - // try to open Notepad++ in admin mode - const NppGUI& nppGui = NppParameters::getInstance().getNppGUI(); - bool isSnapshotMode = nppGui.isSnapshotMode(); - bool isAlwaysInMultiInstMode = nppGui._multiInstSetting == multiInst; - if (isSnapshotMode && !isAlwaysInMultiInstMode) // if both rememberSession && backup mode are enabled and "Always In Multi-Instance Mode" option not activated: - { // Open the 2nd Notepad++ instance in Admin mode, then close the 1st instance. + if (_isAdministrator) + { + // Already in admin mode? File is probably locked. + _nativeLangSpeaker.messageBox("FileLockedWarning", + _pPublicInterface->getHSelf(), + L"Please check whether if this file is opened in another program.", + L"Save failed", + MB_OK | MB_ICONWARNING); + } + else + { + // try to open Notepad++ in admin mode + const NppGUI& nppGui = NppParameters::getInstance().getNppGUI(); + bool isSnapshotMode = nppGui.isSnapshotMode(); + bool isAlwaysInMultiInstMode = nppGui._multiInstSetting == multiInst; + if (isSnapshotMode && !isAlwaysInMultiInstMode) // if both rememberSession && backup mode are enabled and "Always In Multi-Instance Mode" option not activated: + { // Open the 2nd Notepad++ instance in Admin mode, then close the 1st instance. + + int openInAdminModeRes = _nativeLangSpeaker.messageBox("OpenInAdminMode", + _pPublicInterface->getHSelf(), + L"This file cannot be saved and it may be protected.\rDo you want to launch Notepad++ in Administrator mode?", + L"Save failed", + MB_YESNO); - int openInAdminModeRes = _nativeLangSpeaker.messageBox("OpenInAdminMode", - _pPublicInterface->getHSelf(), - L"This file cannot be saved and it may be protected.\rDo you want to launch Notepad++ in Administrator mode?", - L"Save failed", - MB_YESNO); + if (openInAdminModeRes == IDYES) + { + wchar_t nppFullPath[MAX_PATH]{}; + ::GetModuleFileName(NULL, nppFullPath, MAX_PATH); - if (openInAdminModeRes == IDYES) - { - wchar_t nppFullPath[MAX_PATH]{}; - ::GetModuleFileName(NULL, nppFullPath, MAX_PATH); + wstring args = L"-multiInst"; + size_t shellExecRes = (size_t)::ShellExecute(_pPublicInterface->getHSelf(), L"runas", nppFullPath, args.c_str(), L".", SW_SHOW); - wstring args = L"-multiInst"; - size_t shellExecRes = (size_t)::ShellExecute(_pPublicInterface->getHSelf(), L"runas", nppFullPath, args.c_str(), L".", SW_SHOW); + // If the function succeeds, it returns a value greater than 32. If the function fails, + // it returns an error value that indicates the cause of the failure. + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153%28v=vs.85%29.aspx - // If the function succeeds, it returns a value greater than 32. If the function fails, - // it returns an error value that indicates the cause of the failure. - // https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153%28v=vs.85%29.aspx + if (shellExecRes <= 32) + { + _nativeLangSpeaker.messageBox("OpenInAdminModeFailed", + _pPublicInterface->getHSelf(), + L"Notepad++ cannot be opened in Administrator mode.", + L"Open in Administrator mode failed", + MB_OK); + } + else + { + ::SendMessage(_pPublicInterface->getHSelf(), WM_CLOSE, 0, 0); + } - if (shellExecRes <= 32) - { - _nativeLangSpeaker.messageBox("OpenInAdminModeFailed", - _pPublicInterface->getHSelf(), - L"Notepad++ cannot be opened in Administrator mode.", - L"Open in Administrator mode failed", - MB_OK); } - else - { - ::SendMessage(_pPublicInterface->getHSelf(), WM_CLOSE, 0, 0); - } - } - } - else // rememberSession && backup mode are not both enabled, or "Always In Multi-Instance Mode" option is ON: - { // Open only the file to save in Notepad++ of Administrator mode by keeping the current instance. + else // rememberSession && backup mode are not both enabled, or "Always In Multi-Instance Mode" option is ON: + { // Open only the file to save in Notepad++ of Administrator mode by keeping the current instance. - int openInAdminModeRes = _nativeLangSpeaker.messageBox("OpenInAdminModeWithoutCloseCurrent", - _pPublicInterface->getHSelf(), - L"The file cannot be saved and it may be protected.\rDo you want to launch Notepad++ in Administrator mode?", - L"Save failed", - MB_YESNO); + int openInAdminModeRes = _nativeLangSpeaker.messageBox("OpenInAdminModeWithoutCloseCurrent", + _pPublicInterface->getHSelf(), + L"The file cannot be saved and it may be protected.\rDo you want to launch Notepad++ in Administrator mode?", + L"Save failed", + MB_YESNO); - if (openInAdminModeRes == IDYES) - { - wchar_t nppFullPath[MAX_PATH]{}; - ::GetModuleFileName(NULL, nppFullPath, MAX_PATH); + if (openInAdminModeRes == IDYES) + { + wchar_t nppFullPath[MAX_PATH]{}; + ::GetModuleFileName(NULL, nppFullPath, MAX_PATH); - BufferID bufferID = _pEditView->getCurrentBufferID(); - Buffer * buf = MainFileManager.getBufferByID(bufferID); + Buffer* buf = MainFileManager.getBufferByID(id); - //process the fileNamePath into LRF - wstring fileNamePath = buf->getFullPathName(); + //process the fileNamePath into LRF + wstring fileNamePath = buf->getFullPathName(); - wstring args = L"-multiInst -nosession "; - args += L"\""; - args += fileNamePath; - args += L"\""; - size_t shellExecRes = (size_t)::ShellExecute(_pPublicInterface->getHSelf(), L"runas", nppFullPath, args.c_str(), L".", SW_SHOW); + wstring args = L"-multiInst -nosession "; + args += L"\""; + args += fileNamePath; + args += L"\""; + size_t shellExecRes = (size_t)::ShellExecute(_pPublicInterface->getHSelf(), L"runas", nppFullPath, args.c_str(), L".", SW_SHOW); - // If the function succeeds, it returns a value greater than 32. If the function fails, - // it returns an error value that indicates the cause of the failure. - // https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153%28v=vs.85%29.aspx + // If the function succeeds, it returns a value greater than 32. If the function fails, + // it returns an error value that indicates the cause of the failure. + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153%28v=vs.85%29.aspx - if (shellExecRes <= 32) - { - _nativeLangSpeaker.messageBox("OpenInAdminModeFailed", - _pPublicInterface->getHSelf(), - L"Notepad++ cannot be opened in Administrator mode.", - L"Open in Administrator mode failed", - MB_OK); + if (shellExecRes <= 32) + { + _nativeLangSpeaker.messageBox("OpenInAdminModeFailed", + _pPublicInterface->getHSelf(), + L"Notepad++ cannot be opened in Administrator mode.", + L"Open in Administrator mode failed", + MB_OK); + } } } - } + } } } @@ -791,26 +808,33 @@ void Notepad_plus::doClose(BufferID id, int whichOne, bool doDeleteBackup) wstring fileFullPath; if (!buf->isUntitled()) { - // if the file doesn't exist, it could be redirected + const wchar_t *fn = buf->getFullPathName(); + bool fileExists = doesFileExist(fn); + +#ifndef _WIN64 + // For Notepad++ 32 bits, if the file doesn't exist, it could be redirected // So we turn Wow64 off - bool isWow64Off = false; NppParameters& nppParam = NppParameters::getInstance(); - const wchar_t *fn = buf->getFullPathName(); - if (!doesFileExist(fn)) + bool isWow64Off = false; + if (!fileExists) { nppParam.safeWow64EnableWow64FsRedirection(FALSE); isWow64Off = true; + fileExists = doesFileExist(fn); } +#endif - if (doesFileExist(buf->getFullPathName())) - fileFullPath = buf->getFullPathName(); + if (fileExists) + fileFullPath = fn; +#ifndef _WIN64 // We enable Wow64 system, if it was disabled if (isWow64Off) { nppParam.safeWow64EnableWow64FsRedirection(TRUE); //isWow64Off = false; } +#endif } size_t nbDocs = whichOne==MAIN_VIEW?(_mainDocTab.nbItem()):(_subDocTab.nbItem()); @@ -2307,14 +2331,14 @@ bool Notepad_plus::loadSession(Session & session, bool isSnapshotMode, const wch session._mainViewFiles.erase(posIt); continue; //skip session files, not supporting recursive sessions or embedded workspace files } - +#ifndef _WIN64 bool isWow64Off = false; if (!doesFileExist(pFn)) { nppParam.safeWow64EnableWow64FsRedirection(FALSE); isWow64Off = true; } - +#endif if (doesFileExist(pFn)) { if (isSnapshotMode && !session._mainViewFiles[i]._backupFilePath.empty()) @@ -2332,12 +2356,13 @@ bool Notepad_plus::loadSession(Session & session, bool isSnapshotMode, const wch if (foundBufID == BUFFER_INVALID) lastOpened = nppGUI._keepSessionAbsentFileEntries ? MainFileManager.newPlaceholderDocument(pFn, MAIN_VIEW, userCreatedSessionName) : BUFFER_INVALID; } +#ifndef _WIN64 if (isWow64Off) { nppParam.safeWow64EnableWow64FsRedirection(TRUE); isWow64Off = false; } - +#endif if (lastOpened != BUFFER_INVALID) { showView(MAIN_VIEW); @@ -2437,14 +2462,14 @@ bool Notepad_plus::loadSession(Session & session, bool isSnapshotMode, const wch session._subViewFiles.erase(posIt); continue; //skip session files, not supporting recursive sessions or embedded workspace files } - +#ifndef _WIN64 bool isWow64Off = false; if (!doesFileExist(pFn)) { nppParam.safeWow64EnableWow64FsRedirection(FALSE); isWow64Off = true; } - +#endif if (doesFileExist(pFn)) { //check if already open in main. If so, clone @@ -2472,12 +2497,13 @@ bool Notepad_plus::loadSession(Session & session, bool isSnapshotMode, const wch if (foundBufID == BUFFER_INVALID) lastOpened = nppGUI._keepSessionAbsentFileEntries ? MainFileManager.newPlaceholderDocument(pFn, SUB_VIEW, userCreatedSessionName) : BUFFER_INVALID; } - +#ifndef _WIN64 if (isWow64Off) { nppParam.safeWow64EnableWow64FsRedirection(TRUE); isWow64Off = false; } +#endif if (lastOpened != BUFFER_INVALID) { diff --git a/PowerEditor/src/Parameters.cpp b/PowerEditor/src/Parameters.cpp index 0431418f9..bd9be36a4 100644 --- a/PowerEditor/src/Parameters.cpp +++ b/PowerEditor/src/Parameters.cpp @@ -1244,7 +1244,7 @@ bool NppParameters::load() // if (!_cmdSettingsDir.empty()) { - if (!::PathIsDirectory(_cmdSettingsDir.c_str())) + if (!doesDirectoryExist(_cmdSettingsDir.c_str())) { // The following text is not translatable. // _pNativeLangSpeaker is initialized AFTER _userPath being dterminated because nativeLang.xml is from from _userPath. @@ -8653,6 +8653,7 @@ void NppParameters::addScintillaModifiedIndex(int index) } } +#ifndef _WIN64 void NppParameters::safeWow64EnableWow64FsRedirection(BOOL Wow64FsEnableRedirection) { HMODULE kernel = GetModuleHandle(L"kernel32"); @@ -8678,6 +8679,7 @@ void NppParameters::safeWow64EnableWow64FsRedirection(BOOL Wow64FsEnableRedirect } } } +#endif void NppParameters::setUdlXmlDirtyFromIndex(size_t i) { diff --git a/PowerEditor/src/Parameters.h b/PowerEditor/src/Parameters.h index c659a0f13..16f44da93 100644 --- a/PowerEditor/src/Parameters.h +++ b/PowerEditor/src/Parameters.h @@ -1759,7 +1759,9 @@ public: std::wstring getWinVerBitStr() const; FindHistory & getFindHistory() {return _findHistory;}; bool _isFindReplacing = false; // an on the fly variable for find/replace functions +#ifndef _WIN64 void safeWow64EnableWow64FsRedirection(BOOL Wow64FsEnableRedirection); +#endif LocalizationSwitcher & getLocalizationSwitcher() { return _localizationSwitcher; diff --git a/PowerEditor/src/ScintillaComponent/Buffer.cpp b/PowerEditor/src/ScintillaComponent/Buffer.cpp index 447414301..7b4253f5e 100644 --- a/PowerEditor/src/ScintillaComponent/Buffer.cpp +++ b/PowerEditor/src/ScintillaComponent/Buffer.cpp @@ -142,7 +142,7 @@ void Buffer::updateTimeStamp() { FILETIME timeStampLive {}; WIN32_FILE_ATTRIBUTE_DATA attributes{}; - if (GetFileAttributesEx(_fullPathName.c_str(), GetFileExInfoStandard, &attributes) != 0) + if (getFileAttributesExWaitSec(_fullPathName.c_str(), &attributes) != FALSE) { timeStampLive = attributes.ftLastWriteTime; } @@ -194,6 +194,7 @@ void Buffer::setFileName(const wchar_t *fn) _fullPathName = fn; _fileName = PathFindFileName(_fullPathName.c_str()); + _isFromNetwork = PathIsNetworkPath(fn); // for _lang LangType determinatedLang = L_TEXT; @@ -259,15 +260,19 @@ bool Buffer::checkFileState() // returns true if the status has been changed (it return false; WIN32_FILE_ATTRIBUTE_DATA attributes{}; - bool isWow64Off = false; NppParameters& nppParam = NppParameters::getInstance(); - bool fileExists = doesFileExist(_fullPathName.c_str()); + +#ifndef _WIN64 + bool isWow64Off = false; if (!fileExists) { nppParam.safeWow64EnableWow64FsRedirection(FALSE); isWow64Off = true; + + fileExists = doesFileExist(_fullPathName.c_str()); } +#endif bool isOK = false; if (_currentStatus == DOC_INACCESSIBLE && !fileExists) //document is absent on its first load - we set readonly and not dirty, and make it be as document which has been deleted @@ -289,9 +294,9 @@ bool Buffer::checkFileState() // returns true if the status has been changed (it doNotify(BufferChangeStatus | BufferChangeReadonly | BufferChangeTimestamp); isOK = true; } - else if (_currentStatus == DOC_DELETED && fileExists) - { //document has returned from its grave - if (GetFileAttributesEx(_fullPathName.c_str(), GetFileExInfoStandard, &attributes) != 0) + else if (_currentStatus == DOC_DELETED && fileExists) //document has returned from its grave + { + if (GetFileAttributesEx(_fullPathName.c_str(), GetFileExInfoStandard, &attributes) != 0) // fileExists so it's safe to call GetFileAttributesEx directly { _isFileReadOnly = attributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY; @@ -306,7 +311,7 @@ bool Buffer::checkFileState() // returns true if the status has been changed (it isOK = true; } } - else if (GetFileAttributesEx(_fullPathName.c_str(), GetFileExInfoStandard, &attributes) != 0) + else if (getFileAttributesExWaitSec(_fullPathName.c_str(), &attributes) != FALSE) { int mask = 0; //status always 'changes', even if from modified to modified bool isFileReadOnly = attributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY; @@ -364,10 +369,12 @@ bool Buffer::checkFileState() // returns true if the status has been changed (it return false; } +#ifndef _WIN64 if (isWow64Off) { nppParam.safeWow64EnableWow64FsRedirection(TRUE); } +#endif return isOK; } @@ -420,8 +427,6 @@ wstring Buffer::getTimeString(FILETIME rawtime) const wstring Buffer::getFileTime(fileTimeType ftt) const { - wstring filePath; - WIN32_FILE_ATTRIBUTE_DATA attributes{}; if (GetFileAttributesEx(_currentStatus == DOC_UNNAMED ? _backupFileName.c_str() : _fullPathName.c_str(), GetFileExInfoStandard, &attributes) != 0) { @@ -1177,8 +1182,8 @@ SavingStatus FileManager::saveBuffer(BufferID id, const wchar_t* filename, bool const wchar_t* currentBufFilePath = buffer->getFullPathName(); ULARGE_INTEGER freeBytesForUser; - BOOL getFreeSpaceRes = ::GetDiskFreeSpaceExW(dirDest, &freeBytesForUser, nullptr, nullptr); - if (getFreeSpaceRes != FALSE) + BOOL getFreeSpaceSuccessful = getDiskFreeSpaceWaitSec(dirDest, &freeBytesForUser); + if (getFreeSpaceSuccessful) { int64_t fileSize = buffer->getFileLength(); if (fileSize >= 0 && lstrcmp(fullpath, currentBufFilePath) == 0) // if file to save does exist, and it's an operation "Save" but not "Save As" diff --git a/PowerEditor/src/ScintillaComponent/Buffer.h b/PowerEditor/src/ScintillaComponent/Buffer.h index 331c702b4..cbb122f9b 100644 --- a/PowerEditor/src/ScintillaComponent/Buffer.h +++ b/PowerEditor/src/ScintillaComponent/Buffer.h @@ -186,6 +186,8 @@ public: bool isUntitled() const { return ((_currentStatus & DOC_UNNAMED) == DOC_UNNAMED); } + bool isFromNetwork() const { return _isFromNetwork; } + bool isInaccessible() const { return _isInaccessible; } void setInaccessibility(bool val) { _isInaccessible = val; } @@ -279,7 +281,7 @@ public: std::wstring tabCreatedTimeString() const { return _tabCreatedTimeString; } void setTabCreatedTimeStringFromBakFile() { - if (_currentStatus == DOC_UNNAMED) + if (!_isFromNetwork && _currentStatus == DOC_UNNAMED) _tabCreatedTimeString = getFileTime(Buffer::ft_created); // while DOC_UNNAMED, getFileTime will retrieve time from backup file } void setTabCreatedTimeStringWithCurrentTime() { @@ -389,6 +391,7 @@ private: UniMode _unicodeMode = uniUTF8; int _encoding = -1; bool _isUserReadOnly = false; + bool _isFromNetwork = false; bool _needLexer = false; // new buffers do not need lexing, Scintilla takes care of that //these properties have to be duplicated because of multiple references diff --git a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp index 1daf275ae..5e28823aa 100644 --- a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp +++ b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp @@ -2648,7 +2648,7 @@ intptr_t CALLBACK FindReplaceDlg::run_dlgProc(UINT message, WPARAM wParam, LPARA currPath = buf->getFullPathName(); PathRemoveFileSpec(currPath); } - if (currPath.empty() || !PathIsDirectory(currPath.c_str())) + if (currPath.empty() || !doesDirectoryExist(currPath.c_str())) currPath = NppParameters::getInstance().getWorkingDir(); ::SetDlgItemText(_hSelf, IDD_FINDINFILES_DIR_COMBO, currPath.c_str()); } diff --git a/PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp index 1a15ba8b9..2deaf8c95 100644 --- a/PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp +++ b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp @@ -1235,7 +1235,7 @@ bool FileBrowser::addToTree(FilesToChange & group, HTREEITEM node) // Not found, good - Action for (auto & file : group._files) { - if (::PathIsDirectory((group._commonPath + file).c_str())) + if (doesDirectoryExist((group._commonPath + file).c_str())) { SortingData4lParam* customData = new SortingData4lParam(L"", file, true); sortingDataArray.push_back(customData); @@ -1468,7 +1468,7 @@ bool FolderInfo::addToStructure(wstring & fullpath, std::vector linarPa { fullpath += L"\\"; fullpath += linarPathArray[0]; - if (PathIsDirectory(fullpath.c_str())) + if (doesDirectoryExist(fullpath.c_str())) { // search in folders, if found - no good for (const auto& folder : _subFolders) diff --git a/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp b/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp index 867dc94ce..b8ad0bb25 100644 --- a/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp +++ b/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp @@ -16,7 +16,6 @@ #include -#include // PathIsDirectory #ifdef __MINGW32__ #include #endif @@ -142,7 +141,7 @@ namespace // anonymous HRESULT hr = SHCreateItemFromParsingName(path, nullptr, IID_PPV_ARGS(&shellItem)); - if (SUCCEEDED(hr) && shellItem && !::PathIsDirectory(path)) + if (SUCCEEDED(hr) && shellItem && !::doesDirectoryExist(path)) { com_ptr parentItem; hr = shellItem->GetParent(&parentItem); @@ -506,7 +505,7 @@ private: expandEnv(fileName); bool nameChanged = transformPath(fileName); // Update the controls. - if (!::PathIsDirectory(getAbsPath(fileName).c_str())) + if (doesDirectoryExist(getAbsPath(fileName).c_str())) { // Name is a file path. // Add file extension if missing.