@@ -8,6 +8,7 @@ import 'package:flutter/services.dart' show rootBundle;
88import "package:googleapis_auth/auth_io.dart" as auth;
99import 'package:googleapis/customsearch/v1.dart' as customsearch;
1010import 'package:english_words/english_words.dart' ;
11+ import 'package:async/async.dart' ;
1112
1213/// A wrapper class for [customsearch.Result] .
1314/// [SearchResult] will use the landing page link to measure if two results are
@@ -50,9 +51,6 @@ class SearchResult {
5051 : result.image.contextLink.hashCode;
5152}
5253
53- /// A wrapper class to generate search request. To make caching possible.
54- class SearchRequest {}
55-
5654class Promotion {
5755 final customsearch.Promotion promotion;
5856
@@ -76,8 +74,8 @@ class SearchResults {
7674
7775 SearchResults (customsearch.Search search) {
7876 var results = new List <SearchResult >();
79- search.items.forEach ((item) =>
80- results.add (SearchResult .escapeLineBreakInSnippet (item)));
77+ search.items.forEach (
78+ (item) => results.add (SearchResult .escapeLineBreakInSnippet (item)));
8179 // Deduplicate search result.
8280 this .searchResults = Set <SearchResult >.from (results).toList ();
8381 }
@@ -107,6 +105,9 @@ class FakeSearchDataSource implements SearchDataSource {
107105 'promotion' : _StaticSearchResponse (
108106 assetPath: 'res/sampledata/nytimes_with_promotion.json' ),
109107 };
108+ final ExpireCache <SearchQuery , SearchResults > _cache =
109+ ExpireCache <SearchQuery , SearchResults >();
110+ final String cx = 'fake_cx' ;
110111
111112 FakeSearchDataSource () {
112113 searchResponses.keys.forEach ((key) {
@@ -119,6 +120,8 @@ class FakeSearchDataSource implements SearchDataSource {
119120 await rootBundle.loadString (assetPath);
120121 }
121122
123+ int count = 0 ;
124+
122125 @override
123126 Future <SearchResults > search (String query, {String searchType}) async {
124127 if (! searchResponses.containsKey (query)) {
@@ -127,18 +130,186 @@ class FakeSearchDataSource implements SearchDataSource {
127130 if (searchResponses[query].searchType != searchType) {
128131 return SearchResults .empty ();
129132 }
133+ SearchQuery searchQuery =
134+ SearchQuery (query, this .cx, searchType: searchType);
135+ var cachedResponse = await _cache.get (searchQuery);
136+ if (cachedResponse != null ) {
137+ return cachedResponse;
138+ }
130139 Map searchMap = jsonDecode (searchResponses[query].searchResponseJsonString);
131140 customsearch.Search search = customsearch.Search .fromJson (searchMap);
132- return SearchResults (search);
141+
142+ print ('count: $count ' );
143+ count++ ;
144+
145+ var result = SearchResults (search);
146+ await _cache.set (searchQuery, result);
147+ return result;
133148 }
134149}
135150
151+ /// A wrapper class for search request, to make caching search request possible.
152+ class SearchQuery {
153+ String q;
154+ String c2coff;
155+ String cr;
156+ String cx;
157+ String dateRestrict;
158+ String exactTerms;
159+ String excludeTerms;
160+ String fileType;
161+ String filter;
162+ String gl;
163+ String googlehost;
164+ String highRange;
165+ String hl;
166+ String hq;
167+ String imgColorType;
168+ String imgDominantColor;
169+ String imgSize;
170+ String imgType;
171+ String linkSite;
172+ String lowRange;
173+ String lr;
174+ int num ;
175+ String orTerms;
176+ String relatedSite;
177+ String rights;
178+ String safe;
179+ String searchType;
180+ String siteSearch;
181+ String siteSearchFilter;
182+ String sort;
183+ int start;
184+
185+ /// Used to get partial response, see:
186+ /// https://developers.google.com/custom-search/v1/performance#partial
187+ String fields;
188+
189+ SearchQuery (this .q, this .cx,
190+ {this .c2coff,
191+ this .cr,
192+ this .dateRestrict,
193+ this .exactTerms,
194+ this .excludeTerms,
195+ this .fileType,
196+ this .filter,
197+ this .gl,
198+ this .googlehost,
199+ this .highRange,
200+ this .hl,
201+ this .hq,
202+ this .imgColorType,
203+ this .imgDominantColor,
204+ this .imgSize,
205+ this .imgType,
206+ this .linkSite,
207+ this .lowRange,
208+ this .lr,
209+ this .num ,
210+ this .orTerms,
211+ this .relatedSite,
212+ this .rights,
213+ this .safe,
214+ this .searchType,
215+ this .siteSearch,
216+ this .siteSearchFilter,
217+ this .sort,
218+ this .start,
219+ this .fields});
220+
221+ Future <SearchResults > runSearch (customsearch.CustomsearchApi api) async {
222+ return SearchResults (
223+ await api.cse.list (q, cx: cx, searchType: this .searchType));
224+ }
225+
226+ @override
227+ String toString () {
228+ return 'SearchQuery{q: $q , c2coff: $c2coff , cr: $cr , cx: $cx , dateRestrict: $dateRestrict , exactTerms: $exactTerms , excludeTerms: $excludeTerms , fileType: $fileType , filter: $filter , gl: $gl , googlehost: $googlehost , highRange: $highRange , hl: $hl , hq: $hq , imgColorType: $imgColorType , imgDominantColor: $imgDominantColor , imgSize: $imgSize , imgType: $imgType , linkSite: $linkSite , lowRange: $lowRange , lr: $lr , num: $num , orTerms: $orTerms , relatedSite: $relatedSite , rights: $rights , safe: $safe , searchType: $searchType , siteSearch: $siteSearch , siteSearchFilter: $siteSearchFilter , sort: $sort , start: $start , fields: $fields }' ;
229+ }
230+
231+ @override
232+ bool operator == (Object other) =>
233+ identical (this , other) ||
234+ other is SearchQuery &&
235+ runtimeType == other.runtimeType &&
236+ q == other.q &&
237+ c2coff == other.c2coff &&
238+ cr == other.cr &&
239+ cx == other.cx &&
240+ dateRestrict == other.dateRestrict &&
241+ exactTerms == other.exactTerms &&
242+ excludeTerms == other.excludeTerms &&
243+ fileType == other.fileType &&
244+ filter == other.filter &&
245+ gl == other.gl &&
246+ googlehost == other.googlehost &&
247+ highRange == other.highRange &&
248+ hl == other.hl &&
249+ hq == other.hq &&
250+ imgColorType == other.imgColorType &&
251+ imgDominantColor == other.imgDominantColor &&
252+ imgSize == other.imgSize &&
253+ imgType == other.imgType &&
254+ linkSite == other.linkSite &&
255+ lowRange == other.lowRange &&
256+ lr == other.lr &&
257+ num == other.num &&
258+ orTerms == other.orTerms &&
259+ relatedSite == other.relatedSite &&
260+ rights == other.rights &&
261+ safe == other.safe &&
262+ searchType == other.searchType &&
263+ siteSearch == other.siteSearch &&
264+ siteSearchFilter == other.siteSearchFilter &&
265+ sort == other.sort &&
266+ start == other.start &&
267+ fields == other.fields;
268+
269+ @override
270+ int get hashCode =>
271+ q.hashCode ^
272+ c2coff.hashCode ^
273+ cr.hashCode ^
274+ cx.hashCode ^
275+ dateRestrict.hashCode ^
276+ exactTerms.hashCode ^
277+ excludeTerms.hashCode ^
278+ fileType.hashCode ^
279+ filter.hashCode ^
280+ gl.hashCode ^
281+ googlehost.hashCode ^
282+ highRange.hashCode ^
283+ hl.hashCode ^
284+ hq.hashCode ^
285+ imgColorType.hashCode ^
286+ imgDominantColor.hashCode ^
287+ imgSize.hashCode ^
288+ imgType.hashCode ^
289+ linkSite.hashCode ^
290+ lowRange.hashCode ^
291+ lr.hashCode ^
292+ num .hashCode ^
293+ orTerms.hashCode ^
294+ relatedSite.hashCode ^
295+ rights.hashCode ^
296+ safe.hashCode ^
297+ searchType.hashCode ^
298+ siteSearch.hashCode ^
299+ siteSearchFilter.hashCode ^
300+ sort.hashCode ^
301+ start.hashCode ^
302+ fields.hashCode;
303+ }
304+
136305/// The search data source that uses Custom Search API.
137306class CustomSearchDataSource implements SearchDataSource {
138307 final String cx;
139308 final String apiKey;
140- var api;
309+ customsearch. CustomsearchApi api;
141310 int searchCount = 0 ;
311+ final ExpireCache <SearchQuery , SearchResults > _cache =
312+ ExpireCache <SearchQuery , SearchResults >();
142313
143314 CustomSearchDataSource ({@required this .cx, @required this .apiKey}) {
144315 var client = auth.clientViaApiKey (apiKey);
@@ -150,9 +321,20 @@ class CustomSearchDataSource implements SearchDataSource {
150321 if (query.isEmpty) {
151322 return SearchResults .empty ();
152323 }
153- customsearch.Search search =
154- await this .api.cse.list (query, cx: this .cx, searchType: searchType);
155- return SearchResults (search);
324+ SearchQuery searchQuery =
325+ SearchQuery (query, this .cx, searchType: searchType);
326+
327+ final cachedResponse = await _cache.get (searchQuery);
328+ if (cachedResponse != null ) {
329+ return cachedResponse;
330+ }
331+
332+ print ('count: $searchCount ' );
333+ searchCount++ ;
334+
335+ final result = await searchQuery.runSearch (this .api);
336+ await _cache.set (searchQuery, result);
337+ return result;
156338 }
157339}
158340
@@ -162,6 +344,8 @@ abstract class AutoCompleteDataSource {
162344
163345class CommonEnglishWordAutoCompleteDataSource
164346 implements AutoCompleteDataSource {
347+ const CommonEnglishWordAutoCompleteDataSource ();
348+
165349 @override
166350 List <String > getAutoCompletions ({String query, int resultNumber = 10 }) {
167351 var results = all.where ((String word) => word.startsWith (query)).toList ();
0 commit comments