Skip to content

Commit d9a8f1a

Browse files
authored
Merge 72955c8 into 5c4e6e1
2 parents 5c4e6e1 + 72955c8 commit d9a8f1a

3 files changed

Lines changed: 53 additions & 32 deletions

File tree

source/_addonStore/dataManager.py

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
# Can be removed in a future version of python (3.8+)
88
from __future__ import annotations
99

10-
from datetime import datetime, timedelta
1110
import json
1211
import os
1312
import pathlib
@@ -41,7 +40,7 @@
4140
from .models.channel import Channel
4241
from .network import (
4342
_getAddonStoreURL,
44-
_getCurrentApiVersionForURL,
43+
_getCacheHashURL,
4544
_LATEST_API_VER,
4645
)
4746

@@ -68,7 +67,6 @@ def initialize():
6867
class _DataManager:
6968
_cacheLatestFilename: str = "_cachedLatestAddons.json"
7069
_cacheCompatibleFilename: str = "_cachedCompatibleAddons.json"
71-
_cachePeriod = timedelta(hours=6)
7270
_downloadsPendingInstall: Set[Tuple["AddonListItemVM", os.PathLike]] = set()
7371

7472
def __init__(self):
@@ -107,27 +105,43 @@ def _getLatestAddonsDataForVersion(self, apiVersion: str) -> Optional[bytes]:
107105
return None
108106
return response.content
109107

110-
def _cacheCompatibleAddons(self, addonData: str, fetchTime: datetime):
108+
def _getCacheHash(self) -> Optional[str]:
109+
url = _getCacheHashURL()
110+
try:
111+
response = requests.get(url)
112+
except requests.exceptions.RequestException as e:
113+
log.debugWarning(f"Unable to get cache hash: {e}")
114+
return None
115+
if response.status_code != requests.codes.OK:
116+
log.error(
117+
f"Unable to get data from API ({url}),"
118+
f" response ({response.status_code}): {response.content}"
119+
)
120+
return None
121+
cacheHash = response.json()
122+
return cacheHash
123+
124+
def _cacheCompatibleAddons(self, addonData: str, cacheHash: str):
111125
if not NVDAState.shouldWriteToDisk():
112126
return
113-
if not addonData:
127+
if not addonData or not cacheHash:
114128
return
115129
cacheData = {
116-
"cacheDate": fetchTime.isoformat(),
130+
"cacheHash": cacheHash,
117131
"data": addonData,
118132
"cachedLanguage": self._lang,
119133
"nvdaAPIVersion": addonAPIVersion.CURRENT,
120134
}
121135
with open(self._cacheCompatibleFile, 'w') as cacheFile:
122136
json.dump(cacheData, cacheFile, ensure_ascii=False)
123137

