Skip to content

Commit 28d1edc

Browse files
authored
Merge 9481d66 into 1466654
2 parents 1466654 + 9481d66 commit 28d1edc

3 files changed

Lines changed: 57 additions & 28 deletions

File tree

source/_addonStore/dataManager.py

Lines changed: 50 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
@@ -40,8 +39,9 @@
4039
)
4140
from .models.channel import Channel
4241
from .network import (
43-
_getAddonStoreURL,
4442
_getCurrentApiVersionForURL,
43+
_getAddonStoreURL,
44+
_getCacheHashURL,
4545
_LATEST_API_VER,
4646
)
4747

@@ -68,7 +68,6 @@ def initialize():
6868
class _DataManager:
6969
_cacheLatestFilename: str = "_cachedLatestAddons.json"
7070
_cacheCompatibleFilename: str = "_cachedCompatibleAddons.json"
71-
_cachePeriod = timedelta(hours=6)
7271
_downloadsPendingInstall: Set[Tuple["AddonListItemVM", os.PathLike]] = set()
7372

7473
def __init__(self):
@@ -107,27 +106,43 @@ def _getLatestAddonsDataForVersion(self, apiVersion: str) -> Optional[bytes]:
107106
return None
108107
return response.content
109108

110-
def _cacheCompatibleAddons(self, addonData: str, fetchTime: datetime):
109+
def _getCacheHash(self) -> Optional[str]:
110+
url = _getCacheHashURL()
111+
try:
112+
response = requests.get(url)
113+
except requests.exceptions.RequestException as e:
114+
log.debugWarning(f"Unable to get cache hash: {e}")
115+
return None
116+
if response.status_code != requests.codes.OK:
117+
log.error(
118+
f"Unable to get data from API ({url}),"
119+
f" response ({response.status_code}): {response.content}"
120+
)
121+
return None
122+
cacheHash = response.json()
123+
return cacheHash
124+
125+
def _cacheCompatibleAddons(self, addonData: str, cacheHash: str):
111126
if not NVDAState.shouldWriteToDisk():
112127
return
113-
if not addonData:
128+
if not addonData or not cacheHash:
114129
return
115130
cacheData = {
116-
"cacheDate": fetchTime.isoformat(),
131+
"cacheHash": cacheHash,
117132
"data": addonData,
118133
"cachedLanguage": self._lang,
119134
"nvdaAPIVersion": addonAPIVersion.CURRENT,
120135
}
121136
with open(self._cacheCompatibleFile, 'w') as cacheFile:
122137
json.dump(cacheData, cacheFile, ensure_ascii=False)
123138

