diff -u unity-lens-shopping-6.0.0/debian/changelog unity-lens-shopping-6.0.0/debian/changelog --- unity-lens-shopping-6.0.0/debian/changelog +++ unity-lens-shopping-6.0.0/debian/changelog @@ -1,9 +1,15 @@ -unity-lens-shopping (6.0.0-0ubuntu1.1) quantal-proposed; urgency=low +unity-lens-shopping (6.0.0-0ubuntu2) quantal; urgency=low - * No change rebuild to get launchpad translations to pick it up since - the source didn't get an upload in main yet + [ Ɓukasz 'sil2100' Zemczak ] + * debian/control: + - Added build-dependencies to libsoup2.4-dev and libsoup-gnome2.4-dev, as + needed by the addition of secure connections + + [ Iain Lane ] + * Cherry-pick upstream r22 to connect to the remote server using SSL (LP: + #1055649) - -- Sebastien Bacher Thu, 27 Sep 2012 15:59:19 +0200 + -- Iain Lane Fri, 28 Sep 2012 17:20:17 +0100 unity-lens-shopping (6.0.0-0ubuntu1) quantal-proposed; urgency=low diff -u unity-lens-shopping-6.0.0/debian/control unity-lens-shopping-6.0.0/debian/control --- unity-lens-shopping-6.0.0/debian/control +++ unity-lens-shopping-6.0.0/debian/control @@ -12,6 +12,8 @@ libjson-glib-dev, libdee-dev (>= 1.0.7), libunity-dev (>= 5.93), + libsoup2.4-dev, + libsoup-gnome2.4-dev, Standards-Version: 3.9.3 Homepage: https://launchpad.net/unity-lens-shopping Vcs-Bzr: https://code.launchpad.net/+branch/ubuntu/unity-lens-shopping only in patch2: unchanged: --- unity-lens-shopping-6.0.0.orig/configure.ac +++ unity-lens-shopping-6.0.0/configure.ac @@ -62,6 +62,7 @@ gio-unix-2.0 >= $GLIB_REQUIRED dee-1.0 >= 1.0.7 gee-1.0 + libsoup-gnome-2.4 json-glib-1.0 unity >= 5.93.0) @@ -118,6 +119,7 @@ data/Makefile src/Makefile src/config.vala + vapi/Makefile po/Makefile.in ]) AC_OUTPUT only in patch2: unchanged: --- unity-lens-shopping-6.0.0.orig/src/scope.vala +++ unity-lens-shopping-6.0.0/src/scope.vala @@ -23,10 +23,12 @@ { public class ShoppingScope : Unity.Scope { - private const string OFFERS_BASE_URI = "http://productsearch.ubuntu.com"; + private const string OFFERS_BASE_URI = "https://productsearch.ubuntu.com"; private HashTable results_details_map; private HashTable global_results_details_map; + private Soup.Session session; + private PreviewPlayerHandler player; public ShoppingScope () { @@ -35,6 +37,12 @@ protected override void constructed () { + session = new Soup.SessionAsync (); + session.ssl_use_system_ca_file = true; + session.ssl_strict = true; + session.user_agent = "Unity Shopping Lens " + Config.VERSION; + session.add_feature_by_type (typeof (SoupGNOME.ProxyResolverGNOME)); + /* Listen for filter changes */ filters_changed.connect (() => { queue_search_changed (SearchType.DEFAULT); @@ -93,6 +101,7 @@ process_search_reply_json (parser, Category.TREAT_YOURSELF, search.results_model); } + catch (IOError.CANCELLED canc_err) { /* ignore */ } catch (Error err) { warning ("Error: %s", err.message); @@ -121,26 +130,40 @@ } } - private Preview? generate_preview_for_uri (string uri) + private async Preview? process_preview_request (string details_uri) { - string? details_uri = - global_results_details_map[uri] ?? results_details_map[uri]; - - if (details_uri == null) + Json.Parser parser; + try { - return new GenericPreview (Path.get_basename (uri), - "No data available", null); + parser = yield get_json_reply_async (details_uri, null); } - - try + catch (Error err) { - var parser = get_json_reply (details_uri, null); + return new GenericPreview (Path.get_basename (details_uri), + err.message, null); + } + if (U1MSPreviewFactory.is_u1ms_details (parser)) + { + var u1mspf = new U1MSPreviewFactory (); + var preview = u1mspf.create_preview (parser); + u1mspf.add_download_action (preview); // download will be handled by normal activation + if (player == null) + player = new PreviewPlayerHandler (); + player.music_preview = preview; + return preview; + } + else + { var root = parser.get_root ().get_object (); unowned string title = root.get_string_member ("title"); unowned string description = root.get_string_member ("description_html"); - unowned string price = root.get_string_member ("formatted_price"); - if (price == null) price = root.get_string_member ("price"); + + unowned string price = null; + if (root.has_member ("formatted_price")) + price = root.get_string_member ("formatted_price"); + else if (root.has_member ("price")) + price = root.get_string_member ("price"); var img_obj = root.get_object_member ("images"); string image_uri = extract_image_uri (img_obj, int.MAX); @@ -161,10 +184,26 @@ preview.add_action (buy_action); return preview; } - catch (Error err) + } + + private Preview? generate_preview_for_uri (string uri) + { + string? details_uri = + global_results_details_map[uri] ?? results_details_map[uri]; + + if (details_uri == null) { - return new GenericPreview (Path.get_basename (uri), err.message, null); + return new GenericPreview (Path.get_basename (uri), + "No data available", null); } + + var preview = new AsyncPreview (); + process_preview_request.begin (details_uri, (obj, res) => + { + var real_preview = process_preview_request.end (res); + preview.preview_ready (real_preview); + }); + return preview; } private string build_search_uri (string query, SearchType search_type) @@ -185,29 +224,55 @@ } private async Json.Parser get_json_reply_async (string uri, - Cancellable cancellable) + Cancellable? cancellable) throws Error { message ("Sending request: %s", uri); - var file = File.new_for_uri (uri); - var stream = yield file.read_async (Priority.DEFAULT, cancellable); - var parser = new Json.Parser (); - yield parser.load_from_stream_async (stream); + var msg = new Soup.Message ("GET", uri); + session.queue_message (msg, (session_, msg_) => + { + msg = msg_; + get_json_reply_async.callback (); + }); - return parser; - } + var cancelled = false; + ulong cancel_id = 0; + if (cancellable != null) + { + cancel_id = cancellable.connect (() => + { + cancelled = true; + session.cancel_message (msg, Soup.KnownStatusCode.CANCELLED); + }); + } - private Json.Parser get_json_reply (string uri, - Cancellable? cancellable) - throws Error - { - message ("Sending sync request: %s", uri); + yield; + + if (cancelled) + { + // we can't disconnect right away, as that would deadlock (cause + // cancel_message doesn't return before invoking the callback) + Idle.add (get_json_reply_async.callback); + yield; + cancellable.disconnect (cancel_id); + throw new IOError.CANCELLED ("Cancelled"); + } + + if (msg.status_code < 100) + { + throw new IOError.FAILED ("Request failed with error %u", msg.status_code); + } + else if (msg.status_code != 200) + { + warning ("Request returned status code %u", msg.status_code); + } + + if (msg.response_body.data == null) + throw new IOError.FAILED ("Request didn't return any content"); - var file = File.new_for_uri (uri); - var stream = file.read (cancellable); var parser = new Json.Parser (); - parser.load_from_stream (stream); + parser.load_from_data ((string) msg.response_body.data); return parser; } @@ -294,8 +359,12 @@ var image_obj = result.get_object_member ("images"); string image_uri = extract_image_uri (image_obj, 128*128); - unowned string price = result.get_string_member ("formatted_price"); - if (price == null) price = result.get_string_member ("price"); + unowned string price = null; + if (result.has_member ("formatted_price")) + price = result.get_string_member ("formatted_price"); + else if (result.has_member ("price")) + price = result.get_string_member ("price"); + if (image_uri != "") { // TODO: what to do if we have price but no icon? only in patch2: unchanged: --- unity-lens-shopping-6.0.0.orig/src/Makefile.am +++ unity-lens-shopping-6.0.0/src/Makefile.am @@ -28,7 +28,10 @@ --pkg gio-unix-2.0 \ --pkg glib-2.0 \ --pkg json-glib-1.0 \ + --pkg libsoup-2.4 \ --vapidir $(srcdir) \ + --vapidir $(top_srcdir)/vapi \ + --pkg libsoup-gnome-2.4 \ --target-glib=2.26 \ $(MAINTAINER_VALAFLAGS) \ $(NULL) @@ -44,6 +47,8 @@ main.vala \ scope.vala \ markup-cleaner.vala \ + preview-player-client.vala \ + u1ms-preview.vala \ $(NULL) unity_shopping_daemon_SOURCES = \ only in patch2: unchanged: --- unity-lens-shopping-6.0.0.orig/src/preview-player-client.vala +++ unity-lens-shopping-6.0.0/src/preview-player-client.vala @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * 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 . + * + * Authored by Pawel Stolowski + */ + + +/* + * FIXME: this code was copied from music-lens; it needs to be kept in sync and eventually moved to a library !!! + */ +namespace Unity.ShoppingLens { + + static const string PREVIEW_PLAYER_DBUS_NAME = "com.canonical.Unity.Lens.Music.PreviewPlayer"; + static const string PREVIEW_PLAYER_DBUS_PATH = "/com/canonical/Unity/Lens/Music/PreviewPlayer"; + + [DBus (name = "com.canonical.Unity.Lens.Music.PreviewPlayer")] + public interface PreviewPlayerService: GLib.Object + { + public signal void progress(string uri, uint32 state, double progress); + + public abstract async void play (string uri) throws Error; + public abstract async void pause () throws Error; + public abstract async void pause_resume () throws Error; + public abstract async void resume () throws Error; + public abstract async void stop () throws Error; + public abstract async void close () throws Error; + } + + public class PreviewPlayer: GLib.Object + { + public signal void progress(string uri, Unity.MusicPreview.TrackState state, double progress); + + public async void connect_to () throws Error + { + _preview_player_service = Bus.get_proxy_sync (BusType.SESSION, PREVIEW_PLAYER_DBUS_NAME, PREVIEW_PLAYER_DBUS_PATH); + _preview_player_service.progress.connect (on_progress_signal); + } + + public async void play (string uri) throws Error + { + if (_preview_player_service == null) + { + yield connect_to (); + } + yield _preview_player_service.play (uri); + } + + public async void pause () throws Error + { + if (_preview_player_service == null) + { + yield connect_to (); + } + yield _preview_player_service.pause (); + } + + public async void pause_resume () throws Error + { + if (_preview_player_service == null) + { + yield connect_to (); + } + yield _preview_player_service.pause_resume (); + } + + public async void resume () throws Error + { + if (_preview_player_service == null) + { + yield connect_to (); + } + yield _preview_player_service.resume (); + } + + public async void stop () throws Error + { + if (_preview_player_service == null) + { + yield connect_to (); + } + yield _preview_player_service.stop (); + } + + public async void close () throws Error + { + if (_preview_player_service == null) + { + yield connect_to (); + } + yield _preview_player_service.close (); + } + + internal void on_progress_signal (string uri, uint32 state, double progress_value) + { + progress(uri, (Unity.MusicPreview.TrackState)state, progress_value); + } + + private PreviewPlayerService _preview_player_service; + } +} \ No newline at end of file only in patch2: unchanged: --- unity-lens-shopping-6.0.0.orig/src/u1ms-preview.vala +++ unity-lens-shopping-6.0.0/src/u1ms-preview.vala @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2011 Canonical, Ltd. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * version 3.0 as published by the Free Software Foundation. + * + * This 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 Lesser General Public License version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Authored by Pawel Stolowski + * + */ + +/* + * FIXME: this code is based on musicstore-scope preview handling code; it needs to be kept in sync and eventually moved to a library !!! + */ +namespace Unity.ShoppingLens +{ + + public class PreviewPlayerHandler + { + private PreviewPlayer preview_player; + private Unity.MusicPreview music_preview_; + + public Unity.MusicPreview music_preview + { + get + { + return music_preview_; + } + set + { + music_preview_ = value; + if (value != null) + { + music_preview.play.connect (play); + music_preview.pause.connect (pause); + music_preview.closed.connect (closed); + } + } + } + + public PreviewPlayerHandler (Unity.MusicPreview? preview = null) + { + music_preview = preview; + } + + private void on_progress_changed (string uri, Unity.MusicPreview.TrackState state, double progress) + { + if (music_preview != null) + { + music_preview.current_track_uri = uri; + music_preview.current_track_state = state; + music_preview.current_progress = (float)progress; + } + } + + private void closed (Unity.Preview preview) + { + if (preview_player != null) + { + try + { + preview_player.close (); + } + catch (Error e) + { + warning ("Failed to close preview player: %s", e.message); + } + } + } + + private void play (Unity.Preview preview, string uri) + { + debug ("play request: '%s'", uri); + + try + { + if (preview_player == null) + { + preview_player = new PreviewPlayer (); + preview_player.progress.connect (on_progress_changed); + preview_player.connect_to (); + } + + // we will receive state back in on_progress_changed, but set it now so that it's immediately reflected in the dash + music_preview.current_track_uri = uri; + music_preview.current_progress = 0.0f; + music_preview.current_track_state = Unity.MusicPreview.TrackState.PLAYING; + + preview_player.play (uri); + } + catch (Error e) + { + warning ("Failed to play '%s': %s", uri, e.message); + } + } + + public void pause (Unity.Preview preview, string uri) + { + debug ("pause request: '%s'", uri); + + try + { + if (preview_player != null) + { + // we will receive state back in on_progress_changed, but set it now so that it's immediately reflected in the dash + music_preview.current_track_uri = uri; + music_preview.current_track_state = Unity.MusicPreview.TrackState.PAUSED; + + preview_player.pause (); + } + } + catch (Error e) + { + warning ("Failed to pause '%s': %s", uri, e.message); + } + } + } + + + public class U1MSPreviewFactory + { + public string formatted_price { get; internal set; } + + public static bool is_u1ms_details (Json.Parser parser) + { + var root_obj = parser.get_root ().get_object (); + return root_obj.has_member ("source") && root_obj.get_string_member ("source") == "Ubuntu One Music Store" && root_obj.has_member ("tracks"); + } + + public Unity.MusicPreview? create_preview (Json.Parser parser) + { + var root_obj = parser.get_root ().get_object (); + + var title = root_obj.get_string_member ("title"); + var artwork_path = root_obj.get_string_member ("image"); + File cover_file = File.new_for_uri (artwork_path); //artwork path is a remote uri + var cover = new FileIcon (cover_file); + + var artist = root_obj.get_string_member ("artist"); + + var preview = new Unity.MusicPreview (title, artist, cover); + + if (root_obj.has_member ("formatted_price")) + formatted_price = root_obj.get_string_member ("formatted_price"); + else + formatted_price = ""; + + if (root_obj.has_member ("tracks")) + { + var tracks_node = root_obj.get_array_member ("tracks"); + int i = 1; + foreach (var track_node in tracks_node.get_elements ()) + { + var track_obj = track_node.get_object (); + TrackMetadata tm = new TrackMetadata (); + tm.uri = track_obj.get_string_member ("preview"); + tm.track_no = i++; //FIXME: u1ms search doesn't provide track numbers *yet* + tm.title = track_obj.get_string_member ("title"); + tm.length = (int)track_obj.get_member ("duration").get_int (); + preview.add_track (tm); + } + } + else // details for single track + { + TrackMetadata tm = new TrackMetadata (); + tm.uri = root_obj.get_string_member ("preview"); + tm.title = root_obj.get_string_member ("title"); + tm.length = (int)root_obj.get_member ("duration").get_int (); + preview.add_track (tm); + } + return preview; + } + + public Unity.PreviewAction? add_download_action (Unity.MusicPreview preview) + { + GLib.Icon? icon = new GLib.FileIcon (File.new_for_path (Config.DATADIR + "/icons/unity-icon-theme/places/svg/service-u1.svg")); + var download_action = new Unity.PreviewAction ("download_album", _("Download"), icon); + if (formatted_price != null) + download_action.extra_text = formatted_price; + preview.add_action (download_action); + return download_action; + } + } + +} only in patch2: unchanged: --- unity-lens-shopping-6.0.0.orig/vapi/libsoup-gnome-2.4.vapi +++ unity-lens-shopping-6.0.0/vapi/libsoup-gnome-2.4.vapi @@ -0,0 +1,8 @@ +[CCode (cprefix = "Soup", gir_namespace = "SoupGNOME", gir_version = "2.4", lower_case_cprefix = "soup_")] +namespace SoupGNOME { + [CCode (cheader_filename = "libsoup/soup-gnome.h", type_id = "soup_proxy_resolver_gnome_get_type ()")] + public class ProxyResolverGNOME : Soup.ProxyResolverDefault, Soup.ProxyURIResolver, Soup.SessionFeature { + [CCode (has_construct_function = false)] + protected ProxyResolverGNOME (); + } +} only in patch2: unchanged: --- unity-lens-shopping-6.0.0.orig/vapi/Makefile.am +++ unity-lens-shopping-6.0.0/vapi/Makefile.am @@ -0,0 +1,5 @@ +NULL = +BUILT_SOURCES = +CLEANFILES = +EXTRA_DIST = libsoup-gnome-2.4.vapi + only in patch2: unchanged: --- unity-lens-shopping-6.0.0.orig/po/POTFILES.in +++ unity-lens-shopping-6.0.0/po/POTFILES.in @@ -1,5 +1,7 @@ [encoding: UTF-8] src/daemon.vala src/main.vala +src/u1ms-preview.vala +src/scope.vala [type: gettext/ini]shopping.lens.in.in only in patch2: unchanged: --- unity-lens-shopping-6.0.0.orig/po/POTFILES.skip +++ unity-lens-shopping-6.0.0/po/POTFILES.skip @@ -5,3 +5,5 @@ src/banshee-scope.c src/rhythmbox-scope.c src/scope.c +src/preview-player-client.c +src/u1ms-preview.c