~ricmm/+junk/unity-lens_music-sc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
 * Copyright (C) 2011 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 <http://www.gnu.org/licenses/>.
 *
 * Authored by Alex Launi <alex.launi@canonical.com>
 *
 */

using GLib;

namespace Unity.MusicLens {

  public class MusicStoreCollection : Object
  {

    private const string MUSICSTORE_BASE_URI = "http://musicsearch.ubuntu.com/v1/";
    private const uint PURCHASE_CATEGORY = 2;
    private HashTable<string, string> preview_uri_map; //maps u1ms store uri to details uri used by preview request

    public MusicStoreCollection ()
    {
      preview_uri_map = new HashTable<string, string>(str_hash, str_equal);
    }

    public async void search (LensSearch search, SearchType search_type,
			owned List<FilterParser> filters, int max_results = -1, Cancellable cancellable) throws IOError
    {
      string? uri = build_search_uri (search.search_string, filters);

      if (uri == null)
        return;	  

      preview_uri_map.remove_all ();
      var results_model = search.results_model;
      File file = File.new_for_uri (uri);

      yield read_musicstore_search_result_async (file, results_model, cancellable);
    }

    public void get_album_details (string uri, out Album album, out SList<Track> tracks)
    {
      string http_uri = uri.substring (7); // strip off "u1ms://" from the uri
      if (preview_uri_map.contains (http_uri))
      {
        string details_uri = preview_uri_map.get (http_uri);
        debug ("details uri: %s", details_uri);

        var file = File.new_for_uri (details_uri);
        var parser = new Json.Parser ();

        try {
          if (parser.load_from_stream (file.read (null))) //FIXME: make async
          {
            var root_obj = parser.get_root ().get_object ();

            album = new Album ();
            tracks = new SList<Track> ();
            album.title = root_obj.get_string_member ("title");
            album.artwork_path = root_obj.get_string_member ("image");
            album.artist = root_obj.get_string_member ("artist");
            album.uri = http_uri;

            if (root_obj.has_member ("tracks"))
            {
              var tracks_node = root_obj.get_array_member ("tracks");
              debug ("Album details: '%s', '%s'", uri, details_uri);
              foreach (var track_node in tracks_node.get_elements ())
              {
                var track_obj = track_node.get_object ();
                var track = new Track ();
                track.uri = track_obj.get_string_member ("preview");
                track.title = track_obj.get_string_member ("title");
                track.duration = (int)track_obj.get_member ("duration").get_int ();
                tracks.append (track);
              }
            }
            else // details for single track
            {
              debug ("Single track details: '%s', '%s'", uri, details_uri);
              var track = new Track ();
              track.uri = root_obj.get_string_member ("preview");
              track.title = root_obj.get_string_member ("title");
              track.duration = (int)root_obj.get_member ("duration").get_int ();
              tracks.append (track);
            }
          }
          else
          {
            warning ("Can't parse json data for '%s'", details_uri);
          }
        }
        catch (Error e)
        {
          warning ("Error fetching details for '%s': %s", details_uri, e.message);
        }
      }
      else
      {
        warning ("No details uri for '%s'", http_uri);
      }
    }

    private async void read_musicstore_search_result_async (File file, Dee.Model model, Cancellable cancellable)
    {
      var timer = new Timer ();
      debug ("Searching %s", file.get_uri ());

      try {
        var stream = yield file.read_async (Priority.DEFAULT, cancellable);
	var dis = new DataInputStream (stream);
	var parser = new Json.Parser ();
	yield parser.load_from_stream_async (dis, cancellable);
	var root_object = parser.get_root ().get_object ();
	
	Json.Object? results = root_object.get_object_member ("results");
	if (results == null) {
	  warning ("Invalid response from server. No 'results' member.");
	  return;
	}
	
	var albums = results.get_array_member ("album").get_elements ();
	var tracks = results.get_array_member ("track").get_elements ();
	
	debug ("Got %u albums and %u tracks", albums.length (), tracks.length ());
	
	unowned string? uri, details_uri, artwork_path, mimetype, title, artist, dnd_uri;
	
	foreach (var album_node in albums) {
	  var album_obj = album_node.get_object ();
	  
	  uri = album_obj.get_string_member ("purchase_url");
	  details_uri = album_obj.get_string_member ("details");
	  artwork_path = album_obj.get_string_member ("image");
	  mimetype = "audio-x-generic";
	  title = album_obj.get_string_member ("title");
	  artist = album_obj.get_string_member ("artist");
	  dnd_uri = uri;
	  
	  model.append (uri, artwork_path, PURCHASE_CATEGORY,
	                mimetype, title, artist, dnd_uri);

      preview_uri_map.insert (uri.substring (7), details_uri); // strip off "u1ms://" from the uri
    }
        
	foreach (var track_node in tracks) {
	  var track_obj = track_node.get_object ();
	  
	  uri = track_obj.get_string_member ("purchase_url");
	  details_uri = track_obj.get_string_member ("details");
	  artwork_path = track_obj.get_string_member ("image");
	  mimetype = "audio-x-generic";
	  title = track_obj.get_string_member ("title");
	  artist = track_obj.get_string_member ("artist");
	  dnd_uri = uri;
          
	  // FIXME drag n drop uri needs to be the u1ms:// link
	  
	  model.append (uri, artwork_path, PURCHASE_CATEGORY,
	                mimetype, title, artist, dnd_uri);
      preview_uri_map.insert (uri.substring (7), details_uri); // strip off "u1ms://" from the uri
    }

	debug ("Retrieved '%s' in %fms", file.get_uri (), timer.elapsed()*1000);
	debug ("Model has %u rows after search", model.get_n_rows ());

      } catch (Error e) {
	warning ("Error reading URL '%s': %s", file.get_uri (), e.message);
      }
    }

    private string? build_search_uri (string query, List<FilterParser> filters)
    {
      if (query.strip() == "")
        return null;
    
      MusicStoreFilterParser store_parser;
      string musicstore_base_uri = MUSICSTORE_BASE_URI;
      if (GLib.Environment.get_variable("MUSICSTORE_URI") != null)
        musicstore_base_uri = GLib.Environment.get_variable("MUSICSTORE_URI");
      debug ("Using base URI of '%s'", musicstore_base_uri);
      StringBuilder uri = new StringBuilder (musicstore_base_uri);
      uri.append ("search?q=");
      uri.append (Uri.escape_string (query, "", false));

      foreach (FilterParser parser in filters) {
	  if (parser is GenreFilterParser)
	    store_parser = new MusicStoreGenreFilterParser (parser as GenreFilterParser);
	  else if (parser is DecadeFilterParser)
	    store_parser = new MusicStoreDecadeFilterParser (parser as DecadeFilterParser);
	  else
	    continue;

	  uri.append (store_parser.parse ());
      }
      
      uri.append ("&pagesize=10");
      
      // This makes the service return $pagesize results *per content type*.
      // Which we need - as it could otherwise return $pagesize results mixed
      // or artist,album, and track. Since we can't display artists, this can
      // lead to an empty result set in the dash if there is only artists in
      // the response from the webservice
      uri.append ("&grouping=1");

      return uri.str;
    }
  }
}