2
* Copyright (C) 2011 Canonical Ltd
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License version 3 as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authored by Alex Launi <alex.launi@canonical.com>
22
namespace Unity.MusicLens {
24
public class MusicStoreCollection : Object
27
private const string MUSICSTORE_BASE_URI = "http://musicsearch.ubuntu.com/v1/";
28
private const uint PURCHASE_CATEGORY = 2;
29
private HashTable<string, string> preview_uri_map; //maps u1ms store uri to details uri used by preview request
31
public MusicStoreCollection ()
33
preview_uri_map = new HashTable<string, string>(str_hash, str_equal);
36
public async void search (LensSearch search, SearchType search_type,
37
owned List<FilterParser> filters, int max_results = -1, Cancellable cancellable) throws IOError
39
string? uri = build_search_uri (search.search_string, filters);
44
preview_uri_map.remove_all ();
45
var results_model = search.results_model;
46
File file = File.new_for_uri (uri);
48
yield read_musicstore_search_result_async (file, results_model, cancellable);
51
public void get_album_details (string uri, out Album album, out SList<Track> tracks)
53
string http_uri = uri.substring (7); // strip off "u1ms://" from the uri
54
if (preview_uri_map.contains (http_uri))
56
string details_uri = preview_uri_map.get (http_uri);
57
debug ("details uri: %s", details_uri);
59
var file = File.new_for_uri (details_uri);
60
var parser = new Json.Parser ();
63
if (parser.load_from_stream (file.read (null))) //FIXME: make async
65
var root_obj = parser.get_root ().get_object ();
68
tracks = new SList<Track> ();
69
album.title = root_obj.get_string_member ("title");
70
album.artwork_path = root_obj.get_string_member ("image");
71
album.artist = root_obj.get_string_member ("artist");
74
if (root_obj.has_member ("tracks"))
76
var tracks_node = root_obj.get_array_member ("tracks");
77
debug ("Album details: '%s', '%s'", uri, details_uri);
78
foreach (var track_node in tracks_node.get_elements ())
80
var track_obj = track_node.get_object ();
81
var track = new Track ();
82
track.uri = track_obj.get_string_member ("preview");
83
track.title = track_obj.get_string_member ("title");
84
track.duration = (int)track_obj.get_member ("duration").get_int ();
85
tracks.append (track);
88
else // details for single track
90
debug ("Single track details: '%s', '%s'", uri, details_uri);
91
var track = new Track ();
92
track.uri = root_obj.get_string_member ("preview");
93
track.title = root_obj.get_string_member ("title");
94
track.duration = (int)root_obj.get_member ("duration").get_int ();
95
tracks.append (track);
100
warning ("Can't parse json data for '%s'", details_uri);
105
warning ("Error fetching details for '%s': %s", details_uri, e.message);
110
warning ("No details uri for '%s'", http_uri);
114
private async void read_musicstore_search_result_async (File file, Dee.Model model, Cancellable cancellable)
116
var timer = new Timer ();
117
debug ("Searching %s", file.get_uri ());
120
var stream = yield file.read_async (Priority.DEFAULT, cancellable);
121
var dis = new DataInputStream (stream);
122
var parser = new Json.Parser ();
123
yield parser.load_from_stream_async (dis, cancellable);
124
var root_object = parser.get_root ().get_object ();
126
Json.Object? results = root_object.get_object_member ("results");
127
if (results == null) {
128
warning ("Invalid response from server. No 'results' member.");
132
var albums = results.get_array_member ("album").get_elements ();
133
var tracks = results.get_array_member ("track").get_elements ();
135
debug ("Got %u albums and %u tracks", albums.length (), tracks.length ());
137
unowned string? uri, details_uri, artwork_path, mimetype, title, artist, dnd_uri;
139
foreach (var album_node in albums) {
140
var album_obj = album_node.get_object ();
142
uri = album_obj.get_string_member ("purchase_url");
143
details_uri = album_obj.get_string_member ("details");
144
artwork_path = album_obj.get_string_member ("image");
145
mimetype = "audio-x-generic";
146
title = album_obj.get_string_member ("title");
147
artist = album_obj.get_string_member ("artist");
150
model.append (uri, artwork_path, PURCHASE_CATEGORY,
151
mimetype, title, artist, dnd_uri);
153
preview_uri_map.insert (uri.substring (7), details_uri); // strip off "u1ms://" from the uri
156
foreach (var track_node in tracks) {
157
var track_obj = track_node.get_object ();
159
uri = track_obj.get_string_member ("purchase_url");
160
details_uri = track_obj.get_string_member ("details");
161
artwork_path = track_obj.get_string_member ("image");
162
mimetype = "audio-x-generic";
163
title = track_obj.get_string_member ("title");
164
artist = track_obj.get_string_member ("artist");
167
// FIXME drag n drop uri needs to be the u1ms:// link
169
model.append (uri, artwork_path, PURCHASE_CATEGORY,
170
mimetype, title, artist, dnd_uri);
171
preview_uri_map.insert (uri.substring (7), details_uri); // strip off "u1ms://" from the uri
174
debug ("Retrieved '%s' in %fms", file.get_uri (), timer.elapsed()*1000);
175
debug ("Model has %u rows after search", model.get_n_rows ());
178
warning ("Error reading URL '%s': %s", file.get_uri (), e.message);
182
private string? build_search_uri (string query, List<FilterParser> filters)
184
if (query.strip() == "")
187
MusicStoreFilterParser store_parser;
188
string musicstore_base_uri = MUSICSTORE_BASE_URI;
189
if (GLib.Environment.get_variable("MUSICSTORE_URI") != null)
190
musicstore_base_uri = GLib.Environment.get_variable("MUSICSTORE_URI");
191
debug ("Using base URI of '%s'", musicstore_base_uri);
192
StringBuilder uri = new StringBuilder (musicstore_base_uri);
193
uri.append ("search?q=");
194
uri.append (Uri.escape_string (query, "", false));
196
foreach (FilterParser parser in filters) {
197
if (parser is GenreFilterParser)
198
store_parser = new MusicStoreGenreFilterParser (parser as GenreFilterParser);
199
else if (parser is DecadeFilterParser)
200
store_parser = new MusicStoreDecadeFilterParser (parser as DecadeFilterParser);
204
uri.append (store_parser.parse ());
207
uri.append ("&pagesize=10");
209
// This makes the service return $pagesize results *per content type*.
210
// Which we need - as it could otherwise return $pagesize results mixed
211
// or artist,album, and track. Since we can't display artists, this can
212
// lead to an empty result set in the dash if there is only artists in
213
// the response from the webservice
214
uri.append ("&grouping=1");