Skip to content
This repository was archived by the owner on Jan 31, 2022. It is now read-only.

Commit fa867e1

Browse files
authored
Merge pull request #117 from algolia/offline
Integrate the latest offline features
2 parents c2bda46 + 6ab7432 commit fa867e1

File tree

8 files changed

+612
-182
lines changed

8 files changed

+612
-182
lines changed

ChangeLog.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
Change Log
22
==========
33

4+
## Next version
5+
6+
- New `Index.multipleQueriesAsync()` method (convenience shortcut to `Client.multipleQueriesAsync()`)
7+
8+
The following changes impact the offline mode only:
9+
10+
- Require an Android `Context` to be provided during client construction. **Warning: breaking change.**
11+
- Offline requests now also work for disjunctive faceting
12+
- New offline fallback strategy. **Warning: breaking change:** preemptive search no longer supported.
13+
- Improve detection of non-existent indices
14+
- Improve error handling
15+
- Fix crash on concurrent accesses to a local index
16+
- Fix potential race condition in lazy instantiation of the local index
17+
18+
419
## 3.2.2 (2016-06-06)
520

621
- Better resource handling: close client streams and connection when appropriate

algoliasearch/build-offline.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ android {
3737
}
3838

3939
dependencies {
40-
offlineCompile "com.algolia:algoliasearch-offline-core-android:0.1"
40+
offlineCompile "com.algolia:algoliasearch-offline-core-android:0.2"
4141
}
4242

4343

