1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
package de.danoeh.antennapod.discovery;
import android.text.TextUtils;
import android.util.Log;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
public class CombinedSearcher implements PodcastSearcher {
private static final String TAG = "CombinedSearcher";
public CombinedSearcher() {
}
public Single<List<PodcastSearchResult>> search(String query) {
ArrayList<Disposable> disposables = new ArrayList<>();
List<List<PodcastSearchResult>> singleResults = new ArrayList<>(
Collections.nCopies(PodcastSearcherRegistry.getSearchProviders().size(), null));
CountDownLatch latch = new CountDownLatch(PodcastSearcherRegistry.getSearchProviders().size());
for (int i = 0; i < PodcastSearcherRegistry.getSearchProviders().size(); i++) {
PodcastSearcherRegistry.SearcherInfo searchProviderInfo
= PodcastSearcherRegistry.getSearchProviders().get(i);
PodcastSearcher searcher = searchProviderInfo.searcher;
if (searchProviderInfo.weight <= 0.00001f || searcher.getClass() == CombinedSearcher.class) {
latch.countDown();
continue;
}
final int index = i;
disposables.add(searcher.search(query).subscribe(e -> {
singleResults.set(index, e);
latch.countDown();
}, throwable -> {
Log.d(TAG, Log.getStackTraceString(throwable));
latch.countDown();
}
));
}
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
latch.await();
List<PodcastSearchResult> results = weightSearchResults(singleResults);
subscriber.onSuccess(results);
})
.doOnDispose(() -> {
for (Disposable disposable : disposables) {
if (disposable != null) {
disposable.dispose();
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private List<PodcastSearchResult> weightSearchResults(List<List<PodcastSearchResult>> singleResults) {
HashMap<String, Float> resultRanking = new HashMap<>();
HashMap<String, PodcastSearchResult> urlToResult = new HashMap<>();
for (int i = 0; i < singleResults.size(); i++) {
float providerPriority = PodcastSearcherRegistry.getSearchProviders().get(i).weight;
List<PodcastSearchResult> providerResults = singleResults.get(i);
if (providerResults == null) {
continue;
}
for (int position = 0; position < providerResults.size(); position++) {
PodcastSearchResult result = providerResults.get(position);
urlToResult.put(result.feedUrl, result);
float ranking = 0;
if (resultRanking.containsKey(result.feedUrl)) {
ranking = resultRanking.get(result.feedUrl);
}
ranking += 1.f / (position + 1.f);
resultRanking.put(result.feedUrl, ranking * providerPriority);
}
}
List<Map.Entry<String, Float>> sortedResults = new ArrayList<>(resultRanking.entrySet());
Collections.sort(sortedResults, (o1, o2) -> Double.compare(o2.getValue(), o1.getValue()));
List<PodcastSearchResult> results = new ArrayList<>();
for (Map.Entry<String, Float> res : sortedResults) {
results.add(urlToResult.get(res.getKey()));
}
return results;
}
@Override
public Single<String> lookupUrl(String url) {
return PodcastSearcherRegistry.lookupUrl(url);
}
@Override
public boolean urlNeedsLookup(String url) {
return PodcastSearcherRegistry.urlNeedsLookup(url);
}
@Override
public String getName() {
ArrayList<String> names = new ArrayList<>();
for (int i = 0; i < PodcastSearcherRegistry.getSearchProviders().size(); i++) {
PodcastSearcherRegistry.SearcherInfo searchProviderInfo
= PodcastSearcherRegistry.getSearchProviders().get(i);
PodcastSearcher searcher = searchProviderInfo.searcher;
if (searchProviderInfo.weight > 0.00001f && searcher.getClass() != CombinedSearcher.class) {
names.add(searcher.getName());
}
}
return TextUtils.join(", ", names);
}
}
|