124-
def _cacheLatestAddons(self, addonData: str, fetchTime: datetime):
138+
def _cacheLatestAddons(self, addonData: str, cacheHash: str):
125139
if not NVDAState.shouldWriteToDisk():
126140
return
127-
if not addonData:
141+
if not addonData or not cacheHash:
128142
return
129143
cacheData = {
130-
"cacheDate": fetchTime.isoformat(),
144+
"cacheHash": cacheHash,
131145
"data": addonData,
132146
"cachedLanguage": self._lang,
133147
"nvdaAPIVersion": _LATEST_API_VER,
@@ -139,15 +153,24 @@ def _getCachedAddonData(self, cacheFilePath: str) -> Optional[CachedAddonsModel]
139153
if not os.path.exists(cacheFilePath):
140154
return None
141155
with open(cacheFilePath, 'r') as cacheFile:
142-
cacheData = json.load(cacheFile)
143-
if not cacheData:
156+
try:
157+
cacheData = json.load(cacheFile)
158+
except Exception:
159+
log.exception(f"Invalid add-on store cache")
160+
return None
161+
try:
162+
data = cacheData["data"]
163+
cacheHash = cacheData["cacheHash"]
164+
cachedLanguage = cacheData["cachedLanguage"]
165+
nvdaAPIVersion = cacheData["nvdaAPIVersion"]
166+
except KeyError:
167+
log.exception(f"Invalid add-on store cache:\n{cacheData}")
144168
return None
145-
fetchTime = datetime.fromisoformat(cacheData["cacheDate"])
146169
return CachedAddonsModel(
147-
cachedAddonData=_createStoreCollectionFromJson(cacheData["data"]),
148-
cachedAt=fetchTime,
149-
cachedLanguage=cacheData["cachedLanguage"],
150-
nvdaAPIVersion=tuple(cacheData["nvdaAPIVersion"]), # loads as list
170+
cachedAddonData=_createStoreCollectionFromJson(data),
171+
cacheHash=cacheHash,
172+
cachedLanguage=cachedLanguage,
173+
nvdaAPIVersion=tuple(nvdaAPIVersion), # loads as list,
151174
)
152175

153176
# Translators: A title of the dialog shown when fetching add-on data from the store fails
@@ -157,24 +180,24 @@ def getLatestCompatibleAddons(
157180
self,
158181
onDisplayableError: Optional[DisplayableError.OnDisplayableErrorT] = None,
159182
) -> "AddonGUICollectionT":
183+
cacheHash = self._getCacheHash()
160184
shouldRefreshData = (
161185
not self._compatibleAddonCache
162186
or self._compatibleAddonCache.nvdaAPIVersion != addonAPIVersion.CURRENT
163-
or _DataManager._cachePeriod < (datetime.now() - self._compatibleAddonCache.cachedAt)
187+
or cacheHash and (self._compatibleAddonCache.cacheHash != cacheHash)
164188
or self._compatibleAddonCache.cachedLanguage != self._lang
165189
)
166190
if shouldRefreshData:
167-
fetchTime = datetime.now()
168191
apiData = self._getLatestAddonsDataForVersion(_getCurrentApiVersionForURL())
169192
if apiData:
170193
decodedApiData = apiData.decode()
171194
self._cacheCompatibleAddons(
172195
addonData=decodedApiData,
173-
fetchTime=fetchTime,
196+
cacheHash=cacheHash,
174197
)
175198
self._compatibleAddonCache = CachedAddonsModel(
176199
cachedAddonData=_createStoreCollectionFromJson(decodedApiData),
177-
cachedAt=fetchTime,
200+
cacheHash=cacheHash,
178201
cachedLanguage=self._lang,
179202
nvdaAPIVersion=addonAPIVersion.CURRENT,
180203
)
@@ -195,23 +218,23 @@ def getLatestAddons(
195218
self,
196219
onDisplayableError: Optional[DisplayableError.OnDisplayableErrorT] = None,
197220
) -> "AddonGUICollectionT":
221+
cacheHash = self._getCacheHash()
198222
shouldRefreshData = (
199223
not self._latestAddonCache
200-
or _DataManager._cachePeriod < (datetime.now() - self._latestAddonCache.cachedAt)
224+
or cacheHash and (self._latestAddonCache.cacheHash != cacheHash)
201225
or self._latestAddonCache.cachedLanguage != self._lang
202226
)
203227
if shouldRefreshData:
204-
fetchTime = datetime.now()
205228
apiData = self._getLatestAddonsDataForVersion(_LATEST_API_VER)
206229
if apiData:
207230
decodedApiData = apiData.decode()
208231
self._cacheLatestAddons(
209232
addonData=decodedApiData,
210-
fetchTime=fetchTime,
233+
cacheHash=cacheHash,
211234
)
212235
self._latestAddonCache = CachedAddonsModel(
213236
cachedAddonData=_createStoreCollectionFromJson(decodedApiData),
214-
cachedAt=fetchTime,
237+
cacheHash=cacheHash,
215238
cachedLanguage=self._lang,
216239
nvdaAPIVersion=_LATEST_API_VER,
217240
)

source/_addonStore/models/addon.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from __future__ import annotations
99

1010
import dataclasses
11-
from datetime import datetime
1211
import json
1312
import os
1413
from typing import (
@@ -203,7 +202,7 @@ def isPendingInstall(self) -> bool:
203202
@dataclasses.dataclass
204203
class CachedAddonsModel:
205204
cachedAddonData: "AddonGUICollectionT"
206-
cachedAt: datetime
205+
cacheHash: str
207206
cachedLanguage: str
208207
# AddonApiVersionT or the string .network._LATEST_API_VER
209208
nvdaAPIVersion: Union[addonAPIVersion.AddonApiVersionT, str]

source/_addonStore/network.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,20 @@
3535
from gui.message import DisplayableError
3636

3737

38+
_BASE_URL = "https://nvaccess.org/addonStore"
3839
_LATEST_API_VER = "latest"
3940
"""
4041
A string value used in the add-on store to fetch the latest version of all add-ons,
4142
i.e include older incompatible versions.
4243
"""
4344

4445

45-
def _getCurrentApiVersionForURL() -> str:
46-
year, major, minor = addonAPIVersion.CURRENT
47-
return f"{year}.{major}.{minor}"
46+
def _getAddonStoreURL(channel: Channel, lang: str, nvdaApiVersion: str) -> str:
47+
return f"{_BASE_URL}/{lang}/{channel.value}/{nvdaApiVersion}.json"
4848

4949

50-
def _getAddonStoreURL(channel: Channel, lang: str, nvdaApiVersion: str) -> str:
51-
_baseURL = "https://nvaccess.org/addonStore/"
52-
return _baseURL + f"{lang}/{channel.value}/{nvdaApiVersion}.json"
50+
def _getCacheHashURL() -> str:
51+
return f"{_BASE_URL}/cacheHash.json"
5352

5453

5554
class AddonFileDownloader:

0 commit comments

Comments
 (0)