/* * Created by Angel Leon (@gubatron), Alden Torres (aldenml) * Copyright (c) 2011-2014,, FrostWire(R). All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.frostwire.search.appia; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import android.os.AsyncTask; import com.bt.download.android.core.MediaType; import com.bt.download.android.gui.SearchEngine; import com.bt.download.android.gui.util.OfferUtils; import com.frostwire.util.StringUtils; import com.frostwire.logging.Logger; import com.frostwire.search.PagedWebSearchPerformer; import com.frostwire.search.SearchResult; import com.frostwire.search.domainalias.DomainAliasManager; import com.frostwire.search.frostclick.UserAgent; import com.frostwire.util.HttpClientFactory; import com.frostwire.util.JsonUtils; /** * @author gubatron * @author aldenml * */ public class AppiaSearchPerformer extends PagedWebSearchPerformer { private final AppiaSearchThrottle throttle; public static final String HTTP_SERVER_NAME = "192.168.1.14"; private static final Logger LOG = Logger.getLogger(AppiaSearchPerformer.class); private static final int MAX_RESULTS = 1; private final Map<String, String> customHeaders; public AppiaSearchPerformer(DomainAliasManager domainAliasManager, long token, String keywords, int timeout, UserAgent userAgent, String androidId, AppiaSearchThrottle throttle) { super(domainAliasManager ,token, keywords, timeout, MAX_RESULTS); this.customHeaders = buildCustomHeaders(userAgent, androidId); this.throttle = throttle; } @Override protected String getUrl(int page, String encodedKeywords) { return "http://api.frostclick.com/appia"; // for local debugging. //return "http://" + HTTP_SERVER_NAME + ":8080/frostclick-search-api/appia"; } @Override protected List<? extends SearchResult> searchPage(int page) { List<? extends SearchResult> result = Collections.emptyList(); if (OfferUtils.isAppiaSearchEnabled() && throttle.canSearchAgain()) { String url = getUrl(-1, getEncodedKeywords()); String text = null; try { text = fetch(url, null, customHeaders); } catch (IOException e) { checkAccesibleDomains(); return result; } if (text != null) { result = searchPage(text); } else { LOG.warn("Page content empty for url: " + url); } } return result; } @Override protected List<? extends SearchResult> searchPage(String page) { List<AppiaSearchResult> results = new ArrayList<AppiaSearchResult>(); AppiaServletResponse appiaServletResponse = JsonUtils.toObject(page, AppiaServletResponse.class); Set<String> keySet = appiaServletResponse.results.keySet(); for (String categoryId : keySet) { List<AppiaServletResponseItem> responseItems = appiaServletResponse.results.get(categoryId); for (AppiaServletResponseItem item : responseItems) { AppiaSearchResult sr = new AppiaSearchResult(item, categoryId); results.add(sr); AppiaSearchPerformer.asyncHttpGet(sr.getImpressionTrackingURL()); } } if (results.isEmpty()) { results = Collections.emptyList(); } //optimization #hack, should be gone when appia fixes this on their end. //copyTorrentResultsToAudioResultsIfNothingFound(results); return results; } /** * Delete this when Appia is able to provide audio/music related search results. * @param results */ @SuppressWarnings("unused") private void copyTorrentResultsToAudioResultsIfNothingFound(List<AppiaSearchResult> results) { int countAudioMediaType = 0; List<AppiaSearchResult> appMediaTypedOnes = new ArrayList<AppiaSearchResult>(); for (AppiaSearchResult sr : results) { if (sr.getMediaType().equals(MediaType.getAudioMediaType())) { countAudioMediaType++; } else if (sr.getMediaType().equals(MediaType.getTorrentMediaType())) { appMediaTypedOnes.add(new AppiaSearchResult(sr, AppiaSearchResult.CAT_ANDROID_APPS_MUSIC)); } } //no audio results? let's use the new copies then. if (countAudioMediaType == 0 && !appMediaTypedOnes.isEmpty()) { results.addAll(appMediaTypedOnes); } } private Map<String, String> buildCustomHeaders(UserAgent userAgent, String androidId) { Map<String, String> map = new HashMap<String, String>(); map.putAll(userAgent.getHeadersMap()); map.put("User-Agent", userAgent.toString()); map.put("sessionId", userAgent.getUUID()); map.put("androidId", androidId); return map; } private static void asyncHttpGet(String url) { if (!StringUtils.isNullOrEmpty(url)) { AsyncTask<String, Void, Void> task = new AsyncTask<String, Void, Void>() { @Override protected Void doInBackground(String... url) { try { String output = HttpClientFactory.newInstance().get(url[0], 10000, SearchEngine.FROSTWIRE_ANDROID_USER_AGENT.toString()); if (output != null) { System.out.println("Pixel tracked at " + url[0]); } } catch (IOException e) { e.printStackTrace(); } return null; } }; task.execute(url); } } public final static class AppiaSearchThrottle { private final int MAX_SEARCHES_WITHIN_TIME_INTERVAL = 3; private final int TIME_INTERVAL = 2 * 60 * 1000; private int searchAttempts; private long lastTimeSearchPerformed; public AppiaSearchThrottle() { searchAttempts = 0; lastTimeSearchPerformed = -1; } public boolean canSearchAgain() { searchAttempts++; long timeSince = System.currentTimeMillis() - lastTimeSearchPerformed; boolean enoughTimePassed = timeSince >= TIME_INTERVAL; boolean enoughSearches = searchAttempts % MAX_SEARCHES_WITHIN_TIME_INTERVAL == 0; boolean canSearchAgain = enoughTimePassed || enoughSearches; if (canSearchAgain) { lastTimeSearchPerformed = System.currentTimeMillis(); searchAttempts = 1; } return canSearchAgain; } } }