algoliasearch/src/main/java/com/algolia/search/saas/Client.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,9 @@ abstract class AsyncTaskRequest implements Request {
790790
/** The completion handler notified of the result. May be null if the caller omitted it. */
791791
private CompletionHandler completionHandler;
792792

793+
/** The executor used to execute the request. */
794+
private ExecutorService executorService;
795+
793796
private boolean finished = false;
794797

795798
/**
@@ -820,12 +823,23 @@ protected void onCancelled(APIResult apiResult) {
820823
};
821824

822825
/**
823-
* Construct a new request with the specified completion handler.
826+
* Construct a new request with the specified completion handler, executing on the client's default executor.
824827
*
825828
* @param completionHandler The completion handler to be notified of results. May be null if the caller omitted it.
826829
*/
827830
AsyncTaskRequest(CompletionHandler completionHandler) {
831+
this(completionHandler, searchExecutorService);
832+
}
833+
834+
/**
835+
* Construct a new request with the specified completion handler, executing on the specified executor.
836+
*
837+
* @param completionHandler The completion handler to be notified of results. May be null if the caller omitted it.
838+
* @param executorService Executor service on which to execute the request.
839+
*/
840+
AsyncTaskRequest(CompletionHandler completionHandler, @NonNull ExecutorService executorService) {
828841
this.completionHandler = completionHandler;
842+
this.executorService = executorService;
829843
}
830844

831845
/**
@@ -850,7 +864,7 @@ AsyncTaskRequest start() {
850864
// WARNING: Starting with Honeycomb (3.0), `AsyncTask` execution is serial, so we must force parallel
851865
// execution. See <http://developer.android.com/reference/android/os/AsyncTask.html>.
852866
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
853-
task.executeOnExecutor(searchExecutorService);
867+
task.executeOnExecutor(executorService);
854868
} else {
855869
task.execute();
856870
}

algoliasearch/src/main/java/com/algolia/search/saas/Index.java

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,30 @@ public JSONObject searchSync(@NonNull Query query) throws AlgoliaException {
135135
return search(query);
136136
}
137137

138+
/**
139+
* Run multiple queries on this index with one API call.
140+
* A variant of {@link Client#multipleQueriesAsync(List, Client.MultipleQueriesStrategy, CompletionHandler)}
141+
* where the targeted index is always the receiver.
142+
*
143+
* @param queries The queries to run.
144+
* @param strategy The strategy to use.
145+
* @param completionHandler The listener that will be notified of the request's outcome.
146+
* @return A cancellable request.
147+
*/
148+
public Request multipleQueriesAsync(final @NonNull List<Query> queries, final Client.MultipleQueriesStrategy strategy, @NonNull CompletionHandler completionHandler) {
149+
final List<Query> queriesCopy = new ArrayList<>(queries.size());
150+
for (Query query: queries) {
151+
queriesCopy.add(new Query(query));
152+
}
153+
return getClient().new AsyncTaskRequest(completionHandler) {
154+
@NonNull
155+
@Override
156+
JSONObject run() throws AlgoliaException {
157+
return multipleQueries(queriesCopy, strategy == null ? null : strategy.toString());
158+
}
159+
}.start();
160+
}
161+
138162
/**
139163
* Search inside this index synchronously.
140164
*
@@ -153,20 +177,22 @@ protected byte[] searchSyncRaw(@NonNull Query query) throws AlgoliaException {
153177
* @param completionHandler The listener that will be notified of the request's outcome.
154178
* @return A cancellable request.
155179
*/
156-
public Request searchDisjunctiveFacetingAsync(@NonNull Query query, @NonNull List<String> disjunctiveFacets, @NonNull Map<String, List<String>> refinements, @NonNull CompletionHandler completionHandler) {
157-
final Query queryCopy = new Query(query);
158-
final List<String> disjunctiveFacetsCopy = new ArrayList<>(disjunctiveFacets);
159-
final Map<String, List<String>> refinementsCopy = new HashMap<>();
160-
for (Map.Entry<String, List<String>> entry : refinements.entrySet()) {
161-
refinementsCopy.put(entry.getKey(), new ArrayList<String>(entry.getValue()));
162-
}
163-
return getClient().new AsyncTaskRequest(completionHandler) {
164-
@NonNull
180+
public Request searchDisjunctiveFacetingAsync(@NonNull Query query, @NonNull final List<String> disjunctiveFacets, @NonNull final Map<String, List<String>> refinements, @NonNull final CompletionHandler completionHandler) {
181+
final List<Query> queries = computeDisjunctiveFacetingQueries(query, disjunctiveFacets, refinements);
182+
return multipleQueriesAsync(queries, null, new CompletionHandler() {
165183
@Override
166-
JSONObject run() throws AlgoliaException {
167-
return searchDisjunctiveFaceting(queryCopy, disjunctiveFacetsCopy, refinementsCopy);
184+
public void requestCompleted(JSONObject content, AlgoliaException error) {
185+
JSONObject aggregatedResults = null;
186+
try {
187+
if (content != null) {
188+
aggregatedResults = aggregateDisjunctiveFacetingResults(content, disjunctiveFacets, refinements);
189+
}
190+
} catch (AlgoliaException e) {
191+
error = e;
192+
}
193+
completionHandler.requestCompleted(aggregatedResults, error);
168194
}
169-
}.start();
195+
});
170196
}
171197

172198
/**
@@ -957,25 +983,38 @@ protected JSONObject clearIndex() throws AlgoliaException {
957983
}
958984

959985
/**
960-
* Perform a search with disjunctive facets generating as many queries as number of disjunctive facets
986+
* Filter disjunctive refinements from generic refinements and a list of disjunctive facets.
961987
*
962-
* @param query the query
963988
* @param disjunctiveFacets the array of disjunctive facets
964989
* @param refinements Map representing the current refinements
965-
* @throws AlgoliaException
990+
* @return The disjunctive refinements
966991
*/
967-
protected JSONObject searchDisjunctiveFaceting(@NonNull Query query, @NonNull List<String> disjunctiveFacets, @NonNull Map<String, List<String>> refinements) throws AlgoliaException {
968-
// Retain only refinements corresponding to the disjunctive facets.
992+
private @NonNull Map<String, List<String>> computeDisjunctiveRefinements(@NonNull List<String> disjunctiveFacets, @NonNull Map<String, List<String>> refinements)
993+
{
969994
Map<String, List<String>> disjunctiveRefinements = new HashMap<>();
970995
for (Map.Entry<String, List<String>> elt : refinements.entrySet()) {
971996
if (disjunctiveFacets.contains(elt.getKey())) {
972997
disjunctiveRefinements.put(elt.getKey(), elt.getValue());
973998
}
974999
}
1000+
return disjunctiveRefinements;
1001+
}
1002+
1003+
/**
1004+
* Compute the queries to run to implement disjunctive faceting.
1005+
*
1006+
* @param query The query.
1007+
* @param disjunctiveFacets List of disjunctive facets.
1008+
* @param refinements The current refinements, mapping facet names to a list of values.
1009+
* @return A list of queries suitable for {@link Index#multipleQueries}.
1010+
*/
1011+
private @NonNull List<Query> computeDisjunctiveFacetingQueries(@NonNull Query query, @NonNull List<String> disjunctiveFacets, @NonNull Map<String, List<String>> refinements) {
1012+
// Retain only refinements corresponding to the disjunctive facets.
1013+
Map<String, List<String>> disjunctiveRefinements = computeDisjunctiveRefinements(disjunctiveFacets, refinements);
9751014

9761015
// build queries
9771016
// TODO: Refactor using JSON array notation: safer and clearer.
978-
List<IndexQuery> queries = new ArrayList<>();
1017+
List<Query> queries = new ArrayList<>();
9791018
// hits + regular facets query
9801019
StringBuilder filters = new StringBuilder();
9811020
boolean first_global = true;
@@ -1010,7 +1049,7 @@ protected JSONObject searchDisjunctiveFaceting(@NonNull Query query, @NonNull Li
10101049
}
10111050
}
10121051

1013-
queries.add(new IndexQuery(this.indexName, new Query(query).set("facetFilters", filters.toString())));
1052+
queries.add(new Query(query).set("facetFilters", filters.toString()));
10141053
// one query per disjunctive facet (use all refinements but the current one + hitsPerPage=1 + single facet
10151054
for (String disjunctiveFacet : disjunctiveFacets) {
10161055
filters = new StringBuilder();
@@ -1049,11 +1088,25 @@ protected JSONObject searchDisjunctiveFaceting(@NonNull Query query, @NonNull Li
10491088
}
10501089
}
10511090
String[] facets = new String[]{disjunctiveFacet};
1052-
queries.add(new IndexQuery(this.indexName, new Query(query).setHitsPerPage(0).setAnalytics(false)
1091+
queries.add(new Query(query).setHitsPerPage(0).setAnalytics(false)
10531092
.setAttributesToRetrieve().setAttributesToHighlight().setAttributesToSnippet()
1054-
.setFacets(facets).set("facetFilters", filters.toString())));
1093+
.setFacets(facets).set("facetFilters", filters.toString()));
10551094
}
1056-
JSONObject answers = this.client.multipleQueries(queries, null);
1095+
return queries;
1096+
}
1097+
1098+
/**
1099+
* Aggregate results from multiple queries into disjunctive faceting results.
1100+
*
1101+
* @param answers The response from the multiple queries.
1102+
* @param disjunctiveFacets List of disjunctive facets.
1103+
* @param refinements Facet refinements.
1104+
* @return The aggregated results.
1105+
* @throws AlgoliaException
1106+
*/
1107+
private JSONObject aggregateDisjunctiveFacetingResults(@NonNull JSONObject answers, @NonNull List<String> disjunctiveFacets, @NonNull Map<String, List<String>> refinements) throws AlgoliaException
1108+
{
1109+
Map<String, List<String>> disjunctiveRefinements = computeDisjunctiveRefinements(disjunctiveFacets, refinements);
10571110

10581111
// aggregate answers
10591112
// first answer stores the hits + regular facets
@@ -1099,4 +1152,21 @@ protected JSONObject browseFrom(@NonNull String cursor) throws AlgoliaException
10991152
throw new Error(e); // Should never happen: UTF-8 is always supported.
11001153
}
11011154
}
1155+
1156+
/**
1157+
* Run multiple queries on this index with one API call.
1158+
* A variant of {@link Client#multipleQueries(List, String)} where all queries target this index.
1159+
*
1160+
* @param queries Queries to run.
1161+
* @param strategy Strategy to use.
1162+
* @return The JSON results returned by the server.
1163+
* @throws AlgoliaException
1164+
*/
1165+
protected JSONObject multipleQueries(@NonNull List<Query> queries, String strategy) throws AlgoliaException {
1166+
List<IndexQuery> requests = new ArrayList<>(queries.size());
1167+
for (Query query: queries) {
1168+
requests.add(new IndexQuery(this, query));
1169+
}
1170+
return client.multipleQueries(requests, strategy);
1171+
}
11021172
}

0 commit comments

Comments
 (0)