diff --git a/core/res/values/strings.xml b/core/res/values/strings.xml
index a5dc5f8e..a3014d18 100644
--- a/core/res/values/strings.xml
+++ b/core/res/values/strings.xml
@@ -86,7 +86,7 @@
New label
Setting a label is not supported by your client
- Torrent added (refreshing)
+ %1$s added (refreshing)
%1$s removed
%1$s removed and data deleted
%1$s resumed (refreshing)
@@ -103,6 +103,7 @@
Torrent search
Search for torrents
+ The Barcode Scanner could not be found. Would you like to install it from the Android Market?
Servers
Add new server
diff --git a/core/src/org/transdroid/core/app/search/BarcodeHelper.java b/core/src/org/transdroid/core/app/search/BarcodeHelper.java
new file mode 100644
index 00000000..e504068d
--- /dev/null
+++ b/core/src/org/transdroid/core/app/search/BarcodeHelper.java
@@ -0,0 +1,71 @@
+package org.transdroid.core.app.search;
+
+import org.transdroid.core.R;
+import org.transdroid.core.gui.TorrentsActivity;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v4.app.DialogFragment;
+import android.text.TextUtils;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+public class BarcodeHelper {
+
+ public static final int ACTIVITY_BARCODE = 0x0000c0de; // A 'random' ID to identify scan intents
+ public static final Uri SCANNER_MARKET_URI = Uri.parse("market://search?q=pname:com.google.zxing.client.android");
+
+ /**
+ * Call this to start a bar code scanner intent. The calling activity will receive an Intent result with ID
+ * {@link #ACTIVITY_BARCODE}. From there {@link #handleScanResult(int, Intent)} should be called to parse the result
+ * into a search query.
+ * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
+ * the bar code scanner
+ */
+ public static void startBarcodeScanner(final SherlockFragmentActivity activity) {
+ try {
+ // Start a bar code scanner that can handle the SCAN intent (specifically ZXing)
+ activity.startActivityForResult(new Intent("com.google.zxing.client.android.SCAN"), ACTIVITY_BARCODE);
+ } catch (Exception e) {
+ // Can't start the bar code scanner, for example with a SecurityException or when ZXing is not present
+ new DialogFragment() {
+ public android.app.Dialog onCreateDialog(android.os.Bundle savedInstanceState) {
+ return new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(activity.getString(R.string.search_barcodescannernotfound))
+ .setPositiveButton(android.R.string.yes, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ activity.startActivity(new Intent(Intent.ACTION_VIEW, SCANNER_MARKET_URI));
+ }
+ }).setNegativeButton(android.R.string.no, null).create();
+ };
+ }.show(activity.getSupportFragmentManager(), "installscanner");
+ }
+ }
+
+ /**
+ * The activity that called {@link #startBarcodeScanner(SherlockFragmentActivity)} should call this after the scan
+ * result was returned. This will parse the scan data and return a query search query appropriate to the bar code.
+ * @param resultCode The raw result code as returned by the bar code scanner
+ * @param data The raw data as returned from the bar code scanner
+ * @return A String that can be used as new search query, or null if the bar code could not be scanned or no query
+ * can be constructed for it
+ */
+ public static String handleScanResult(TorrentsActivity activity, int resultCode, Intent data) {
+ String contents = data.getStringExtra("SCAN_RESULT");
+ String formatName = data.getStringExtra("SCAN_RESULT_FORMAT");
+ if (formatName != null && formatName.equals("QR_CODE")) {
+ // Scanned barcode was a QR code: return the contents directly
+ return contents;
+ } else {
+ if (TextUtils.isEmpty(contents))
+ return null;
+ // Get a meaningful search query based on a Google Search product lookup
+ return GoogleWebSearchBarcodeResolver.resolveBarcode(contents);
+ }
+ }
+
+}
diff --git a/core/src/org/transdroid/core/app/search/GoogleWebSearchBarcodeResolver.java b/core/src/org/transdroid/core/app/search/GoogleWebSearchBarcodeResolver.java
new file mode 100644
index 00000000..d681deba
--- /dev/null
+++ b/core/src/org/transdroid/core/app/search/GoogleWebSearchBarcodeResolver.java
@@ -0,0 +1,140 @@
+/*
+ * This file is part of Transdroid
+ *
+ * Transdroid 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.
+ *
+ * Transdroid 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 Transdroid. If not, see .
+ *
+ */
+package org.transdroid.core.app.search;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.transdroid.daemon.util.HttpHelper;
+
+public class GoogleWebSearchBarcodeResolver {
+
+ public static final String apiUrl = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=%s";
+
+ public static String resolveBarcode(String barcode) {
+
+ try {
+ // We use the Google AJAX Search API to get a JSON-formatted list of web search results
+ String callUrl = apiUrl.replace("%s", barcode);
+ DefaultHttpClient httpclient = new DefaultHttpClient();
+ HttpGet httpget = new HttpGet(callUrl);
+ HttpResponse response = httpclient.execute(httpget);
+ InputStream instream = response.getEntity().getContent();
+ String result = HttpHelper.ConvertStreamToString(instream);
+ JSONArray results = new JSONObject(result).getJSONObject("responseData").getJSONArray("results");
+
+ // We will combine and filter multiple results, if there are any
+ if (results.length() < 1) {
+ return null;
+ }
+ return stripGarbage(results, barcode);
+ } catch (Exception e) {
+ return null;
+ }
+
+ }
+
+ private static String stripGarbage(JSONArray results, String barcode) throws JSONException {
+
+ String good = " abcdefghijklmnopqrstuvwxyz";
+ final int MAX_TITLE_CONSIDER = 4;
+ final int MAX_MISSING = 1;
+ final int MIN_TITLE_CONSIDER = 2;
+
+ // First gather the titles for the first MAX_TITLE_CONSIDER results
+ List titles = new ArrayList();
+ for (int i = 0; i < results.length() && i < MAX_TITLE_CONSIDER; i++) {
+
+ String title = results.getJSONObject(i).getString("titleNoFormatting");
+
+ // Make string lowercase first
+ title = title.toLowerCase(Locale.US);
+
+ // Remove the barcode number if it's there
+ title = title.replace(barcode, "");
+
+ // Remove unwanted words and HTML special chars
+ for (String rem : new String[] { "dvd", "blu-ray", "bluray", "&", """, "'", "<", ">" }) {
+ title = title.replace(rem, "");
+ }
+
+ // Remove all non-alphanumeric (and space) characters
+ String result = "";
+ for ( int j = 0; j < title.length(); j++ ) {
+ if ( good.indexOf(title.charAt(j)) >= 0 )
+ result += title.charAt(j);
+ }
+
+ // Remove double spaces
+ while (result.contains(" ")) {
+ result = result.replace(" ", " ");
+ }
+
+ titles.add(result);
+
+ }
+
+ // Only retain the words that are missing in at most one of the search result titles
+ List allWords = new ArrayList();
+ for (String title : titles) {
+ for (String word : Arrays.asList(title.split(" "))) {
+ if (!allWords.contains(word)) {
+ allWords.add(word);
+ }
+ }
+ }
+ List remainingWords = new ArrayList();
+ int allowMissing = Math.min(MAX_MISSING, Math.max(titles.size() - MIN_TITLE_CONSIDER, 0));
+ for (String word : allWords) {
+
+ int missing = 0;
+ for (String title : titles) {
+ if (!title.contains(word)) {
+ // The word is not contained in this result title
+ missing++;
+ if (missing > allowMissing) {
+ // Already misssing more than once, no need to look further
+ break;
+ }
+ }
+ }
+ if (missing <= allowMissing) {
+ // The word was only missing at most once, so we keep it
+ remainingWords.add(word);
+ }
+ }
+
+ // Now the query is the concatenation of the words remaining; with spaces in between
+ String query = "";
+ for (String word : remainingWords) {
+ query += " " + word;
+ }
+ return query.length() > 0? query.substring(1): null;
+
+ }
+
+}
diff --git a/core/src/org/transdroid/core/gui/TorrentsActivity.java b/core/src/org/transdroid/core/gui/TorrentsActivity.java
index 48bfdb31..154ffd29 100644
--- a/core/src/org/transdroid/core/gui/TorrentsActivity.java
+++ b/core/src/org/transdroid/core/gui/TorrentsActivity.java
@@ -9,12 +9,14 @@ import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.FragmentById;
import org.androidannotations.annotations.InstanceState;
+import org.androidannotations.annotations.OnActivityResult;
import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.SystemService;
import org.androidannotations.annotations.UiThread;
import org.androidannotations.annotations.ViewById;
import org.transdroid.core.R;
+import org.transdroid.core.app.search.BarcodeHelper;
import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.gui.lists.LocalTorrent;
@@ -33,6 +35,7 @@ import org.transdroid.core.gui.settings.MainSettingsActivity_;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Torrent;
+import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult;
@@ -95,11 +98,9 @@ public class TorrentsActivity extends SherlockFragmentActivity implements OnNavi
@InstanceState
protected boolean turleModeEnabled = false;
- // Torrents list components
+ // Contained torrent and details fragments
@FragmentById(resName = "torrent_list")
protected TorrentsFragment fragmentTorrents;
-
- // Details view components
@FragmentById(resName = "torrent_details")
protected DetailsFragment fragmentDetails;
@@ -322,6 +323,30 @@ public class TorrentsActivity extends SherlockFragmentActivity implements OnNavi
// TODO: Handle start intent
}
+ @OptionsItem(resName = "action_add_fromurl")
+ protected void startUrlEntryDialog() {
+ // TODO: Open URL input dialog
+ }
+
+ @OptionsItem(resName = "action_add_fromfile")
+ protected void startFilePicker() {
+ // TODO: Start file picker
+ }
+
+ @OptionsItem(resName = "action_add_frombarcode")
+ protected void startBarcodeScanner() {
+ BarcodeHelper.startBarcodeScanner(this);
+ }
+
+ @OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE)
+ public void onBarcodeScanned(int resultCode, Intent data) {
+ String query = BarcodeHelper.handleScanResult(this, resultCode, data);
+ if (query.startsWith("http"))
+ addTorrentByUrl(query, "QR code result"); // No torrent title known
+ else
+ startSearch(query, false, null, false);
+ }
+
@OptionsItem(resName = "action_refresh")
protected void refreshScreen() {
fragmentTorrents.updateIsLoading(true);
@@ -340,6 +365,10 @@ public class TorrentsActivity extends SherlockFragmentActivity implements OnNavi
updateTurtleMode(false);
}
+ @OptionsItem(resName = "action_filter")
+ protected void filterList() {
+ }
+
@OptionsItem(resName = "action_settings")
protected void openSettings() {
MainSettingsActivity_.intent(this).start();
@@ -382,6 +411,17 @@ public class TorrentsActivity extends SherlockFragmentActivity implements OnNavi
}
}
+ @Background
+ public void addTorrentByUrl(String url, String title) {
+ DaemonTaskResult result = AddByUrlTask.create(currentConnection, url, title).execute();
+ if (result instanceof DaemonTaskResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_added, title);
+ refreshTorrents();
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result);
+ }
+ }
+
@Background
@Override
public void resumeTorrent(Torrent torrent) {