@@ -29,7 +29,7 @@ class DownloadCountsBackend {
2929 final DatastoreDB _db;
3030
3131 late CachedValue <Map <String , int >> _thirtyDaysTotals;
32- var _lastData = (data: < String , int > {}, etag: '' );
32+ var _lastDownloadsData = (data: < String , int > {}, etag: '' );
3333
3434 late CachedValue <Map <String , int >> _trendScores;
3535 var _lastTrendData = (data: < String , int > {}, etag: '' );
@@ -48,70 +48,86 @@ class DownloadCountsBackend {
4848 }
4949
5050 Future <Map <String , int >> _updateThirtyDaysTotals () async {
51+ return _fetchAndUpdateCachedData (
52+ fileName: downloadCounts30DaysTotalsFileName,
53+ currentCachedData: _lastDownloadsData,
54+ updateCache: (data) => _lastDownloadsData = data,
55+ errorContext: 'trend scores' );
56+ }
57+
58+ Future <Map <String , int >> _updateTrendScores () async {
59+ return _fetchAndUpdateCachedData (
60+ fileName: trendScoreFileName,
61+ currentCachedData: _lastTrendData,
62+ updateCache: (data) => _lastTrendData = data,
63+ errorContext: 'trend scores' );
64+ }
65+
66+ Future <Map <String , int >> _fetchAndUpdateCachedData ({
67+ required String fileName,
68+ required ({Map <String , int > data, String etag}) currentCachedData,
69+ required void Function (({Map <String , int > data, String etag}) newData)
70+ updateCache,
71+ required String errorContext,
72+ }) async {
5173 try {
5274 final info = await storageService
5375 .bucket (activeConfiguration.reportsBucketName! )
54- .infoWithRetry (downloadCounts30DaysTotalsFileName );
76+ .infoWithRetry (fileName );
5577
56- if (_lastData .etag == info.etag) {
57- return _lastData .data;
78+ if (currentCachedData .etag == info.etag) {
79+ return currentCachedData .data;
5880 }
59- final data = (await storageService
60- .bucket (activeConfiguration.reportsBucketName! )
61- .readWithRetry (
62- downloadCounts30DaysTotalsFileName,
63- (input) async => await input
64- .transform (utf8.decoder)
65- .transform (json.decoder)
66- .single as Map <String , dynamic >,
67- ))
68- .cast <String , int >();
69- _lastData = (data: data, etag: info.etag);
81+
82+ final rawData = await storageService
83+ .bucket (activeConfiguration.reportsBucketName! )
84+ .readWithRetry (
85+ fileName,
86+ (input) async => await input
87+ .transform (utf8.decoder)
88+ .transform (json.decoder)
89+ .single,
90+ );
91+
92+ final data = _parseJsonToMapStringInt (rawData, fileName);
93+
94+ final newData = (data: data, etag: info.etag);
95+ updateCache (newData);
7096 return data;
7197 } on FormatException catch (e, st) {
72- logger.severe ('Error loading 30-days total download counts: ' , e, st);
98+ logger.severe ('Error parsing $ errorContext : $ e ' , e, st);
7399 rethrow ;
74100 } on DetailedApiRequestError catch (e, st) {
75101 if (e.status != 404 ) {
76102 logger.severe (
77- 'Failed to load $downloadCounts30DaysTotalsFileName , error : ' ,
78- e,
79- st);
103+ 'Failed to load $fileName ($errorContext ), error : $e ' , e, st);
80104 }
81105 rethrow ;
106+ } on TypeError catch (e, st) {
107+ logger.severe ('Type error during processing $errorContext : $e ' , e, st);
108+ rethrow ;
82109 }
83110 }
84111
85- Future < Map <String , int >> _updateTrendScores () async {
86- try {
87- final info = await storageService
88- . bucket (activeConfiguration.reportsBucketName ! )
89- . infoWithRetry (trendScoreFileName);
112+ Map <String , int > _parseJsonToMapStringInt ( dynamic rawJson, String fileName) {
113+ if (rawJson is ! Map ) {
114+ throw FormatException (
115+ 'Expected JSON for $ fileName to be a Map, but got ${ rawJson . runtimeType }' );
116+ }
90117
91- if (_lastTrendData.etag == info.etag) {
92- return _lastTrendData.data;
118+ final Map <String , int > result = {};
119+ for (final entry in rawJson.entries) {
120+ if (entry.key is ! String ) {
121+ throw FormatException (
122+ 'Expected map keys for $fileName to be String, but found ${entry .key .runtimeType }' );
93123 }
94- final data = (await storageService
95- .bucket (activeConfiguration.reportsBucketName! )
96- .readWithRetry (
97- trendScoreFileName,
98- (input) async => await input
99- .transform (utf8.decoder)
100- .transform (json.decoder)
101- .single as Map <String , dynamic >,
102- ))
103- .cast <String , int >();
104- _lastTrendData = (data: data, etag: info.etag);
105- return data;
106- } on FormatException catch (e, st) {
107- logger.severe ('Error package trend scores:' , e, st);
108- rethrow ;
109- } on DetailedApiRequestError catch (e, st) {
110- if (e.status != 404 ) {
111- logger.severe ('Failed to load $trendScoreFileName , error : ' , e, st);
124+ if (entry.value is ! int ) {
125+ throw FormatException (
126+ 'Expected map value for key "${entry .key }" in $fileName to be int, but got ${entry .value .runtimeType }' );
112127 }
113- rethrow ;
128+ result[entry.key as String ] = entry.value as int ;
114129 }
130+ return result;
115131 }
116132
117133 Future <void > start () async {
0 commit comments