[discident-glib] ean: Add support for Amazon lookups



commit 5a647ae620eacd064b94995e1ec14e0a11b41925
Author: Bastien Nocera <hadess hadess net>
Date:   Mon Apr 8 20:10:41 2013 +0200

    ean: Add support for Amazon lookups

 discident-glib/Makefile.am                  |    6 +-
 discident-glib/amz-response.xml             |  161 +++++++++++
 discident-glib/discident-ean-amz-glib.c     |  408 +++++++++++++++++++++++++++
 discident-glib/discident-ean-amz-glib.h     |   43 +++
 discident-glib/discident-ean-glib.c         |   60 ++++-
 discident-glib/discident-ean-private-glib.h |    9 +
 discident-glib/discident-glib.symbols       |    2 +
 discident-glib/test-diglib.c                |  101 +++++++
 8 files changed, 781 insertions(+), 9 deletions(-)
---
diff --git a/discident-glib/Makefile.am b/discident-glib/Makefile.am
index 4082295..e78f695 100644
--- a/discident-glib/Makefile.am
+++ b/discident-glib/Makefile.am
@@ -1,6 +1,6 @@
 include $(top_srcdir)/Makefile.decl
 
-EXTRA_DIST = discident-glib.symbols rl-response.xml
+EXTRA_DIST = discident-glib.symbols rl-response.xml amz-response.xml
 BUILT_GIRSOURCES =
 
 lib_LTLIBRARIES = libdiscident-glib.la
@@ -18,7 +18,9 @@ libdiscident_glib_la_SOURCES =                                \
        discident-glib-private.h                        \
        discident-ean-private-glib.h                    \
        discident-ean-rl-glib.c                         \
-       discident-ean-rl-glib.h
+       discident-ean-rl-glib.h                         \
+       discident-ean-amz-glib.c                        \
+       discident-ean-amz-glib.h
 
 libdiscident_glib_la_LIBADD = $(DISCIDENT_LIBS)
 
