Skip to content

Commit

Permalink
Merge 72955c8 into 5c4e6e1
Browse files Browse the repository at this point in the history
  • Loading branch information
nvdaes authored Jul 4, 2023
2 parents 5c4e6e1 + 72955c8 commit d9a8f1a
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 32 deletions.
71 changes: 47 additions & 24 deletions source/_addonStore/dataManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
# Can be removed in a future version of python (3.8+)
from __future__ import annotations

from datetime import datetime, timedelta
import json
import os
import pathlib
Expand Down Expand Up @@ -41,7 +40,7 @@
from .models.channel import Channel
from .network import (
_getAddonStoreURL,
_getCurrentApiVersionForURL,
_getCacheHashURL,
_LATEST_API_VER,
)

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

def __init__(self):
Expand Down Expand Up @@ -107,27 +105,43 @@ def _getLatestAddonsDataForVersion(self, apiVersion: str) -> Optional[bytes]:
return None
return response.content

def _cacheCompatibleAddons(self, addonData: str, fetchTime: datetime):
def _getCacheHash(self) -> Optional[str]:
url = _getCacheHashURL()
try:
response = requests.get(url)
except requests.exceptions.RequestException as e:
log.debugWarning(f"Unable to get cache hash: {e}")
return None
if response.status_code != requests.codes.OK:
log.error(
f"Unable to get data from API ({url}),"
f" response ({response.status_code}): {response.content}"
)
return None
cacheHash = response.json()
return cacheHash

def _cacheCompatibleAddons(self, addonData: str, cacheHash: str):
if not NVDAState.shouldWriteToDisk():
return
if not addonData:
if not addonData or not cacheHash:
return
cacheData = {
"cacheDate": fetchTime.isoformat(),
"cacheHash": cacheHash,
"data": addonData,
"cachedLanguage": self._lang,
"nvdaAPIVersion": addonAPIVersion.CURRENT,
}
with open(self._cacheCompatibleFile, 'w') as cacheFile:
json.dump(cacheData, cacheFile, ensure_ascii=False)

def _cacheLatestAddons(self, addonData: str, fetchTime: datetime):
def _cacheLatestAddons(self, addonData: str, cacheHash: str):
if not NVDAState.shouldWriteToDisk():
return
if not addonData:
if not addonData or not cacheHash:
return
cacheData = {
"cacheDate": fetchTime.isoformat(),
"cacheHash": cacheHash,
"data": addonData,
"cachedLanguage": self._lang,
"nvdaAPIVersion": _LATEST_API_VER,
Expand All @@ -139,15 +153,24 @@ def _getCachedAddonData(self, cacheFilePath: str) -> Optional[CachedAddonsModel]
if not os.path.exists(cacheFilePath):
return None
with open(cacheFilePath, 'r') as cacheFile:
cacheData = json.load(cacheFile)
if not cacheData:
try:
cacheData = json.load(cacheFile)
except Exception:
log.exception(f"Invalid add-on store cache")
return None
try:
data = cacheData["data"]
cacheHash = cacheData["cacheHash"]
cachedLanguage = cacheData["cachedLanguage"]
nvdaAPIVersion = cacheData["nvdaAPIVersion"]
except KeyError:
log.exception(f"Invalid add-on store cache:\n{cacheData}")
return None
fetchTime = datetime.fromisoformat(cacheData["cacheDate"])
return CachedAddonsModel(
cachedAddonData=_createStoreCollectionFromJson(cacheData["data"]),
cachedAt=fetchTime,
cachedLanguage=cacheData["cachedLanguage"],
nvdaAPIVersion=tuple(cacheData["nvdaAPIVersion"]), # loads as list
cachedAddonData=_createStoreCollectionFromJson(data),
cacheHash=cacheHash,
cachedLanguage=cachedLanguage,
nvdaAPIVersion=tuple(nvdaAPIVersion), # loads as list,
)

# Translators: A title of the dialog shown when fetching add-on data from the store fails
Expand All @@ -157,24 +180,24 @@ def getLatestCompatibleAddons(
self,
onDisplayableError: Optional[DisplayableError.OnDisplayableErrorT] = None,
) -> "AddonGUICollectionT":
cacheHash = self._getCacheHash()
shouldRefreshData = (
not self._compatibleAddonCache
or self._compatibleAddonCache.nvdaAPIVersion != addonAPIVersion.CURRENT
or _DataManager._cachePeriod < (datetime.now() - self._compatibleAddonCache.cachedAt)
or cacheHash and (self._compatibleAddonCache.cacheHash != cacheHash)
or self._compatibleAddonCache.cachedLanguage != self._lang
)
if shouldRefreshData:
fetchTime = datetime.now()
apiData = self._getLatestAddonsDataForVersion(_getCurrentApiVersionForURL())
if apiData:
decodedApiData = apiData.decode()
self._cacheCompatibleAddons(
addonData=decodedApiData,
fetchTime=fetchTime,
cacheHash=cacheHash,
)
self._compatibleAddonCache = CachedAddonsModel(
cachedAddonData=_createStoreCollectionFromJson(decodedApiData),
cachedAt=fetchTime,
cacheHash=cacheHash,
cachedLanguage=self._lang,
nvdaAPIVersion=addonAPIVersion.CURRENT,
)
Expand All @@ -195,23 +218,23 @@ def getLatestAddons(
self,
onDisplayableError: Optional[DisplayableError.OnDisplayableErrorT] = None,
) -> "AddonGUICollectionT":
cacheHash = self._getCacheHash()
shouldRefreshData = (
not self._latestAddonCache
or _DataManager._cachePeriod < (datetime.now() - self._latestAddonCache.cachedAt)
or cacheHash and (self._latestAddonCache.cacheHash != cacheHash)
or self._latestAddonCache.cachedLanguage != self._lang
)
if shouldRefreshData:
fetchTime = datetime.now()
apiData = self._getLatestAddonsDataForVersion(_LATEST_API_VER)
if apiData:
decodedApiData = apiData.decode()
self._cacheLatestAddons(
addonData=decodedApiData,
fetchTime=fetchTime,
cacheHash=cacheHash,
)
self._latestAddonCache = CachedAddonsModel(
cachedAddonData=_createStoreCollectionFromJson(decodedApiData),
cachedAt=fetchTime,
cacheHash=cacheHash,
cachedLanguage=self._lang,
nvdaAPIVersion=_LATEST_API_VER,
)
Expand Down
3 changes: 1 addition & 2 deletions source/_addonStore/models/addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from __future__ import annotations

import dataclasses
from datetime import datetime
import json
import os
from typing import (
Expand Down Expand Up @@ -203,7 +202,7 @@ def isPendingInstall(self) -> bool:
@dataclasses.dataclass
class CachedAddonsModel:
cachedAddonData: "AddonGUICollectionT"
cachedAt: datetime
cacheHash: str
cachedLanguage: str
# AddonApiVersionT or the string .network._LATEST_API_VER
nvdaAPIVersion: Union[addonAPIVersion.AddonApiVersionT, str]
Expand Down
11 changes: 5 additions & 6 deletions source/_addonStore/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,20 @@
from gui.message import DisplayableError


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


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


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


class AddonFileDownloader:
Expand Down

0 comments on commit d9a8f1a

Please sign in to comment.