77# Can be removed in a future version of python (3.8+)
88from __future__ import annotations
99
10- from datetime import datetime , timedelta
1110import json
1211import os
1312import pathlib
4140from .models .channel import Channel
4241from .network import (
4342 _getAddonStoreURL ,
44- _getCurrentApiVersionForURL ,
43+ _getCacheHashURL ,
4544 _LATEST_API_VER ,
4645)
4746
@@ -68,7 +67,6 @@ def initialize():
6867class _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 )
0 commit comments