diff --git a/discident-glib/amz-response.xml b/discident-glib/amz-response.xml
new file mode 100644
index 0000000..336fae7
--- /dev/null
+++ b/discident-glib/amz-response.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0"?>
+<ItemLookupResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2011-08-01";>
+  <OperationRequest>
+    <RequestId>e49631f6-80c6-49c2-aab4-3dd769616262</RequestId>
+    <Arguments>
+      <Argument Name="Operation" Value="ItemLookup"/>
+      <Argument Name="Service" Value="AWSECommerceService"/>
+      <Argument Name="AssociateTag" Value="hadessnet-21"/>
+      <Argument Name="SearchIndex" Value="DVD"/>
+      <Argument Name="Signature" Value="Q33sFvy7DOXxSTiKbnCSX9u9wYfnXMvsFJPSl8CP1xQ="/>
+      <Argument Name="ItemId" Value="9781449316853"/>
+      <Argument Name="IdType" Value="EAN"/>
+      <Argument Name="AWSAccessKeyId" Value="AKIAILKBE5WAT5HLCKJA"/>
+      <Argument Name="Timestamp" Value="2013-04-11T20:23:09Z"/>
+      <Argument Name="ResponseGroup" Value="ItemAttributes,Images"/>
+    </Arguments>
+    <RequestProcessingTime>0.0240230000000000</RequestProcessingTime>
+  </OperationRequest>
+  <Items>
+    <Request>
+      <IsValid>True</IsValid>
+      <ItemLookupRequest>
+        <IdType>EAN</IdType>
+        <ItemId>9781449316853</ItemId>
+        <ResponseGroup>ItemAttributes</ResponseGroup>
+        <ResponseGroup>Images</ResponseGroup>
+        <SearchIndex>DVD</SearchIndex>
+        <VariationPage>All</VariationPage>
+      </ItemLookupRequest>
+    </Request>
+    <Item>
+      <ASIN>1449316859</ASIN>
+      
<DetailPageURL>http://www.amazon.co.uk/JavaScript-Pocket-Reference-OReilly/dp/1449316859%3FSubscriptionId%3DAKIAILKBE5WAT5HLCKJA%26tag%3Dhadessnet-21%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D1449316859</DetailPageURL>
+      <ItemLinks>
+        <ItemLink>
+          <Description>Add To Wishlist</Description>
+          
<URL>http://www.amazon.co.uk/gp/registry/wishlist/add-item.html%3Fasin.0%3D1449316859%26SubscriptionId%3DAKIAILKBE5WAT5HLCKJA%26tag%3Dhadessnet-21%26linkCode%3Dxm2%26camp%3D2025%26creative%3D12734%26creativeASIN%3D1449316859</URL>
+        </ItemLink>
+        <ItemLink>
+          <Description>Tell A Friend</Description>
+          
<URL>http://www.amazon.co.uk/gp/pdp/taf/1449316859%3FSubscriptionId%3DAKIAILKBE5WAT5HLCKJA%26tag%3Dhadessnet-21%26linkCode%3Dxm2%26camp%3D2025%26creative%3D12734%26creativeASIN%3D1449316859</URL>
+        </ItemLink>
+        <ItemLink>
+          <Description>All Customer Reviews</Description>
+          
<URL>http://www.amazon.co.uk/review/product/1449316859%3FSubscriptionId%3DAKIAILKBE5WAT5HLCKJA%26tag%3Dhadessnet-21%26linkCode%3Dxm2%26camp%3D2025%26creative%3D12734%26creativeASIN%3D1449316859</URL>
+        </ItemLink>
+        <ItemLink>
+          <Description>All Offers</Description>
+          
<URL>http://www.amazon.co.uk/gp/offer-listing/1449316859%3FSubscriptionId%3DAKIAILKBE5WAT5HLCKJA%26tag%3Dhadessnet-21%26linkCode%3Dxm2%26camp%3D2025%26creative%3D12734%26creativeASIN%3D1449316859</URL>
+        </ItemLink>
+      </ItemLinks>
+      <SmallImage>
+        <URL>http://ecx.images-amazon.com/images/I/51UL7NX3-6L._SL75_.jpg</URL>
+        <Height Units="pixels">75</Height>
+        <Width Units="pixels">45</Width>
+      </SmallImage>
+      <MediumImage>
+        <URL>http://ecx.images-amazon.com/images/I/51UL7NX3-6L._SL160_.jpg</URL>
+        <Height Units="pixels">160</Height>
+        <Width Units="pixels">97</Width>
+      </MediumImage>
+      <LargeImage>
+        <URL>http://ecx.images-amazon.com/images/I/51UL7NX3-6L.jpg</URL>
+        <Height Units="pixels">500</Height>
+        <Width Units="pixels">303</Width>
+      </LargeImage>
+      <ImageSets>
+        <ImageSet Category="primary">
+          <SwatchImage>
+            <URL>http://ecx.images-amazon.com/images/I/51UL7NX3-6L._SL30_.jpg</URL>
+            <Height Units="pixels">30</Height>
+            <Width Units="pixels">18</Width>
+          </SwatchImage>
+          <SmallImage>
+            <URL>http://ecx.images-amazon.com/images/I/51UL7NX3-6L._SL75_.jpg</URL>
+            <Height Units="pixels">75</Height>
+            <Width Units="pixels">45</Width>
+          </SmallImage>
+          <ThumbnailImage>
+            <URL>http://ecx.images-amazon.com/images/I/51UL7NX3-6L._SL75_.jpg</URL>
+            <Height Units="pixels">75</Height>
+            <Width Units="pixels">45</Width>
+          </ThumbnailImage>
+          <TinyImage>
+            <URL>http://ecx.images-amazon.com/images/I/51UL7NX3-6L._SL110_.jpg</URL>
+            <Height Units="pixels">110</Height>
+            <Width Units="pixels">67</Width>
+          </TinyImage>
+          <MediumImage>
+            <URL>http://ecx.images-amazon.com/images/I/51UL7NX3-6L._SL160_.jpg</URL>
+            <Height Units="pixels">160</Height>
+            <Width Units="pixels">97</Width>
+          </MediumImage>
+          <LargeImage>
+            <URL>http://ecx.images-amazon.com/images/I/51UL7NX3-6L.jpg</URL>
+            <Height Units="pixels">500</Height>
+            <Width Units="pixels">303</Width>
+          </LargeImage>
+        </ImageSet>
+      </ImageSets>
+      <ItemAttributes>
+        <Author>David Flanagan</Author>
+        <Binding>Paperback</Binding>
+        <EAN>9781449316853</EAN>
+        <EANList>
+          <EANListElement>9781449316853</EANListElement>
+        </EANList>
+        <Edition>3</Edition>
+        <ISBN>1449316859</ISBN>
+        <IsEligibleForTradeIn>1</IsEligibleForTradeIn>
+        <ItemDimensions>
+          <Height Units="hundredths-inches">701</Height>
+          <Length Units="hundredths-inches">425</Length>
+          <Weight Units="hundredths-pounds">44</Weight>
+          <Width Units="hundredths-inches">59</Width>
+        </ItemDimensions>
+        <Label>O'Reilly Media</Label>
+        <Languages>
+          <Language>
+            <Name>English</Name>
+            <Type>Unknown</Type>
+          </Language>
+          <Language>
+            <Name>English</Name>
+            <Type>Original Language</Type>
+          </Language>
+          <Language>
+            <Name>English</Name>
+            <Type>Published</Type>
+          </Language>
+        </Languages>
+        <ListPrice>
+          <Amount>999</Amount>
+          <CurrencyCode>GBP</CurrencyCode>
+          <FormattedPrice>&#xA3;9.99</FormattedPrice>
+        </ListPrice>
+        <Manufacturer>O'Reilly Media</Manufacturer>
+        <NumberOfItems>1</NumberOfItems>
+        <NumberOfPages>288</NumberOfPages>
+        <PackageDimensions>
+          <Height Units="hundredths-inches">79</Height>
+          <Length Units="hundredths-inches">709</Length>
+          <Weight Units="hundredths-pounds">44</Weight>
+          <Width Units="hundredths-inches">453</Width>
+        </PackageDimensions>
+        <ProductGroup>Book</ProductGroup>
+        <ProductTypeName>ABIS_BOOK</ProductTypeName>
+        <PublicationDate>2012-04-25</PublicationDate>
+        <Publisher>O'Reilly Media</Publisher>
+        <SKU>ACOUK_book_usedgood_1449316859</SKU>
+        <Studio>O'Reilly Media</Studio>
+        <Title>JavaScript Pocket Reference (Pocket Reference (O'Reilly))</Title>
+        <TradeInValue>
+          <Amount>52</Amount>
+          <CurrencyCode>GBP</CurrencyCode>
+          <FormattedPrice>&#xA3;0.52</FormattedPrice>
+        </TradeInValue>
+      </ItemAttributes>
+    </Item>
+  </Items>
+</ItemLookupResponse>
diff --git a/discident-glib/discident-ean-amz-glib.c b/discident-glib/discident-ean-amz-glib.c
new file mode 100644
index 0000000..4a36c7b
--- /dev/null
+++ b/discident-glib/discident-ean-amz-glib.c
@@ -0,0 +1,408 @@
+/*
+   Copyright (C) 2010 Bastien Nocera
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301  USA.
+
+   Authors: Bastien Nocera <hadess hadess net>
+
+ */
+
+#include <string.h>
+
+#include <libsoup/soup.h>
+#include <glib/gprintf.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#include "discident-ean-glib.h"
+#include "discident-ean-amz-glib.h"
+#include "discident-error.h"
+
+#define RESPONSE_NAMESPACE "http://webservices.amazon.com/AWSECommerceService/2011-08-01";
+
+/* FIXME: Ability to lookup more than DVD? Automatically look up Books for ISBN numbers? UPC support? */
+
+#define SEARCH_URL "https://webservices.amazon.%s/onca/xml?";
+
+#define COMMON_PARAMETERS                                      \
+"AWSAccessKeyId=%s&"                                           \
+"AssociateTag=%s&"                                             \
+"IdType=EAN&"                                                  \
+"ItemId=%s&"                                                   \
+"Operation=ItemLookup&"                                                \
+"ResponseGroup=ItemAttributes,Images&"                         \
+"SearchIndex=DVD&"                                             \
+"Service=AWSECommerceService"
+
+static char *
+get_timestamp (void)
+{
+       GTimeVal tv;
+       char *timestamp;
+       char *ts_escaped;
+
+       g_get_current_time (&tv);
+       /* Amazon doesn't want sub-second information */
+       tv.tv_usec = 0;
+       timestamp = g_time_val_to_iso8601 (&tv);
+       ts_escaped = soup_uri_encode (timestamp, NULL);
+       g_free (timestamp);
+
+       return ts_escaped;
+}
+
+static int
+byte_sort (gconstpointer a,
+          gconstpointer b)
+{
+       return memcmp (a, b, MIN (strlen (a), strlen (b)));
+}
+
+/* orig will be freed */
+static char *
+replace (char       *orig,
+        const char *string,
+        const char *replacement,
+        gint  start)
+{
+       GRegex *regex;
+       char *escaped;
+
+       regex = g_regex_new (string, 0, 0, NULL);
+       escaped = g_regex_replace (regex, orig, -1, start, replacement, 0, NULL);
+       g_free (orig);
+       g_regex_unref (regex);
+
+       return escaped;
+}
+
+static char *
+flatten_list (GList *list)
+{
+       GString *string;
+       GList *l;
+
+       string = g_string_new (NULL);
+       for (l = list; l != NULL; l = l->next) {
+               if (l->prev != NULL)
+                       g_string_append_c (string, '&');
+               g_string_append (string, l->data);
+       }
+       return g_string_free (string, FALSE);
+}
+
+#define VERIFY(x, num) if (steps != NULL && steps[num -1] != NULL) g_assert_cmpstr (x, ==, steps[num - 1])
+
+char *
+_sign_query (const char  *url,
+            const char  *timestamp,
+            const char  *private_key,
+            const char **steps)
+{
+       char *with_time, **params, *sorted, *final, *to_sign, *encoded_sig, *sig;
+       SoupURI *uri;
+       guint i;
+       GList *list;
+       GHmac *hmac;
+       guint8 buffer[256];
+       gsize len;
+
+       with_time = g_strdup_printf ("%s&Timestamp=%s", url, timestamp);
+       VERIFY (with_time, 1);
+
+       /* http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/rest-signature.html
+        * step 2 */
+       with_time = replace (with_time, ",", "%2C", 0);
+       with_time = replace (with_time, ":", "%3A", strstr (with_time, "?") - with_time);
+       VERIFY(with_time, 2);
+
+       /* http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/rest-signature.html
+        * step 3 */
+       uri = soup_uri_new (with_time);
+       params = g_strsplit (soup_uri_get_query (uri), "&", -1);
+       list = NULL;
+       for (i = 0; params[i] != NULL; i++)
+               list = g_list_prepend (list, params[i]);
+       g_free (params);
+
+       /* http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/rest-signature.html
+        * step 4 */
+       list = g_list_sort (list, byte_sort);
+
+       /* http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/rest-signature.html
+        * step 5 */
+       sorted = flatten_list (list);
+
+       VERIFY (sorted, 5);
+
+       /* http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/rest-signature.html
+        * step 6, 7 */
+       to_sign = g_strdup_printf ("GET\n%s\n/onca/xml\n%s", soup_uri_get_host (uri), sorted);
+       g_free (sorted);
+
+       VERIFY (to_sign, 7);
+
+       /* http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/rest-signature.html
+        * step 8 */
+       hmac = g_hmac_new (G_CHECKSUM_SHA256, (guchar *) private_key, strlen (private_key));
+       g_hmac_update (hmac, (const guchar *) to_sign, -1);
+       g_free (to_sign);
+       len = sizeof(buffer);
+       g_hmac_get_digest (hmac, buffer, &len);
+       g_hmac_unref (hmac);
+
+       sig = g_base64_encode (buffer, len);
+       VERIFY (sig, 8);
+
+       /* http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/rest-signature.html
+        * step 9 */
+
+       encoded_sig = soup_uri_encode (sig, NULL);
+       g_free (sig);
+       encoded_sig = replace (encoded_sig, "\\+", "%2B", 0);
+       VERIFY (encoded_sig, 9);
+
+       /* http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/rest-signature.html
+        * step 10 */
+
+       sig = g_strdup_printf ("Signature=%s", encoded_sig);
+       list = g_list_prepend (list, sig);
+       list = g_list_sort (list, byte_sort);
+       sorted = flatten_list (list);
+       g_list_free_full (list, g_free);
+
+       final = g_strdup_printf ("%s://%s%s?%s",
+                                soup_uri_get_scheme (uri),
+                                soup_uri_get_host (uri),
+                                soup_uri_get_path (uri),
+                                sorted);
+       g_free (sorted);
+       soup_uri_free (uri);
+
+       VERIFY (final, 10);
+
+       return final;
+}
+
+static char *
+get_search_uri (DiscidentEan *ean,
+               const char   *barcode)
+{
+       char *uri, *timestamp, *ret;
+
+       uri = g_strdup_printf (SEARCH_URL COMMON_PARAMETERS,
+                              ean->priv->service + strlen ("amz-"),
+                              ean->priv->access_key,
+                              ean->priv->associate_tag, barcode);
+       timestamp = get_timestamp ();
+
+       ret = _sign_query (uri, timestamp, ean->priv->private_key, NULL);
+       g_free (uri);
+       g_free (timestamp);
+
+       return ret;
+}
+
+static char *
+get_value_for_xpath (xmlDocPtr           doc,
+                    xmlXPathContextPtr  xpath_ctx,
+                    const char         *path)
+{
+       xmlXPathObjectPtr xpath_obj;
+       xmlNodePtr cur;
+       char *ret;
+
+       xpath_obj = xmlXPathEvalExpression (BAD_CAST (path), xpath_ctx);
+       if (xpath_obj == NULL)
+               return NULL;
+       if (xpath_obj->nodesetval == NULL) {
+               xmlXPathFreeObject (xpath_obj);
+               return NULL;
+       }
+       cur = xpath_obj->nodesetval->nodeTab[0];
+       ret = g_strdup ((const char *) xmlNodeListGetString (doc, cur->xmlChildrenNode, 1));
+       xmlXPathFreeObject (xpath_obj);
+
+       return ret;
+}
+
+gboolean
+_amz_parse_lookup_response (const char  *response,
+                           char       **ret_title,
+                           char       **ret_img_url)
+{
+       xmlDocPtr doc;
+       xmlXPathContextPtr xpath_ctx;
+
+       if (response == NULL)
+               return FALSE;
+
+       doc = xmlParseMemory (response, strlen (response));
+       if (doc == NULL)
+               doc = xmlRecoverMemory (response, strlen (response));
+
+       if(!doc ||
+          !doc->children ||
+          !doc->children->name ||
+          g_ascii_strcasecmp ((char *)doc->children->name, "ItemLookupResponse") != 0) {
+               if (doc != NULL)
+                       xmlFreeDoc (doc);
+               return FALSE;
+       }
+
+       xpath_ctx = xmlXPathNewContext(doc);
+
+       /* Register namespace */
+       xmlXPathRegisterNs (xpath_ctx, BAD_CAST ("ns"), BAD_CAST (RESPONSE_NAMESPACE));
+
+       *ret_title = get_value_for_xpath (doc, xpath_ctx, "//ns:Title");
+       if (*ret_title == NULL)
+               goto bail;
+
+       *ret_img_url = get_value_for_xpath (doc, xpath_ctx, "//ns:Item/ns:LargeImage/ns:URL");
+
+bail:
+       xmlXPathFreeContext(xpath_ctx);
+       xmlFreeDoc (doc);
+
+       return TRUE;
+}
+
+static SoupMessage *
+create_query_message (DiscidentEan *ean,
+                     const char   *barcode)
+{
+       SoupMessage *msg;
+       char *uri;
+
+       uri = get_search_uri (ean, barcode);
+       msg = soup_message_new ("GET", uri);
+       g_free (uri);
+
+       return msg;
+}
+
+gboolean
+discident_ean_amz_lookup_sync (DiscidentEan *ean,
+                             const char   *barcode,
+                             char        **title,
+                             char        **img_url,
+                             GError      **error)
+{
+       SoupSession *session;
+       SoupMessage *msg;
+       char *response;
+       int ret;
+
+       g_return_val_if_fail (DISCIDENT_IS_EAN (ean), FALSE);
+       g_return_val_if_fail (title != NULL, FALSE);
+
+       session = soup_session_new ();
+
+       msg = create_query_message (ean, barcode);
+
+       response = NULL;
+
+       ret = soup_session_send_message (session, msg);
+       if (SOUP_STATUS_IS_SUCCESSFUL (ret) &&
+           msg->response_body != NULL) {
+               response = g_strdup (msg->response_body->data);
+       }
+
+       g_object_unref (msg);
+       g_object_unref (session);
+
+       if (response == NULL) {
+               g_set_error (error, SOUP_HTTP_ERROR, ret, "Could not query EAN service");
+               return FALSE;
+       }
+
+       if (_amz_parse_lookup_response (response, title, img_url) == FALSE) {
+               g_free (response);
+               g_set_error (error, DISCIDENT_ERROR, DISCIDENT_ERROR_PARSE, "Failed to parse response from 
EAN service");
+               return FALSE;
+       }
+
+       if (g_strcmp0 (*title, barcode) == 0) {
+               g_free (response);
+               g_set_error (error, DISCIDENT_ERROR, DISCIDENT_ERROR_EMPTY_RESPONSE, "No response for 
requested barcode");
+               return FALSE;
+       }
+
+       g_free (response);
+
+       return TRUE;
+}
+
+static void
+got_body_query (SoupMessage *msg,
+               QueryData   *data)
+{
+       const char *barcode;
+       char *title = NULL;
+       char *img_url = NULL;
+
+       if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code) ||
+           msg->response_body == NULL) {
+               g_simple_async_result_set_error (data->simple,
+                                                SOUP_HTTP_ERROR,
+                                                msg->status_code,
+                                                "Could not query EAN service: %s",
+                                                soup_status_get_phrase (msg->status_code));
+               goto out;
+       }
+
+       if (_amz_parse_lookup_response (msg->response_body->data, &title, &img_url) == FALSE) {
+               g_simple_async_result_set_error (data->simple,
+                                                DISCIDENT_ERROR,
+                                                DISCIDENT_ERROR_PARSE,
+                                                "Failed to parse response from EAN service");
+               goto out;
+       }
+
+       barcode = g_object_get_data (G_OBJECT (data->simple), "barcode");
+       if (g_strcmp0 (title, barcode) == 0) {
+               g_simple_async_result_set_error (data->simple,
+                                                DISCIDENT_ERROR,
+                                                DISCIDENT_ERROR_EMPTY_RESPONSE,
+                                                "No response for requested barcode");
+               g_free (title);
+               g_free (img_url);
+               goto out;
+       }
+
+       g_object_set_data_full (G_OBJECT (data->simple), "image-url", img_url, g_free);
+       g_simple_async_result_set_op_res_gpointer (data->simple, title, NULL);
+
+out:
+       g_simple_async_result_complete_in_idle (data->simple);
+       free_query_data (data);
+}
+
+void
+discident_ean_amz_lookup (DiscidentEan        *ean,
+                        SoupSession         *session,
+                        QueryData           *data)
+{
+       SoupMessage *msg;
+
+       msg = create_query_message (ean, data->barcode);
+       g_signal_connect (G_OBJECT (msg), "got-body",
+                         G_CALLBACK (got_body_query), data);
+       soup_session_queue_message (session, msg, NULL, NULL);
+}
diff --git a/discident-glib/discident-ean-amz-glib.h b/discident-glib/discident-ean-amz-glib.h
new file mode 100644
index 0000000..fde9ca1
--- /dev/null
+++ b/discident-glib/discident-ean-amz-glib.h
@@ -0,0 +1,43 @@
+/*
+   Copyright (C) 2010 Bastien Nocera
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301  USA.
+
+   Authors: Bastien Nocera <hadess hadess net>
+
+ */
+
+#ifndef DISCIDENT_EAN_AMZ_GLIB_H
+#define DISCIDENT_EAN_AMZ_GLIB_H
+
+#include <glib.h>
+#include "discident-ean-private-glib.h"
+
+G_BEGIN_DECLS
+
+gboolean discident_ean_amz_lookup_sync (DiscidentEan *ean,
+                                       const char   *barcode,
+                                       char        **title,
+                                       char        **img_url,
+                                       GError      **error);
+
+void discident_ean_amz_lookup (DiscidentEan        *ean,
+                              SoupSession         *session,
+                              QueryData           *data);
+
+G_END_DECLS
+
+#endif /* DISCIDENT_EAN_AMZ_GLIB_H */
diff --git a/discident-glib/discident-ean-glib.c b/discident-glib/discident-ean-glib.c
index ce97d47..61419a5 100644
--- a/discident-glib/discident-ean-glib.c
+++ b/discident-glib/discident-ean-glib.c
@@ -29,6 +29,7 @@
 
 #include "discident-ean-glib.h"
 #include "discident-error.h"
+#include "discident-ean-amz-glib.h"
 #include "discident-ean-rl-glib.h"
 #include "discident-ean-private-glib.h"
 
@@ -48,10 +49,18 @@ enum {
        PROP_PARAMETERS
 };
 
+/* FIXME Add more countries:
+ * http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/AnatomyOfaRESTRequest.html
+ */
 const char *rl_services[] = {
        "rl-en_US",
        "rl-en_GB",
-       "rl-fr_FR"
+       "rl-fr_FR",
+       "amz-co.uk",
+       "amz-de",
+       "amz-es",
+       "amz-fr",
+       "amz-it"
 };
 
 static gboolean
@@ -66,6 +75,19 @@ service_is_valid (const char *service)
        return FALSE;
 }
 
+static gboolean
+parameters_are_valid (DiscidentEan  *ean,
+                     GError       **error)
+{
+       if (ean->priv->access_key != NULL &&
+           ean->priv->private_key != NULL &&
+           ean->priv->associate_tag != NULL)
+               return TRUE;
+
+       g_set_error (error, DISCIDENT_ERROR, DISCIDENT_ERROR_MISSING_PARAMETERS, "Parameters are missing to 
access this web service");
+       return FALSE;
+}
+
 static void
 discident_ean_set_parameters (DiscidentEan *ean,
                              GHashTable   *parameters)
@@ -194,11 +216,23 @@ discident_ean_lookup_sync (DiscidentEan *ean,
                           char        **img_url,
                           GError      **error)
 {
-       return discident_ean_rl_lookup_sync (ean,
-                                            barcode,
-                                            title,
-                                            img_url,
-                                            error);
+       if (g_str_has_prefix (ean->priv->service, "rl-")) {
+               return discident_ean_rl_lookup_sync (ean,
+                                                    barcode,
+                                                    title,
+                                                    img_url,
+                                                    error);
+       } else if (g_str_has_prefix (ean->priv->service, "amz-")) {
+               if (!parameters_are_valid (ean, error))
+                       return FALSE;
+               return discident_ean_amz_lookup_sync (ean,
+                                                     barcode,
+                                                     title,
+                                                     img_url,
+                                                     error);
+       }
+
+       g_assert_not_reached ();
 }
 
 void
@@ -234,6 +268,7 @@ discident_ean_lookup (DiscidentEan        *ean,
        GSimpleAsyncResult *simple;
        SoupSession *session;
        QueryData *data;
+       GError *error = NULL;
 
        simple = g_simple_async_result_new (G_OBJECT (ean),
                                            callback,
@@ -249,7 +284,16 @@ discident_ean_lookup (DiscidentEan        *ean,
        data->barcode = g_strdup (barcode);
        data->simple = simple;
 
-       discident_ean_rl_lookup (ean, session, data);
+       if (g_str_has_prefix (ean->priv->service, "rl-")) {
+               discident_ean_rl_lookup (ean, session, data);
+       } else if (g_str_has_prefix (ean->priv->service, "amz-")) {
+               if (!parameters_are_valid (ean, &error)) {
+                       g_simple_async_result_take_error (simple, error);
+                       g_simple_async_result_complete_in_idle (simple);
+                       return;
+               }
+               discident_ean_amz_lookup (ean, session, data);
+       }
 }
 
 /**
@@ -326,6 +370,8 @@ discident_ean_lookup_get_barcode (DiscidentEan  *ean,
 DiscidentEan *
 discident_ean_new (const char *service)
 {
+       g_return_val_if_fail (!service || service_is_valid (service), NULL);
+
        if (service)
                return g_object_new (DISCIDENT_TYPE_EAN, "service", service, NULL);
 
diff --git a/discident-glib/discident-ean-private-glib.h b/discident-glib/discident-ean-private-glib.h
index e0320e5..f2a386f 100644
--- a/discident-glib/discident-ean-private-glib.h
+++ b/discident-glib/discident-ean-private-glib.h
@@ -23,6 +23,8 @@
 #ifndef DISCIDENT_EAN_PRIVATE_GLIB_H
 #define DISCIDENT_EAN_PRIVATE_GLIB_H
 
+#include <libsoup/soup.h>
+
 struct DiscidentEanPrivate {
        char *service;
 
@@ -47,9 +49,16 @@ typedef struct {
 } QueryData;
 
 void free_query_data (QueryData *data);
+char *_sign_query (const char  *url,
+                  const char  *timestamp,
+                  const char  *private_key,
+                  const char **steps);
 
 gboolean _rl_parse_lookup_response (const char  *response,
                                    char       **ret_title,
                                    char       **ret_img_url);
+gboolean _amz_parse_lookup_response (const char  *response,
+                                    char       **ret_title,
+                                    char       **ret_img_url);
 
 #endif /* DISCIDENT_EAN_PRIVATE_GLIB_H */
diff --git a/discident-glib/discident-glib.symbols b/discident-glib/discident-glib.symbols
index 140646b..f2e93b5 100644
--- a/discident-glib/discident-glib.symbols
+++ b/discident-glib/discident-glib.symbols
@@ -13,5 +13,7 @@ discident_ean_lookup_sync
 discident_ean_lookup
 discident_ean_lookup_get_barcode
 discident_ean_lookup_finish
+_sign_query
 _rl_parse_lookup_response
+_amz_parse_lookup_response
 discident_error_quark
diff --git a/discident-glib/test-diglib.c b/discident-glib/test-diglib.c
index 0dbcb41..eda0cc4 100644
--- a/discident-glib/test-diglib.c
+++ b/discident-glib/test-diglib.c
@@ -8,6 +8,7 @@
 #include <discident-glib.h>
 #include <discident-ean-glib.h>
 #include <discident-glib-private.h>
+#include <discident-ean-private-glib.h>
 
 static gboolean option_async = FALSE;
 static gboolean option_file_list = FALSE;
@@ -101,6 +102,105 @@ test_rl_parse (void)
 }
 
 static void
+test_amz_parse (void)
+{
+       char *response;
+       gboolean ret;
+       char *img_url, *title;
+
+       g_file_get_contents (SRCDIR "/amz-response.xml",
+                            &response,
+                            NULL,
+                            NULL);
+       ret = _amz_parse_lookup_response (response,
+                                        &title,
+                                        &img_url);
+       g_assert (ret);
+       g_assert_cmpstr (title, ==, "JavaScript Pocket Reference (Pocket Reference (O'Reilly))");
+       g_assert_cmpstr (img_url, ==, "http://ecx.images-amazon.com/images/I/51UL7NX3-6L.jpg";);
+}
+
+static void
+test_amz1 (void)
+{
+       char *ret;
+       const char *steps[] = {
+               
"http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes,Offers,Images,Reviews&Version=2009-01-06&Timestamp=2009-01-01T12:00:00Z";,
+               
"http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Version=2009-01-06&Timestamp=2009-01-01T12%3A00%3A00Z";,
+               NULL,
+               NULL,
+               
"AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&ItemId=0679722769&Operation=ItemLookup&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Service=AWSECommerceService&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-06",
+               NULL,
+               
"GET\nwebservices.amazon.com\n/onca/xml\nAWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&ItemId=0679722769&Operation=ItemLookup&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Service=AWSECommerceService&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-06",
+               "M/y0+EAFFGaUAp4bWv/WEuXYah99pVsxvqtAuC8YN7I=",
+               "M%2Fy0%2BEAFFGaUAp4bWv%2FWEuXYah99pVsxvqtAuC8YN7I%3D",
+               /* Note: this has been modified to reorder the parameters as
+                * per other tests */
+               
"http://webservices.amazon.com/onca/xml?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&ItemId=0679722769&Operation=ItemLookup&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Service=AWSECommerceService&Signature=M%2Fy0%2BEAFFGaUAp4bWv%2FWEuXYah99pVsxvqtAuC8YN7I%3D&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-06";
+       };
+
+       ret = _sign_query 
("http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes,Offers,Images,Reviews&Version=2009-01-06";,
 "2009-01-01T12:00:00Z", "1234567890", steps);
+       g_free (ret);
+}
+
+static void
+test_amz2 (void)
+{
+       char *ret;
+       const char *steps[10];
+       memset (steps, 0, sizeof(steps));
+       steps[6] = 
"GET\necs.amazonaws.co.uk\n/onca/xml\nAWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Actor=Johnny%20Depp&AssociateTag=mytag-20&Operation=ItemSearch&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews%2CVariations&SearchIndex=DVD&Service=AWSECommerceService&Sort=salesrank&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-01";
+//     steps[9] = 
"http://ecs.amazonaws.co.uk/onca/xml?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Actor=Johnny%20Depp&AssociateTag=mytag-20&Operation=ItemSearch&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews%2CVariations&SearchIndex=DVD&Service=AWSECommerceService&Signature=TuM6E5L9u%2FuNqOX09ET03BXVmHLVFfJIna5cxXuHxiU%3D&Sort=salesrank&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-01";;
+
+       ret = _sign_query 
("http://ecs.amazonaws.co.uk/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Operation=ItemSearch&Actor=Johnny%20Depp&ResponseGroup=ItemAttributes,Offers,Images,Reviews,Variations&Version=2009-01-01&SearchIndex=DVD&Sort=salesrank&AssociateTag=mytag-20";,
 "2009-01-01T12:00:00Z", "1234567890", steps);
+       g_free (ret);
+}
+
+static void
+test_amz3 (void)
+{
+       char *ret;
+       const char *steps[10];
+       memset (steps, 0, sizeof(steps));
+       steps[6] = 
"GET\nwebservices.amazon.com\n/onca/xml\nAWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&AssociateTag=mytag-20&Item.1.OfferListingId=j8ejq9wxDfSYWf2OCp6XQGDsVrWhl08GSQ9m5j%2Be8MS449BN1XGUC3DfU5Zw4nt%2FFBt87cspLow1QXzfvZpvzg%3D%3D&Item.1.Quantity=3&Operation=CartCreate&Service=AWSECommerceService&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-01";
+//     steps[9] = 
"http://webservices.amazon.com/onca/xml?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&AssociateTag=mytag-20&Item.1.OfferListingId=j8ejq9wxDfSYWf2OCp6XQGDsVrWhl08GSQ9m5j%2Be8MS449BN1XGUC3DfU5Zw4nt%2FFBt87cspLow1QXzfvZpvzg%3D%3D&Item.1.Quantity=3&Operation=CartCreate&Service=AWSECommerceService&Signature=cF3UtjbJb1%2BxDh387C%2FEmS1BCtS%2FZ01taykBCGemvUU%3D&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-01";;
+
+       ret = _sign_query 
("http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Operation=CartCreate&Version=2009-01-01&Item.1.OfferListingId=j8ejq9wxDfSYWf2OCp6XQGDsVrWhl08GSQ9m5j%2Be8MS449BN1XGUC3DfU5Zw4nt%2FFBt87cspLow1QXzfvZpvzg%3D%3D&Item.1.Quantity=3&AssociateTag=mytag-20";,
 "2009-01-01T12:00:00Z", "1234567890", steps);
+       g_free (ret);
+}
+
+static void
+test_amz4 (void)
+{
+       char *ret;
+       const char *steps[10];
+       memset (steps, 0, sizeof(steps));
+       steps[6] = 
"GET\nwebservices.amazon.com\n/onca/xml\nAWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&AssociateTag=mytag-20&BrowseNodeId=465600&Operation=BrowseNodeLookup&ResponseGroup=BrowseNodeInfo%2CTopSellers%2CNewReleases%2CMostWishedFor%2CMostGifted&Service=AWSECommerceService&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-01";
+//     steps[9] = 
"http://webservices.amazon.com/onca/xml?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&AssociateTag=mytag-20&BrowseNodeId=465600&Operation=BrowseNodeLookup&ResponseGroup=BrowseNodeInfo%2CTopSellers%2CNewReleases%2CMostWishedFor%2CMostGifted&Service=AWSECommerceService&Signature=kEXxAIqhh6eBhLhrVMz2gt3ocMaH%2FOBVPbjvc9TG8ao%3D&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-01";;
+
+       ret = _sign_query 
("http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Operation=BrowseNodeLookup&Version=2009-01-01&BrowseNodeId=465600&AssociateTag=mytag-20&ResponseGroup=BrowseNodeInfo,TopSellers,NewReleases,MostWishedFor,MostGifted";,
 "2009-01-01T12:00:00Z", "1234567890", steps);
+       g_free (ret);
+}
+
+static void
+test_amz5 (void)
+{
+       /* This is completely busted,
+        * there's a ";" that somebody typoed into
+        * the GET request example */
+#if 0
+       char *ret;
+       const char *steps[10];
+       memset (steps, 0, sizeof(steps));
+       steps[6] = 
"GET\nwebservices.amazon.com\n/onca/xml\nAWSAccessKeyId=AKIAIOSFODNN7EXAMPLE;&AssociateTag=mytag-20&Condition=New&ItemId=B0011ZK6PC%2CB000NK8EWI&Merchant=Amazon&Operation=SimilarityLookup&ResponseGroup=Offers%2CItemAttributes&Service=AWSECommerceService&SimilarityType=Intersection&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-01";
+       steps[9] = 
"http://webservices.amazon.com/onca/xml?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&AssociateTag=mytag-20&Condition=New&ItemId=B0011ZK6PC%2CB000NK8EWI&Merchant=Amazon&Operation=SimilarityLookup&ResponseGroup=Offers%2CItemAttributes&Service=AWSECommerceService&Signature=I2pbqxuS%2FmZK6Apwz0oLBxJn2wDL5n4kFQhgYWgLM7I%3D&SimilarityType=Intersection&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-01";;
+
+       ret = _sign_query 
("http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Operation=SimilarityLookup&ItemId=B0011ZK6PC,B000NK8EWI&Version=2009-01-01&AssociateTag=mytag-20&ResponseGroup=Offers,ItemAttributes&SimilarityType=Intersection&Condition=New&Merchant=Amazon";,
 "2009-01-01T12:00:00Z", "1234567890", steps);
+       g_free (ret);
+#endif
+}
+
+static void
 test_ean (void)
 {
        DiscidentEan *ean;
@@ -347,6 +447,7 @@ int main (int argc, char **argv)
                g_test_add_func ("/discident/ean-amz-browsenodelookup", test_amz4);
                g_test_add_func ("/discident/ean-amz-similaritylookup", test_amz5);
                g_test_add_func ("/discident/rl-parse", test_rl_parse);
+               g_test_add_func ("/discident/amz-parse", test_amz_parse);
                g_test_add_func ("/discident/ean", test_ean);
                g_test_add_func ("/discident/ean_fail", test_ean_fail);
                g_test_add_func ("/discident/hash", test_hash);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]