Skip to content

Commit 7391591

Browse files
authored
Merge 5eddff4 into 1573904
2 parents 1573904 + 5eddff4 commit 7391591

3 files changed

Lines changed: 148 additions & 46 deletions

File tree

source/gui/_SetURLDialog.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
from .settingsDialogs import SettingsDialog
2121

2222

23+
class _ValidationError(ValueError):
24+
"""Exception raised when validation of an OK response returns False.
25+
This is only used internally.
26+
"""
27+
28+
2329
class _SetURLDialog(SettingsDialog):
2430
class _URLTestStatus(Enum):
2531
UNTESTED = auto()
@@ -36,6 +42,7 @@ def __init__(
3642
configPath: Iterable[str],
3743
helpId: str | None = None,
3844
urlTransformer: Callable[[str], str] = lambda url: url,
45+
responseValidator: Callable[[requests.Response], bool] = lambda response: True,
3946
*args,
4047
**kwargs,
4148
):
@@ -46,6 +53,8 @@ def __init__(
4653
:param configPath: Where in the config the URL is to be stored.
4754
:param helpId: Anchor of the user guide section for this dialog, defaults to None
4855
:param urlTransformer: Function to transform the given URL into something usable, eg by adding required query parameters. Defaults to the identity function.
56+
:param responseValidator: Function to check that the response returned when querying the transformed URL is valid.
57+
The response will always have a status of 200 (OK). Defaults to always returning True.
4958
:raises ValueError: If no config path is given.
5059
"""
5160
if not configPath or len(configPath) < 1:
@@ -54,6 +63,7 @@ def __init__(
5463
self.helpId = helpId
5564
self._configPath = configPath
5665
self._urlTransformer = urlTransformer
66+
self._responseValidator = responseValidator
5767
super().__init__(parent, *args, **kwargs)
5868

5969
def makeSettings(self, settingsSizer: wx.Sizer):
@@ -65,13 +75,13 @@ def makeSettings(self, settingsSizer: wx.Sizer):
6575
wx.TextCtrl,
6676
size=(250, -1),
6777
)
68-
self.bindHelpEvent("UpdateMirrorURL", urlControl)
78+
self.bindHelpEvent("SetURLTextbox", urlControl)
6979
self._testButton = testButton = wx.Button(
7080
self,
7181
# Translators: A button in a dialog which allows the user to test a URL that they have entered.
7282
label=_("&Test..."),
7383
)
74-
self.bindHelpEvent("UpdateMirrorTest", testButton)
84+
self.bindHelpEvent("SetURLTest", testButton)
7585
urlControlsSizerHelper = guiHelper.BoxSizerHelper(self, sizer=urlControl.GetContainingSizer())
7686
urlControlsSizerHelper.addItem(testButton)
7787
testButton.Bind(wx.EVT_BUTTON, self._onTest)
@@ -147,9 +157,11 @@ def _bg(self):
147157
try:
148158
with requests.get(self._urlTransformer(self._url)) as r:
149159
r.raise_for_status()
160+
if not self._responseValidator(r):
161+
raise _ValidationError
150162
self._success()
151-
except RequestException as e:
152-
log.debug(f"Failed to check URL: {e}")
163+
except (RequestException, _ValidationError) as e:
164+
log.debug(f"URL check failed: {e}")
153165
self._failure(e)
154166

155167
def _success(self):
@@ -168,15 +180,32 @@ def _success(self):
168180

169181
def _failure(self, error: Exception):
170182
"""Notify the user that testing their URL failed."""
183+
if isinstance(error, _ValidationError):
184+
message = _(
185+
# Translators: Message shown to users when testing a given URL has failed because the response was invalid.
186+
"The URL you have entered failed the connection test. The response received from the server was invalid. Check the URL is correct before trying again.",
187+
)
188+
elif isinstance(error, requests.HTTPError):
189+
message = _(
190+
# Translators: Message shown to users when testing a given URL has failed because the server returned an error.
191+
"The URL you have entered failed the connection test. The server returned an error. Check that the URL is correct before trying again.",
192+
)
193+
elif isinstance(error, requests.ConnectionError):
194+
message = _(
195+
# Translators: Message shown to users when testing a given URL has failed because of a network error.
196+
"The URL you have entered failed the connection test. There was a network error. Check that you are connected to the internet and try again.",
197+
)
198+
else:
199+
message = _(
200+
# Translators: Message shown to users when testing a given URL has failed for unknown reasons.
201+
"The URL you have entered failed the connection test. Make sure you are connected to the internet and the URL is correct.",
202+
)
171203
wx.CallAfter(self._progressDialog.done)
172204
self._progressDialog = None
173205
self._testStatus = _SetURLDialog._URLTestStatus.FAILED
174206
wx.CallAfter(
175207
gui.messageBox,
176-
_(
177-
# Translators: Message displayed to users when testing a URL has failed.
178-
"Unable to connect to the given URL. Check that you are connected to the internet and the URL is correct.",
179-
),
208+
message,
180209
# Translators: The title of a dialog presented when an error occurs.
181210
"Error",
182211
wx.OK | wx.ICON_ERROR,

source/gui/settingsDialogs.py

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from locale import strxfrm
1616
import re
1717
import typing
18+
import requests
1819
import wx
1920
from NVDAState import WritePaths
2021

@@ -62,7 +63,6 @@
6263
Set,
6364
cast,
6465
)
65-
from url_normalize import url_normalize
6666
import core
6767
import keyboardHandler
6868
import characterProcessing
@@ -996,7 +996,7 @@ def onChangeMirrorURL(self, evt: wx.CommandEvent | wx.KeyEvent):
996996
# Translators: Title of the dialog used to change NVDA's update server mirror URL.
997997
title=_("Set NVDA Update Mirror"),
998998
configPath=("update", "serverURL"),
999-
helpId="SetUpdateMirror",
999+
helpId="SetURLDialog",
10001000
urlTransformer=lambda url: f"{url}?versionType=stable",
10011001
)
10021002
ret = changeMirror.ShowModal()
@@ -3274,27 +3274,83 @@ def makeSettings(self, settingsSizer: wx.BoxSizer) -> None:
32743274
index = [x.value for x in AddonsAutomaticUpdate].index(config.conf["addonStore"]["automaticUpdates"])
32753275
self.automaticUpdatesComboBox.SetSelection(index)
32763276

3277-
# Translators: This is the label for a text box in the add-on store settings dialog.
3278-
self.addonMetadataMirrorLabelText = _("Server &mirror URL")
3279-
self.addonMetadataMirrorTextbox = sHelper.addLabeledControl(
3280-
self.addonMetadataMirrorLabelText,
3281-
wx.TextCtrl,
3277+
# Translators: The label for the server mirror on the Add-on store Settings panel.
3278+
mirrorBoxSizer = wx.StaticBoxSizer(wx.HORIZONTAL, self, label=_("Server mirror"))
3279+
mirrorBox = mirrorBoxSizer.GetStaticBox()
3280+
mirrorBoxSizerHelper = guiHelper.BoxSizerHelper(self, sizer=mirrorBoxSizer)
3281+
sHelper.addItem(mirrorBoxSizerHelper)
3282+
3283+
# Use an ExpandoTextCtrl because even when read-only it accepts focus from keyboard, which
3284+
# standard read-only TextCtrl does not. ExpandoTextCtrl is a TE_MULTILINE control, however
3285+
# by default it renders as a single line. Standard TextCtrl with TE_MULTILINE has two lines,
3286+
# and a vertical scroll bar. This is not neccessary for the single line of text we wish to
3287+
# display here.
3288+
# Note: To avoid code duplication, the value of this text box will be set in `onPanelActivated`.
3289+
self.mirrorURLTextBox = ExpandoTextCtrl(
3290+
mirrorBox,
3291+
size=(self.scaleSize(250), -1),
3292+
style=wx.TE_READONLY,
32823293
)
3283-
self.addonMetadataMirrorTextbox.SetValue(config.conf["addonStore"]["baseServerURL"])
3284-
self.bindHelpEvent("AddonStoreMetadataMirror", self.addonMetadataMirrorTextbox)
3294+
# Translators: This is the label for the button used to change the Add-on Store mirror URL,
3295+
# it appears in the context of the Server mirror group on the Add-on Store page of NVDA's settings.
3296+
changeMirrorBtn = wx.Button(mirrorBox, label=_("Change..."))
3297+
mirrorBoxSizerHelper.addItem(
3298+
guiHelper.associateElements(
3299+
self.mirrorURLTextBox,
3300+
changeMirrorBtn,
3301+
),
3302+
)
3303+
self.bindHelpEvent("AddonStoreMetadataMirror", mirrorBox)
3304+
self.mirrorURLTextBox.Bind(wx.EVT_CHAR_HOOK, self._enterTriggersOnChangeMirrorURL)
3305+
changeMirrorBtn.Bind(wx.EVT_BUTTON, self.onChangeMirrorURL)
32853306

3286-
def isValid(self) -> bool:
3287-
self.addonMetadataMirrorTextbox.SetValue(
3288-
url_normalize(self.addonMetadataMirrorTextbox.GetValue().strip()).rstrip("/"),
3307+
def onChangeMirrorURL(self, evt: wx.CommandEvent | wx.KeyEvent):
3308+
"""Show the dialog to change the Add-on Store mirror URL, and refresh the dialog in response to the URL being changed."""
3309+
# Import late to avoid circular dependency.
3310+
from gui._SetURLDialog import _SetURLDialog
3311+
3312+
changeMirror = _SetURLDialog(
3313+
self,
3314+
# Translators: Title of the dialog used to change the Add-on Store server mirror URL.
3315+
title=_("Set Add-on Store Server Mirror"),
3316+
configPath=("addonStore", "baseServerURL"),
3317+
helpId="SetURLDialog",
3318+
urlTransformer=lambda url: f"{url}/cacheHash.json",
3319+
responseValidator=_isResponseAddonStoreCacheHash,
32893320
)
3290-
return True
3321+
ret = changeMirror.ShowModal()
3322+
if ret == wx.ID_OK:
3323+
self.Freeze()
3324+
# trigger a refresh of the settings
3325+
self.onPanelActivated()
3326+
self._sendLayoutUpdatedEvent()
3327+
self.Thaw()
3328+
3329+
def _enterTriggersOnChangeMirrorURL(self, evt: wx.KeyEvent):
3330+
"""Open the change update mirror URL dialog in response to the enter key in the mirror URL read-only text box."""
3331+
if evt.KeyCode == wx.WXK_RETURN:
3332+
self.onChangeMirrorURL(evt)
3333+
else:
3334+
evt.Skip()
3335+
3336+
def _updateCurrentMirrorURL(self):
3337+
self.mirrorURLTextBox.SetValue(
3338+
(
3339+
url
3340+
if (url := config.conf["addonStore"]["baseServerURL"])
3341+
# Translators: A value that appears in NVDA's Settings to indicate that no mirror is in use.
3342+
else _("No mirror")
3343+
),
3344+
)
3345+
3346+
def onPanelActivated(self):
3347+
self._updateCurrentMirrorURL()
3348+
super().onPanelActivated()
32913349

32923350
def onSave(self):
32933351
index = self.automaticUpdatesComboBox.GetSelection()
32943352
config.conf["addonStore"]["automaticUpdates"] = [x.value for x in AddonsAutomaticUpdate][index]
32953353

3296-
config.conf["addonStore"]["baseServerURL"] = self.addonMetadataMirrorTextbox.Value.strip().rstrip("/")
3297-
32983354

32993355
class TouchInteractionPanel(SettingsPanel):
33003356
# Translators: This is the label for the touch interaction settings panel.
@@ -5520,3 +5576,14 @@ def onFilterEditTextChange(self, evt):
55205576
self.filter(self.filterEdit.Value)
55215577
self._refreshVisibleItems()
55225578
evt.Skip()
5579+
5580+
5581+
def _isResponseAddonStoreCacheHash(response: requests.Response) -> bool:
5582+
try:
5583+
# Attempt to parse the response as JSON
5584+
data = response.json()
5585+
except ValueError:
5586+
# Add-on Store cache hash is JSON, so this can't be it.
5587+
return False
5588+
# Add-on Store cache hash is a git commit hash as a string.
5589+
return isinstance(data, str) and bool(re.fullmatch("[0-9a-fA-F]{7,40}", data))

user_docs/en/userGuide.md

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,30 +1839,11 @@ This may be of use in locations where access to the NV Access NVDA update server
18391839
The read-only text box shows the current mirror URL.
18401840
If no mirror is in use (I.E. the NV Access NVDA update server is being used), "No mirror" is displayed.
18411841

1842-
If you wish to change the update mirror, press the "Change..." button to open the [Set Update Mirror dialog](#SetUpdateMirror).
1843-
1844-
#### Set Update Mirror {#SetUpdateMirror}
1845-
1846-
This dialog, which is accessed from the [Update mirror section of the General page in NVDA's Settings dialog](#UpdateMirror), allows you to set a mirror to use when checking for NVDA updates.
1847-
This may be of use in locations where access to the NV Access NVDA update server is slow or unavailable.
1842+
If you wish to change the update mirror, press the "Change..." button to open the [Set Update Mirror dialog](#SetURLDialog).
18481843

18491844
Please note that when using an update mirror, the operator of the mirror has access to all [information sent with update checks](#GeneralSettingsCheckForUpdates).
18501845
Contact the operator of the update mirror for details of their data handling policies to ensure you are comfortable with the way your information will be handled before setting an update mirror.
18511846

1852-
##### URL {#UpdateMirrorURL}
1853-
1854-
Enter the URL (web address) of the update server mirror you wish to use here.
1855-
Only HTTP and HTTPS URLs are supported.
1856-
For your privacy, NV Access recommends using HTTPS URLs whenever possible.
1857-
1858-
Leave this blank to use the NV Access NVDA update check server.
1859-
1860-
##### Test... {#UpdateMirrorTest}
1861-
1862-
Press this button to test the NVDA update server URL you have entered.
1863-
You must be connected to the internet for the test to succeed.
1864-
It is recommended that you always test the URL before saving it.
1865-
18661847
#### Speech Settings {#SpeechSettings}
18671848

18681849
<!-- KC:setting -->
@@ -3107,12 +3088,15 @@ For example, for installed beta add-ons, you will only be notified of updates wi
31073088
|Notify |Notify when updates are available to add-ons within the same channel |
31083089
|Disabled |Do not automatically check for updates to add-ons |
31093090

3110-
##### Server mirror URL {#AddonStoreMetadataMirror}
3091+
##### Server mirror {#AddonStoreMetadataMirror}
31113092

3112-
This option allows you to specify an alternative URL to download Add-on Store data from.
3093+
These controls allow you to specify an alternative URL to download Add-on Store data from.
31133094
This may be of use in locations where access to the NV Access Add-on Store server is slow or unavailable.
31143095

3115-
Leave this blank to use the default NV Access Add-on Store server.
3096+
The read-only text box shows the current mirror URL.
3097+
If no mirror is in use (I.E. the NV Access NVDA Add-on Store server is being used), "No mirror" is displayed.
3098+
3099+
If you wish to change the Add-on Store server mirror, press the "Change..." button to open the [Set Add-on Store Mirror dialog](#SetURLDialog).
31163100

31173101
#### Windows OCR Settings {#Win10OcrSettings}
31183102

@@ -3131,6 +3115,28 @@ This can be very useful when you want to monitor constantly changing content, su
31313115
The refresh takes place every one and a half seconds.
31323116
This option is disabled by default.
31333117

3118+
#### Set Mirror Dialog {#SetURLDialog}
3119+
3120+
This dialog allows you to specify the URL of a mirror to use when [updating NVDA](#GeneralSettingsCheckForUpdates) or [using the Add-on Store](#AddonsManager).
3121+
This may be of use in locations where access to the NV Access servers for these functions is slow or unavailable.
3122+
3123+
* When setting the [NVDA update mirror](#UpdateMirror), the title of this dialog will be "Set NVDA Update Mirror".
3124+
* When setting the [Add-on Store server mirror](#AddonStoreMetadataMirror), the title of this dialog will be "Set Add-on Store Server Mirror".
3125+
3126+
##### URL {#SetURLTextbox}
3127+
3128+
Enter the URL (web address) of the mirror you wish to use here.
3129+
Only HTTP and HTTPS URLs are supported.
3130+
For your privacy, NV Access recommends using HTTPS URLs whenever possible.
3131+
3132+
Leave this blank to use the default NV Access server.
3133+
3134+
##### Test... {#SetURLTest}
3135+
3136+
Press this button to test the mirror URL you have entered.
3137+
You must be connected to the internet for the test to succeed.
3138+
It is recommended that you always test the URL before saving it.
3139+
31343140
#### Advanced Settings {#AdvancedSettings}
31353141

31363142
Warning! The settings in this category are for advanced users and may cause NVDA to not function correctly if configured in the wrong way.

0 commit comments

Comments
 (0)