package de.onyxbits.raccoon.gui; import java.io.File; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Vector; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.prefs.Preferences; import javax.swing.JLabel; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.apache.commons.io.FileUtils; import com.akdeniz.googleplaycrawler.GooglePlay.BulkDetailsEntry; import com.akdeniz.googleplaycrawler.GooglePlay.BulkDetailsResponse; import com.akdeniz.googleplaycrawler.GooglePlay.DocV2; import com.akdeniz.googleplaycrawler.GooglePlay.Image; import com.akdeniz.googleplaycrawler.GooglePlay.SearchResponse; import com.akdeniz.googleplaycrawler.GooglePlayAPI; import com.akdeniz.googleplaycrawler.GooglePlayException; import de.onyxbits.raccoon.App; import de.onyxbits.raccoon.BrowseUtil; import de.onyxbits.raccoon.Messages; import de.onyxbits.raccoon.io.Archive; /** * A background task for performing searches on Google Play. * * @author patrick * */ class SearchWorker extends SwingWorker<Vector<BulkDetailsEntry>, String> { private String search; private SearchView searchView; private int offset; private int limit; private String localization; private Archive archive; private boolean fetchIcons; /** * * @param archive * storage area. * @param search * what to submit to google (ideally a packagename, but the search * engine also accepts anything else). May be null to search for * updates. * @param callback * where to report back when the results are in. */ public SearchWorker(Archive archive, String search, SearchView callback) { this.archive = archive; this.search = search; this.searchView = callback; this.limit = 10; this.localization = Locale.getDefault().getCountry(); if (callback == null) { throw new NullPointerException(); } Preferences prefs = Preferences.userNodeForPackage(MainActivity.class); fetchIcons = prefs.getBoolean(MainActivity.FETCHICONS, true); } /** * Optional starting page (default: 0) * * @param o * index * @return a this reference for chaining. */ public SearchWorker withOffset(int o) { this.offset = o; return this; } /** * Max number of results to retrieve (default: 10). * * @param l * amount of result entries * @return a this reference for chaining. */ public SearchWorker withLimit(int l) { this.limit = l; return this; } /** * What language to return results in (default "en"); * * @param l * locale id * @return this reference for chaining. */ public SearchWorker withLocalization(String l) { this.localization = l; return this; } @Override protected Vector<BulkDetailsEntry> doInBackground() throws Exception { if (search != null) { return doQuerySearch(); } else { return doUpdateSearch(); } } private Vector<BulkDetailsEntry> doUpdateSearch() throws Exception { GooglePlayAPI service = App.createConnection(archive); BulkDetailsResponse response = service.bulkDetails(archive.list()); Vector<BulkDetailsEntry> ret = new Vector<BulkDetailsEntry>(); for (BulkDetailsEntry bulkDetailsEntry : response.getEntryList()) { DocV2 doc = bulkDetailsEntry.getDoc(); String pn = doc.getBackendDocid(); int vc = doc.getDetails().getAppDetails().getVersionCode(); if (!archive.fileUnder(pn, vc).exists()) { ret.add(bulkDetailsEntry); } if (fetchIcons) { fetchIcon(doc); } } return ret; } private Vector<BulkDetailsEntry> doQuerySearch() throws Exception { GooglePlayAPI service = App.createConnection(archive); service.setLocalization(localization); SearchResponse response = service.search(search, offset, limit); List<String> apps = new Vector<String>(); if (response.getDocCount() > 0) { DocV2 all = response.getDoc(0); for (int i = 0; i < all.getChildCount(); i++) { apps.add(all.getChild(i).getBackendDocid()); if (fetchIcons) { fetchIcon(all.getChild(i)); } } } BulkDetailsResponse bdr = service.bulkDetails(apps); Vector<BulkDetailsEntry> ret = new Vector<BulkDetailsEntry>(); for (int i = 0; i < bdr.getEntryCount(); i++) { ret.add(bdr.getEntry(i)); } return ret; } private void fetchIcon(DocV2 doc) { if (isCancelled()) { return; } List<Image> lst = doc.getImageList(); Iterator<Image> it = lst.iterator(); while (it.hasNext()) { Image img = it.next(); if (img.getImageType() == 4) { try { File f = getImageCacheFile(doc.getBackendDocid(), 4); if (!f.exists()) { URL u = new URL(img.getImageUrl()); FileUtils.copyURLToFile(u, f); } } catch (Exception e) { e.printStackTrace(); } } } } /** * Figure out where to images * * @param appId * package id * @param type * the numerical type of the image * @return where to cache the image. */ public static File getImageCacheFile(String appId, int type) { return new File(App.getDir(App.CACHEDIR), appId + "-img-" + type); //$NON-NLS-1$ } @Override protected void done() { Vector<BulkDetailsEntry> response = new Vector<BulkDetailsEntry>(); try { response = get(); } catch (CancellationException e) { searchView.doMessage(Messages.getString("SearchWorker.4")); //$NON-NLS-1$ SwingUtilities.invokeLater(searchView); return; } catch (InterruptedException e) { searchView.doMessage(Messages.getString("SearchWorker.5")); //$NON-NLS-1$ SwingUtilities.invokeLater(searchView); return; } catch (ExecutionException e) { // Stuff that happened on the backgroundthread. Throwable wrapped = e.getCause(); if (wrapped instanceof GooglePlayException) { searchView.doMessage(Messages.getString("SearchWorker.1")); //$NON-NLS-1$ e.printStackTrace(); } else { searchView.doMessage(e.getMessage()); } SwingUtilities.invokeLater(searchView); return; } ListView listing = new ListView(); for (BulkDetailsEntry bulkDetailsEntry : response) { DocV2 doc = bulkDetailsEntry.getDoc(); try { listing.add(ResultView.create(searchView, doc)); } catch (Exception e) { // We likely get here when trying to update an archive and the user // either had the brilliant idea of creating his/her own directories // in the APK storage or if a stored app is no longer listed on // Google. Maybe, the user even thought it clever to dump externally // downloaded apps into the storage. Design decision: silently // ignore, don't bother alerting the user and most certainly don't // try to automatically fix anything. } } if (listing.getComponentCount() > 0) { HypertextPane hp = new HypertextPane(Messages.getString("SearchWorker.6")); hp.addHyperlinkListener(new BrowseUtil()); listing.add(hp); hp.setBorder(null); searchView.doResultList(listing); } else { if (search == null) { searchView.doMessage(Messages.getString("SearchWorker.2")); //$NON-NLS-1$ } else { searchView.doMessage(Messages.getString("SearchWorker.3")); //$NON-NLS-1$ } } SwingUtilities.invokeLater(searchView); } }