-
Notifications
You must be signed in to change notification settings - Fork 4
CollectionInterface: add JSON error handling to avoid plugin crash #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ | |
| CollectionInterface::CollectionInterface(HWND hwndNpp) { | ||
| _hwndNPP = hwndNpp; | ||
| _populateNppDirs(); | ||
| getListsFromJson(); | ||
| _areListsPopulated = getListsFromJson(); | ||
| }; | ||
|
|
||
| void CollectionInterface::_populateNppDirs(void) { | ||
|
|
@@ -228,8 +228,9 @@ std::string CollectionInterface::_xml_unentity(const std::string& text) | |
| } | ||
| #pragma warning ( pop ) | ||
|
|
||
| void CollectionInterface::getListsFromJson(void) | ||
| bool CollectionInterface::getListsFromJson(void) | ||
| { | ||
| bool didThemeFail = false; | ||
| auto string2wstring = [](std::string str) { | ||
| if (str.empty()) return std::wstring(); | ||
| int wsz = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), static_cast<int>(str.size()), NULL, 0); | ||
|
|
@@ -242,104 +243,169 @@ void CollectionInterface::getListsFromJson(void) | |
| // Process Theme JSON | ||
| //////////////////////////////// | ||
| std::vector<char> vcThemeJSON = downloadFileInMemory(L"https://raw.githubusercontent.com/notepad-plus-plus/nppThemes/master/themes/.toc.json"); | ||
| nlohmann::json jTheme = nlohmann::json::parse(vcThemeJSON); | ||
| std::string v = jTheme.at(0).get<std::string>(); | ||
| for (const auto& item : jTheme.items()) { | ||
| std::wstring ws = string2wstring(item.value().get<std::string>()); | ||
| vThemeFiles.push_back(ws.c_str()); | ||
| if (vcThemeJSON.empty()) { | ||
| // issue#13: do not continue if there's internet/connection problems | ||
| return false; // nothing downloaded, so want to know to close the download-dialog to avoid annoying user with useless empty listbox | ||
| } | ||
| else if (vcThemeJSON[0] != L'[') { | ||
| // related to issue#13: if downloadFileInMemory returns "404 Not Found" or similar, don't try to parse as JSON. | ||
| // easiest check: if the JSON isn't the expected [...] JSON array, don't continue with the _theme_; | ||
| // however, can still move to the UDL section, because that might still work, and since UDL is the primary purpose, it's probably worth it if UDL is working even if Themes aren't. | ||
| std::string msg = "Cannot interpret Themes Collection information:\n\n"; | ||
| msg += vcThemeJSON.data(); | ||
| if (msg.size() > 100) { | ||
| msg.resize(100); | ||
| msg += "\n..."; | ||
| } | ||
| ::MessageBoxA(_hwndNPP, msg.c_str(), "CollectionInterface: Download Problems", MB_ICONWARNING); | ||
| didThemeFail = true; | ||
| } | ||
| else { | ||
| try | ||
| { | ||
| nlohmann::json jTheme = nlohmann::json::parse(vcThemeJSON); | ||
| std::string v = jTheme.at(0).get<std::string>(); | ||
| for (const auto& item : jTheme.items()) { | ||
| std::wstring ws = string2wstring(item.value().get<std::string>()); | ||
| vThemeFiles.push_back(ws.c_str()); | ||
| } | ||
| } | ||
| catch (nlohmann::json::exception& e) { | ||
| std::string msg = std::string("JSON Error in Theme data: ") + e.what(); | ||
| ::MessageBoxA(_hwndNPP, msg.c_str(), "CollectionInterface: JSON Error", MB_ICONERROR); | ||
| didThemeFail = true; | ||
| } | ||
| catch (std::exception& e) { | ||
| std::string msg = std::string("Unrecognized Error in Theme data: ") + e.what(); | ||
| ::MessageBoxA(_hwndNPP, msg.c_str(), "CollectionInterface: Unrecognized Error", MB_ICONERROR); | ||
| didThemeFail = true; | ||
| } | ||
| } | ||
|
|
||
| //////////////////////////////// | ||
| // Process UDL JSON | ||
| //////////////////////////////// | ||
| std::vector<char> vcUdlJSON = downloadFileInMemory(L"https://raw.githubusercontent.com/notepad-plus-plus/userDefinedLanguages/refs/heads/master/udl-list.json"); | ||
| nlohmann::json jUdl = nlohmann::json::parse(vcUdlJSON); | ||
| // for a list, the key() is just the index, and the value() is the sub-object | ||
| for (const auto& item : jUdl["UDLs"].items()) { | ||
| auto j = item.value(); | ||
| std::wstring ws_id_name = string2wstring(j["id-name"].get<std::string>()); | ||
| std::wstring udl_base = L"https://raw.githubusercontent.com/notepad-plus-plus/userDefinedLanguages/master/"; | ||
|
|
||
| // Logic for UDL -> URL | ||
| if (j.contains("repository")) { | ||
| std::wstring sUDL = L""; | ||
| if (j["repository"].is_boolean()) { // URL repo should never be boolean; but if it is, generate default URL | ||
| sUDL = udl_base + L"UDLs/" + ws_id_name + L".xml"; | ||
| } | ||
| if (j["repository"].is_string()) { | ||
| std::wstring ws = string2wstring(j["repository"].get<std::string>()); | ||
| if (ws == L"") { | ||
| sUDL = udl_base + L"UDLs/" + ws_id_name + L".xml"; | ||
| } | ||
| else if (ws.find(L"http") == 0) { // if string _starts_ with http or https, it's the full URL | ||
| sUDL = ws; | ||
| } | ||
| } | ||
|
|
||
| // assign into the data structure... | ||
| if (sUDL != L"") { | ||
| mapUDL[ws_id_name] = sUDL; | ||
| } | ||
| if (vcUdlJSON.empty()) { | ||
| // issue#13: do not continue if there's internet/connection problems | ||
| return false; // nothing downloaded, so want to know to close the download-dialog to avoid annoying user with useless empty listbox | ||
| } | ||
| else if (vcUdlJSON[0] != L'{') { | ||
| // related to issue#13: if downloadFileInMemory returns "404 Not Found" or similar, don't try to parse as JSON. | ||
| // easiest check: if the JSON isn't the expected [...] JSON array, don't continue with the _theme_; | ||
| std::string msg = "Cannot interpret UDL Collection information:\n\n"; | ||
| msg += vcUdlJSON.data(); | ||
| if (msg.size() > 100) { | ||
| msg.resize(100); | ||
| msg += "\n..."; | ||
| } | ||
| if (!didThemeFail) | ||
| ::MessageBoxA(_hwndNPP, msg.c_str(), "CollectionInterface: Download Problems", MB_ICONWARNING); | ||
| return false; // without UDL info, it's not worth displaying the Download Dialog | ||
| } | ||
| else { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as with if (vcUdlJSON.empty())
{
return;
}to return early and reduce indentation level for next block for better readability. |
||
| try | ||
| { | ||
| nlohmann::json jUdl = nlohmann::json::parse(vcUdlJSON); | ||
| // for a list, the key() is just the index, and the value() is the sub-object | ||
| for (const auto& item : jUdl["UDLs"].items()) { | ||
| auto j = item.value(); | ||
| std::wstring ws_id_name = string2wstring(j["id-name"].get<std::string>()); | ||
| std::wstring udl_base = L"https://raw.githubusercontent.com/notepad-plus-plus/userDefinedLanguages/master/"; | ||
|
|
||
| // Logic for UDL -> URL | ||
| if (j.contains("repository")) { | ||
| std::wstring sUDL = L""; | ||
| if (j["repository"].is_boolean()) { // URL repo should never be boolean; but if it is, generate default URL | ||
| sUDL = udl_base + L"UDLs/" + ws_id_name + L".xml"; | ||
| } | ||
| if (j["repository"].is_string()) { | ||
| std::wstring ws = string2wstring(j["repository"].get<std::string>()); | ||
| if (ws == L"") { | ||
| sUDL = udl_base + L"UDLs/" + ws_id_name + L".xml"; | ||
| } | ||
| else if (ws.find(L"http") == 0) { // if string _starts_ with http or https, it's the full URL | ||
| sUDL = ws; | ||
| } | ||
| } | ||
|
|
||
| // Extract display-name | ||
| if (j.contains("display-name")) { | ||
| std::wstring wdisplay_name = string2wstring(_xml_unentity(j["display-name"].get<std::string>())); | ||
|
|
||
| // assign into the data structure... | ||
| if (wdisplay_name != L"") { | ||
| mapDISPLAY[ws_id_name] = wdisplay_name; | ||
| revDISPLAY[wdisplay_name] = ws_id_name; | ||
| } | ||
| } | ||
| // assign into the data structure... | ||
| if (sUDL != L"") { | ||
| mapUDL[ws_id_name] = sUDL; | ||
| } | ||
| } | ||
|
|
||
| // Logic for functionList -> URL | ||
| if (j.contains("functionList")) { | ||
| std::wstring wsFuncList = L""; | ||
| if (j["functionList"].is_boolean() && j["functionList"].get<bool>()) { | ||
| wsFuncList = udl_base + L"functionList/" + ws_id_name + L".xml"; | ||
| } | ||
| if (j["functionList"].is_string()) { | ||
| std::wstring ws = string2wstring(j["functionList"].get<std::string>()); | ||
| // Extract display-name | ||
| if (j.contains("display-name")) { | ||
| std::wstring wdisplay_name = string2wstring(_xml_unentity(j["display-name"].get<std::string>())); | ||
|
|
||
| if (ws.find(L"http") == 0) { // if string _starts_ with http or https, it's the full URL | ||
| wsFuncList = ws; | ||
| } | ||
| else { | ||
| wsFuncList = udl_base + L"functionList/" + ws + L".xml"; | ||
| // assign into the data structure... | ||
| if (wdisplay_name != L"") { | ||
| mapDISPLAY[ws_id_name] = wdisplay_name; | ||
| revDISPLAY[wdisplay_name] = ws_id_name; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // assign wsFuncList into the data structure... | ||
| if (wsFuncList != L"") { | ||
| mapFL[ws_id_name] = wsFuncList; | ||
| } | ||
| } | ||
| // Logic for functionList -> URL | ||
| if (j.contains("functionList")) { | ||
| std::wstring wsFuncList = L""; | ||
| if (j["functionList"].is_boolean() && j["functionList"].get<bool>()) { | ||
| wsFuncList = udl_base + L"functionList/" + ws_id_name + L".xml"; | ||
| } | ||
| if (j["functionList"].is_string()) { | ||
| std::wstring ws = string2wstring(j["functionList"].get<std::string>()); | ||
|
|
||
| if (ws.find(L"http") == 0) { // if string _starts_ with http or https, it's the full URL | ||
| wsFuncList = ws; | ||
| } | ||
| else { | ||
| wsFuncList = udl_base + L"functionList/" + ws + L".xml"; | ||
| } | ||
| } | ||
|
|
||
| // Logic for autoCompletion -> URL | ||
| if (j.contains("autoCompletion")) { | ||
| std::wstring wsAutoComp = L""; | ||
| if (j["autoCompletion"].is_boolean()) { | ||
| wsAutoComp = udl_base + L"autoCompletion/" + ws_id_name + L".xml"; | ||
| } | ||
| if (j["autoCompletion"].is_string()) { | ||
| std::wstring ws = string2wstring(j["autoCompletion"].get<std::string>()); | ||
| if (ws.find(L"http") == 0) { | ||
| wsAutoComp = ws; | ||
| } | ||
| else { | ||
| wsAutoComp = udl_base + L"autoCompletion/" + ws + L".xml"; | ||
| // assign wsFuncList into the data structure... | ||
| if (wsFuncList != L"") { | ||
| mapFL[ws_id_name] = wsFuncList; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // assign sAutoComp into the data structure... | ||
| if (wsAutoComp != L"") { | ||
| mapAC[ws_id_name] = wsAutoComp; | ||
| // Logic for autoCompletion -> URL | ||
| if (j.contains("autoCompletion")) { | ||
| std::wstring wsAutoComp = L""; | ||
| if (j["autoCompletion"].is_boolean()) { | ||
| wsAutoComp = udl_base + L"autoCompletion/" + ws_id_name + L".xml"; | ||
| } | ||
| if (j["autoCompletion"].is_string()) { | ||
| std::wstring ws = string2wstring(j["autoCompletion"].get<std::string>()); | ||
| if (ws.find(L"http") == 0) { | ||
| wsAutoComp = ws; | ||
| } | ||
| else { | ||
| wsAutoComp = udl_base + L"autoCompletion/" + ws + L".xml"; | ||
| } | ||
| } | ||
|
|
||
| // assign sAutoComp into the data structure... | ||
| if (wsAutoComp != L"") { | ||
| mapAC[ws_id_name] = wsAutoComp; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| catch (nlohmann::json::exception& e) { | ||
| std::string msg = std::string("JSON Error in UDL data: ") + e.what(); | ||
| ::MessageBoxA(_hwndNPP, msg.c_str(), "CollectionInterface: JSON Error", MB_ICONERROR); | ||
| return false; // without UDL info, it's not worth displaying the Download Dialog | ||
| } | ||
| catch (std::exception& e) { | ||
| std::string msg = std::string("Unrecognized Error in UDL data: ") + e.what(); | ||
| ::MessageBoxA(_hwndNPP, msg.c_str(), "CollectionInterface: Unrecognized Error", MB_ICONERROR); | ||
| return false; // without UDL info, it's not worth displaying the Download Dialog | ||
| } | ||
| } | ||
|
|
||
| return; | ||
| // if it makes it here, there is enough data to be worth displaying the dialog | ||
| return true; | ||
| } | ||
|
|
||
| std::wstring& CollectionInterface::_wsDeleteTrailingNulls(std::wstring& str) | ||
|
|
@@ -371,7 +437,7 @@ bool CollectionInterface::_is_dir_writable(const std::wstring& path) | |
| std::wstring CollectionInterface::getWritableTempDir(void) | ||
| { | ||
| // first try the system TEMP | ||
| std::wstring tempDir(MAX_PATH+1, L'\0'); | ||
| std::wstring tempDir(MAX_PATH + 1, L'\0'); | ||
| GetTempPath(MAX_PATH + 1, const_cast<LPWSTR>(tempDir.data())); | ||
| _wsDeleteTrailingNulls(tempDir); | ||
|
|
||
|
|
@@ -418,5 +484,5 @@ bool CollectionInterface::ask_overwrite_if_exists(const std::wstring& path) | |
| if (!PathFileExists(path.c_str())) return true; // if file doesn't exist, it's okay to "overwrite" nothing ;-) | ||
| std::wstring msg = L"The path\r\n" + path + L"\r\nalready exists. Should I overwrite it?"; | ||
| int ans = ::MessageBox(_hwndNPP, msg.c_str(), L"Overwrite File?", MB_YESNO); | ||
| return ans==IDYES; | ||
| return ans == IDYES; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use only
if (!vcThemeJSON.empty())and remove current if block,.I don't expect
.toc.jsonfile content to be wrong.In
NppPlugin-CollectionInterface/src/Classes/CollectionInterfaceClass.cpp
Line 57 in 374be61
there are already message boxes with error description, and here seems to be redundant, And it can be annoying when you have to dismiss 4 message boxes and in the end get empty listbox.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would not catch the problem of GitHub sending a 404 or server error, because the vector will actually be the string of the error code (like
404 Not Found)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see in that case you can add check for empty vector, to reduce message boxes.
I have a firewall configured to block applications the first time they try to connect to net, so I can to decide whether to allow them.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took me a while to figure out how there could be 4 message boxes. But I'm assuming now that your reported "connection is blocked" was actually one of the "Could not connect to internet when trying to download" message boxes that the plugin launches from inside the
downloadFileInMemory()function.Now that I think I've figured that out, I can figure out a good way to non-annoyingly handle that condition -- while still preventing other possible problems, like a 404 or 500 error from the GH server, or receiving junk rather than JSON for unknown reasons. (Because there are multiple types of errors, there will still be the extra indentation -- but since it's my code, I will do the blocks the best way for me to understand for future maintenance)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another issue when clicking on "Download" button with empty listbox notepad++ will crash.