124-
def _cacheLatestAddons(self, addonData: str, fetchTime: datetime):
139+
def _cacheLatestAddons(self, addonData: str, cacheHash: str):
125140
if not NVDAState.shouldWriteToDisk():
126141
return
127-
if not addonData:
142+
if not addonData or not cacheHash:
128143
return
129144
cacheData = {
130-
"cacheDate": fetchTime.isoformat(),
145+
"cacheHash": cacheHash,
131146
"data": addonData,
132147
"cachedLanguage": self._lang,
133148
"nvdaAPIVersion": _LATEST_API_VER,
@@ -139,15 +154,24 @@ def _getCachedAddonData(self, cacheFilePath: str) -> Optional[CachedAddonsModel]
139154
if not os.path.exists(cacheFilePath):
140155
return None
141156
with open(cacheFilePath, 'r') as cacheFile:
142-
cacheData = json.load(cacheFile)
143-
if not cacheData:
157+
try:
158+
cacheData = json.load(cacheFile)
159+
except Exception:
160+
log.exception(f"Invalid add-on store cache")
161+
return None
162+
try:
163+
data = cacheData["data"]
164+
cacheHash = cacheData["cacheHash"]
165+
cachedLanguage = cacheData["cachedLanguage"]
166+
nvdaAPIVersion = cacheData["nvdaAPIVersion"]
167+
except KeyError:
168+
log.exception(f"Invalid add-on store cache:\n{cacheData}")
144169
return None
145-
fetchTime = datetime.fromisoformat(cacheData["cacheDate"])
146170
return CachedAddonsModel(
147-
cachedAddonData=_createStoreCollectionFromJson(cacheData["data"]),
148-
cachedAt=fetchTime,
149-
cachedLanguage=cacheData["cachedLanguage"],
150-
nvdaAPIVersion=tuple(cacheData["nvdaAPIVersion"]), # loads as list
171+
cachedAddonData=_createStoreCollectionFromJson(data),
172+
cacheHash=cacheHash,
173+
cachedLanguage=cachedLanguage,
174+
nvdaAPIVersion=tuple(nvdaAPIVersion), # loads as list,
151175
)
152176

153177
# Translators: A title of the dialog shown when fetching add-on data from the store fails
@@ -157,24 +181,25 @@ def getLatestCompatibleAddons(
157181
self,
158182
onDisplayableError: Optional[DisplayableError.OnDisplayableErrorT] = None,
159183
) -> "AddonGUICollectionT":
184+
cacheHash = self._getCacheHash()
160185
shouldRefreshData = (
161186
not self._compatibleAddonCache
162187
or self._compatibleAddonCache.nvdaAPIVersion != addonAPIVersion.CURRENT
163-
or _DataManager._cachePeriod < (datetime.now() - self._compatibleAddonCache.cachedAt)
188+
or cacheHash is None
189+
or self._compatibleAddonCache.cacheHash != cacheHash
164190
or self._compatibleAddonCache.cachedLanguage != self._lang
165191
)
166192
if shouldRefreshData:
167-
fetchTime = datetime.now()
168193
apiData = self._getLatestAddonsDataForVersion(_getCurrentApiVersionForURL())
169194
if apiData:
170195
decodedApiData = apiData.decode()
171196
self._cacheCompatibleAddons(
172197
addonData=decodedApiData,
173-
fetchTime=fetchTime,
198+
cacheHash=cacheHash,
174199
)
175200
self._compatibleAddonCache = CachedAddonsModel(
176201
cachedAddonData=_createStoreCollectionFromJson(decodedApiData),
177-
cachedAt=fetchTime,
202+
cacheHash=cacheHash,
178203
cachedLanguage=self._lang,
179204
nvdaAPIVersion=addonAPIVersion.CURRENT,
180205
)
@@ -195,23 +220,24 @@ def getLatestAddons(
195220
self,
196221
onDisplayableError: Optional[DisplayableError.OnDisplayableErrorT] = None,
197222
) -> "AddonGUICollectionT":
223+
cacheHash = self._getCacheHash()
198224
shouldRefreshData = (
199225
not self._latestAddonCache
200-
or _DataManager._cachePeriod < (datetime.now() - self._latestAddonCache.cachedAt)
226+
or cacheHash is None
227+
or self._latestAddonCache.cacheHash != cacheHash
201228
or self._latestAddonCache.cachedLanguage != self._lang
202229
)
203230
if shouldRefreshData:
204-
fetchTime = datetime.now()
205231
apiData = self._getLatestAddonsDataForVersion(_LATEST_API_VER)
206232
if apiData:
207233
decodedApiData = apiData.decode()
208234
self._cacheLatestAddons(
209235
addonData=decodedApiData,
210-
fetchTime=fetchTime,
236+
cacheHash=cacheHash,
211237
)
212238
self._latestAddonCache = CachedAddonsModel(
213239
cachedAddonData=_createStoreCollectionFromJson(decodedApiData),
214-
cachedAt=fetchTime,
240+
cacheHash=cacheHash,
215241
cachedLanguage=self._lang,
216242
nvdaAPIVersion=_LATEST_API_VER,
217243
)

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: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
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,
@@ -48,8 +49,11 @@ def _getCurrentApiVersionForURL() -> str:
4849

4950

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

5458

5559
class AddonFileDownloader:

0 commit comments

Comments
 (0)