|
1 /* |
|
2 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: Music Player Query Manager. |
|
15 * |
|
16 */ |
|
17 |
|
18 #include "mpquerymanager.h" |
|
19 #include <QObject> |
|
20 #include <QNetworkAccessManager> |
|
21 #include <QNetworkDiskCache> |
|
22 #include <QNetworkProxyFactory> |
|
23 #include <qmobilityglobal.h> |
|
24 #include <QNetworkSession> |
|
25 #include <QDomElement> |
|
26 #include <QList> |
|
27 #include <QUrl> |
|
28 #include <QSslError> |
|
29 #include <XQSysInfo> |
|
30 #include <QSignalMapper> |
|
31 #include <QSettings> |
|
32 |
|
33 #include "mptrace.h" |
|
34 |
|
35 const int KRecommendationCount = 2; |
|
36 |
|
37 MpQueryManager::MpQueryManager() |
|
38 : mManager(0), |
|
39 mAlbumArtDownloader(0), |
|
40 mDefaultRecommendationAlbumArt("qtg_large_album_art"), |
|
41 mRequestType(NoRequest), |
|
42 mRecommendationCount(0) |
|
43 { |
|
44 TX_ENTRY |
|
45 mManager = new QNetworkAccessManager( this ); |
|
46 // A second intance is necessary to reduce complexity. |
|
47 // Otherwise, we would have to shoot async events when we want to receive inspire me items' album art |
|
48 // and that may not always work. |
|
49 mAlbumArtDownloader = new QNetworkAccessManager( this ); |
|
50 |
|
51 mDownloadSignalMapper = new QSignalMapper(this); |
|
52 TX_EXIT |
|
53 } |
|
54 |
|
55 MpQueryManager::~MpQueryManager() |
|
56 { |
|
57 TX_ENTRY |
|
58 reset(); |
|
59 if ( mManager ) { |
|
60 mManager->deleteLater(); |
|
61 } |
|
62 if ( mAlbumArtDownloader ) { |
|
63 mAlbumArtDownloader->deleteLater(); |
|
64 } |
|
65 delete mDownloadSignalMapper; |
|
66 TX_EXIT |
|
67 } |
|
68 |
|
69 |
|
70 void MpQueryManager::clearNetworkReplies() |
|
71 { |
|
72 mRequestType = NoRequest; |
|
73 disconnect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ) ); |
|
74 TX_ENTRY_ARGS( "Reply count = " << mReplys.count() ); |
|
75 for ( int i = 0; i < mReplys.count(); ++i ) { |
|
76 QNetworkReply *reply = mReplys.at( i ); |
|
77 disconnect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( retrieveInformationNetworkError( QNetworkReply::NetworkError ) ) ); |
|
78 if ( reply != NULL ) { |
|
79 reply->close(); |
|
80 reply->deleteLater(); |
|
81 reply = NULL; |
|
82 } |
|
83 } |
|
84 mReplys.clear(); |
|
85 TX_EXIT |
|
86 } |
|
87 |
|
88 |
|
89 void MpQueryManager::queryInspireMeItems(QString artist,QString album,QString title) |
|
90 { |
|
91 TX_ENTRY |
|
92 mArtist=artist; |
|
93 mAlbum=album; |
|
94 mTitle=title; |
|
95 // start querying inspire me items |
|
96 QString queryRecommendation("http://api.music.ovi.com/1.0/" + mMusicStore + "/releases/recommend/?"); |
|
97 constructRequest( queryRecommendation ); |
|
98 // TODO: Tokens change per new ovi api release. |
|
99 // Need to figure out a way to get them updated on the fly |
|
100 queryRecommendation.append("&Token=03574704-e3d1-4466-9691-e0b34c7abfff"); |
|
101 |
|
102 TX_LOG_ARGS( queryRecommendation ); |
|
103 retrieveInformation( queryRecommendation ); |
|
104 mRequestType = InspireMeItemsMetadataRequest; |
|
105 TX_EXIT |
|
106 } |
|
107 |
|
108 void MpQueryManager::clearRecommendations() |
|
109 { |
|
110 TX_ENTRY |
|
111 mRecommendationCount = 0; |
|
112 mDownloadedAlbumArts = 0; |
|
113 mAlbumArtsReadyCount = 0; |
|
114 mRecommendationSongs.clear(); |
|
115 mRecommendationArtists.clear(); |
|
116 mRecommendationAlbumArtsLink.clear(); |
|
117 mRecommendationAlbumArtsMap.clear(); |
|
118 TX_EXIT |
|
119 } |
|
120 |
|
121 |
|
122 |
|
123 /*! |
|
124 Returns the Local Music Store(if available) to be used while querying "Inspire Me" Items |
|
125 */ |
|
126 void MpQueryManager::queryLocalMusicStore() |
|
127 { |
|
128 TX_ENTRY |
|
129 QString imsi,mcc; |
|
130 |
|
131 XQSysInfo sysInfo( this ); |
|
132 imsi = sysInfo.imsi(); |
|
133 mcc= imsi.left(3); |
|
134 TX_LOG_ARGS( "mcc : " << mcc ); |
|
135 |
|
136 QString queryLocalMusicStore("http://api.music.cq1.brislabs.com/1.0/?mcc=" + mcc + "&token=06543e34-0261-40a4-a03a-9e09fe110c1f"); |
|
137 TX_LOG_ARGS( "queryLocalMusicStore : " << queryLocalMusicStore ); |
|
138 retrieveInformation( queryLocalMusicStore ); |
|
139 mRequestType = LocalStoreRequest; |
|
140 TX_EXIT |
|
141 } |
|
142 |
|
143 |
|
144 int MpQueryManager::recommendationsCount() const |
|
145 { |
|
146 TX_LOG_ARGS ("count: " << mRecommendationSongs.count()); |
|
147 return mRecommendationSongs.count(); |
|
148 } |
|
149 |
|
150 QString MpQueryManager::recommendedSong(int index) const |
|
151 { |
|
152 QString result; |
|
153 if( (0 <= index) && (index < mRecommendationSongs.count())) { |
|
154 result = mRecommendationSongs.at(index); |
|
155 } |
|
156 TX_LOG_ARGS ("recommendedSong: " << result); |
|
157 return result; |
|
158 } |
|
159 |
|
160 /*! |
|
161 Return recommendation artists |
|
162 */ |
|
163 QString MpQueryManager::recommendedArtist(int index) const |
|
164 { |
|
165 QString result; |
|
166 if( (0 <= index) && (index < mRecommendationArtists.count())) { |
|
167 result = mRecommendationArtists.at(index); |
|
168 } |
|
169 TX_LOG_ARGS ("recommendedArtist: " << result); |
|
170 return result; |
|
171 } |
|
172 |
|
173 /*! |
|
174 Slot to call when getting response |
|
175 */ |
|
176 void MpQueryManager::retrieveInformationFinished( QNetworkReply* reply ) |
|
177 { |
|
178 TX_ENTRY |
|
179 QString errorStr; |
|
180 int errorLine; |
|
181 int errorColumn; |
|
182 bool parsingSuccess; |
|
183 |
|
184 if ( reply->error() != QNetworkReply::NoError ) { |
|
185 TX_LOG_ARGS("reply->error(): " << reply->error()); |
|
186 signalError(); |
|
187 return; |
|
188 } |
|
189 |
|
190 parsingSuccess = mDomDocument.setContent( reply, true, &errorStr, &errorLine, &errorColumn ); |
|
191 if ( !parsingSuccess ) { |
|
192 TX_LOG_ARGS("Parsing Received Content Failed"); |
|
193 signalError(); |
|
194 return; |
|
195 } |
|
196 |
|
197 handleParsedXML(); //CodeScanner throws a warning mis-interpreting the trailing 'L' to be a leaving function. |
|
198 |
|
199 mReplys.removeAll(reply); // remove it so that we wont process it again |
|
200 reply->deleteLater(); // make sure reply is deleted, as we longer care about it |
|
201 |
|
202 TX_EXIT |
|
203 } |
|
204 |
|
205 void MpQueryManager::signalError() |
|
206 { |
|
207 TX_ENTRY; |
|
208 switch(mRequestType) { |
|
209 case InspireMeItemsMetadataRequest: |
|
210 TX_LOG_ARGS("emit inspireMeItemsRetrievalError"); |
|
211 emit inspireMeItemsRetrievalError(); |
|
212 break; |
|
213 case InspireMeItemsAlbumArtRequest: |
|
214 TX_LOG_ARGS("Warning: InspireMeItemsAlbumArtRequestError, will keep using the default AA icon"); |
|
215 break; |
|
216 case LocalStoreRequest: |
|
217 TX_LOG_ARGS("emit localMusicStoreRetrievalError"); |
|
218 emit localMusicStoreRetrievalError(); |
|
219 break; |
|
220 case NoRequest: |
|
221 default: |
|
222 TX_LOG_ARGS("Warning!! Possible uninitialized mRequestType"); |
|
223 break; |
|
224 } |
|
225 TX_EXIT |
|
226 } |
|
227 |
|
228 /*! |
|
229 Slot to call when there is network error |
|
230 */ |
|
231 void MpQueryManager::retrieveInformationNetworkError( QNetworkReply::NetworkError error ) |
|
232 { |
|
233 TX_ENTRY_ARGS( "Network error for retrieving Information" << error); |
|
234 |
|
235 Q_UNUSED(error) |
|
236 |
|
237 disconnect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ) ); |
|
238 signalError(); |
|
239 TX_EXIT |
|
240 } |
|
241 |
|
242 /*! |
|
243 Slot to call when there is ssl error |
|
244 */ |
|
245 void MpQueryManager::retrieveInformationSslErrors( const QList<QSslError> &/*error*/ ) |
|
246 { |
|
247 TX_ENTRY_ARGS( "SSL error for retrieving Information" ); |
|
248 disconnect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ) ); |
|
249 signalError(); |
|
250 TX_EXIT |
|
251 } |
|
252 |
|
253 /*! |
|
254 Slot to call when downloading finished |
|
255 */ |
|
256 void MpQueryManager::albumArtDownloaded( int index ) |
|
257 { |
|
258 TX_ENTRY_ARGS( "mDownloadedAlbumArts = " << mDownloadedAlbumArts << "index = " << index); |
|
259 |
|
260 QNetworkReply* reply = qobject_cast<QNetworkReply*> ( qobject_cast<QSignalMapper*>( sender() )->mapping( index ) ); |
|
261 // It seems we get several finished signals for the same reply obj |
|
262 // do nothing if we get a second signal |
|
263 if( mReplys.indexOf(reply) == -1 ) { |
|
264 TX_LOG_ARGS("Warning: QNetworkReply AA request may have been processed in previous call: " << reply ); |
|
265 return; |
|
266 } |
|
267 |
|
268 if ( reply->error() == QNetworkReply::NoError ) { |
|
269 QPixmap albumart; |
|
270 bool result = albumart.loadFromData( reply->readAll() ); |
|
271 if ( result ) { |
|
272 mRecommendationAlbumArtsMap.insert( mRecommendationAlbumArtsLink.at( index ), HbIcon( QIcon( albumart ) ) ); |
|
273 |
|
274 } else { |
|
275 mRecommendationAlbumArtsMap.insert( mRecommendationAlbumArtsLink.at( index ), mDefaultRecommendationAlbumArt ); |
|
276 } |
|
277 |
|
278 ++mDownloadedAlbumArts; |
|
279 mReplys.removeAll(reply); // remove it so that we wont process it again |
|
280 reply->deleteLater(); // make sure reply is deleted, as we longer care about it |
|
281 } |
|
282 else { |
|
283 TX_LOG_ARGS( "Error: Downloading album art failed! Will keep using the default AA" ); |
|
284 mRecommendationAlbumArtsMap.insert( mRecommendationAlbumArtsLink.at( index ), mDefaultRecommendationAlbumArt ); |
|
285 } |
|
286 mDownloadSignalMapper->removeMappings( reply ); |
|
287 |
|
288 if( mDownloadedAlbumArts == mRecommendationCount) { |
|
289 // no need to be informed anymore |
|
290 mDownloadSignalMapper->disconnect(this); |
|
291 emit inspireMeItemAlbumArtReady(); |
|
292 } |
|
293 |
|
294 TX_EXIT |
|
295 } |
|
296 |
|
297 |
|
298 /*! |
|
299 Get Atom response from Ovi server based on query |
|
300 */ |
|
301 void MpQueryManager::retrieveInformation( const QString &urlEncoded ) |
|
302 { |
|
303 TX_ENTRY_ARGS( "urlEconded = " << urlEncoded) |
|
304 connect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ), Qt::UniqueConnection ); |
|
305 QNetworkReply *reply = mManager->get( QNetworkRequest( QUrl( urlEncoded ) ) ); |
|
306 mReplys.append( reply ); |
|
307 |
|
308 connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( retrieveInformationNetworkError( QNetworkReply::NetworkError ) ) ); |
|
309 connect( reply, SIGNAL( sslErrors( QList<QSslError> ) ), this, SLOT( retrieveInformationSslErrors( QList<QSslError> ) ) ); |
|
310 TX_EXIT |
|
311 } |
|
312 |
|
313 |
|
314 /*! |
|
315 Find the most suitable link based on Atom response from Ovi music server |
|
316 */ |
|
317 void MpQueryManager::handleParsedXML() |
|
318 { |
|
319 TX_ENTRY |
|
320 QDomElement rootElement = mDomDocument.documentElement(); |
|
321 |
|
322 if ( rootElement.attribute( "type" ) == "recommendedTracks" ) { |
|
323 TX_LOG_ARGS( "Recommendation response" ) |
|
324 QDomElement entry = rootElement.firstChildElement( "entry" ); |
|
325 mRecommendationCount = 0; |
|
326 while ( !entry.isNull() && mRecommendationCount < KRecommendationCount ) { |
|
327 if ( entry.attribute( "type" ) == "musictrack" ) { |
|
328 QDomElement link = entry.firstChildElement( "link" ); |
|
329 while ( !link.isNull() ) { |
|
330 if ( link.attribute( "title" ) == "albumart100" ) { |
|
331 mRecommendationAlbumArtsLink.append( link.attribute( "href" ) ); |
|
332 mRecommendationAlbumArtsMap.insert( link.attribute( "href" ), mDefaultRecommendationAlbumArt ); |
|
333 break; |
|
334 } |
|
335 else { |
|
336 link = link.nextSiblingElement( "link" ); |
|
337 } |
|
338 } |
|
339 QDomElement metadata = entry.firstChildElement( "metadata" ); |
|
340 mRecommendationSongs.append( metadata.firstChildElement( "name" ).text() ); |
|
341 mRecommendationArtists.append( metadata.firstChildElement( "primaryartist" ).text() ); |
|
342 ++mRecommendationCount; |
|
343 } |
|
344 entry = entry.nextSiblingElement( "entry" ); |
|
345 } |
|
346 |
|
347 emit inspireMeItemsMetadataRetrieved(); |
|
348 |
|
349 QNetworkReply *reply = 0; |
|
350 // we need to channel the retrieved album arts to albumArtDownloaded slot only |
|
351 disconnect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ) ); |
|
352 for (int i = 0; i < mRecommendationCount; i++ ) { |
|
353 TX_LOG_ARGS( "song name: " << mRecommendationSongs.at(i) ); |
|
354 TX_LOG_ARGS( "Artist name: " << mRecommendationArtists.at(i) ); |
|
355 TX_LOG_ARGS( "Album art link: " << mRecommendationAlbumArtsLink.at(i) ); |
|
356 mRequestType = InspireMeItemsAlbumArtRequest; |
|
357 if ( mRecommendationAlbumArtsLink.at( i ).contains( "http", Qt::CaseInsensitive ) ) { |
|
358 reply = mAlbumArtDownloader->get( QNetworkRequest( QUrl( mRecommendationAlbumArtsLink.at(i) ) ) ); |
|
359 mReplys.append( reply ); |
|
360 connect( reply, SIGNAL( finished() ), mDownloadSignalMapper, SLOT( map() ) ); |
|
361 mDownloadSignalMapper->setMapping( reply, i ); |
|
362 |
|
363 connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( retrieveInformationNetworkError( QNetworkReply::NetworkError ) ) ); |
|
364 connect( reply, SIGNAL( sslErrors( QList<QSslError> ) ), this, SLOT( retrieveInformationSslErrors( QList<QSslError> ) ) ); |
|
365 } |
|
366 } |
|
367 // we have queried for album arts for inspire me items. Now, time to wait for a response |
|
368 connect( mDownloadSignalMapper, SIGNAL( mapped( int ) ), this, SLOT( albumArtDownloaded( int ) ) ); |
|
369 |
|
370 } |
|
371 else if ( rootElement.attribute( "type" ) == "storeList" ) { |
|
372 TX_LOG_ARGS( "Music Store List" ) |
|
373 QDomElement entry = rootElement.firstChildElement( "workspace" ); |
|
374 QString previousMusicStore = mMusicStore; |
|
375 mMusicStore = entry.attribute( "countryCode" ); |
|
376 if( !mMusicStore.isEmpty() ) { |
|
377 bool musicStoreUpdated = ( previousMusicStore != mMusicStore ); |
|
378 TX_LOG_ARGS("Music Store" << mMusicStore ); |
|
379 emit localMusicStoreRetrieved( musicStoreUpdated ); |
|
380 if( musicStoreUpdated ) { |
|
381 QSettings settings; |
|
382 TX_LOG_ARGS( "Storing music store value: " << mMusicStore ); |
|
383 settings.setValue( "LocalMusicStore", QVariant( mMusicStore ) ); |
|
384 } |
|
385 } |
|
386 else { |
|
387 emit localMusicStoreRetrievalError(); |
|
388 } |
|
389 } |
|
390 else { |
|
391 TX_LOG_ARGS( "Not supported response" ) |
|
392 } |
|
393 TX_EXIT |
|
394 } |
|
395 |
|
396 |
|
397 void MpQueryManager::reset() |
|
398 { |
|
399 TX_ENTRY |
|
400 mManager->disconnect(this); |
|
401 mAlbumArtDownloader->disconnect(this); |
|
402 clearNetworkReplies(); |
|
403 clearRecommendations(); |
|
404 mRecommendationAlbumArtsMap.clear(); |
|
405 TX_EXIT |
|
406 } |
|
407 |
|
408 /*! |
|
409 Construct the query for fetching URI & recommendations |
|
410 */ |
|
411 void MpQueryManager::constructRequest( QString &uri ) |
|
412 { |
|
413 TX_ENTRY_ARGS( "uri =" << uri) |
|
414 |
|
415 QStringList keys; |
|
416 keys << "artist" << "albumtitle" << "tracktitle" << "orderby"; |
|
417 |
|
418 // "relevancy" is the selected sort order |
|
419 // sort order types can be relevancy, alltimedownloads, streetreleasedate, sortname, recentdownloads |
|
420 QStringList values; |
|
421 values << mArtist << mAlbum << mTitle << QString("relevancy"); |
|
422 TX_LOG_ARGS( "Artist: " << mArtist ); |
|
423 TX_LOG_ARGS( "Album: " << mAlbum ); |
|
424 TX_LOG_ARGS( "Title: " << mTitle ); |
|
425 |
|
426 uri += keyValues( keys, values ); |
|
427 |
|
428 QUrl url(uri); |
|
429 uri = url.toEncoded(); |
|
430 TX_EXIT |
|
431 } |
|
432 |
|
433 /*! |
|
434 Make a key & value pair string for querying |
|
435 */ |
|
436 QString MpQueryManager::keyValues( QStringList keys, QStringList values ) const |
|
437 { |
|
438 TX_ENTRY |
|
439 QString str; |
|
440 if ( keys.length() != values.length() ) { |
|
441 TX_LOG_ARGS( "Error: keys length is not equal to values length" ); |
|
442 } |
|
443 else { |
|
444 for ( int i = 0; i < keys.length(); i++ ) { |
|
445 QString tValue = values.at( i ); |
|
446 if ( 0 != tValue.length() ) { |
|
447 str += keys.at( i ) + "=" + values.at( i ) + "&"; |
|
448 } |
|
449 } |
|
450 } |
|
451 TX_EXIT |
|
452 return str.left( str.length() - 1 ); |
|
453 } |
|
454 |
|
455 bool MpQueryManager::isLocalMusicStore() |
|
456 { |
|
457 if( mMusicStore.isEmpty() ) { |
|
458 QSettings settings; |
|
459 QVariant settingsvariant = settings.value( "LocalMusicStore", "" ); |
|
460 mMusicStore = settingsvariant.toString(); |
|
461 TX_LOG_ARGS( "Got local music store from settings:" << mMusicStore ); |
|
462 } |
|
463 TX_LOG_ARGS( "isLocalMusicStore = " << !mMusicStore.isEmpty() ) |
|
464 return !mMusicStore.isEmpty(); |
|
465 } |
|
466 |
|
467 HbIcon MpQueryManager::recommendedAlbumArt(int index) const |
|
468 { |
|
469 TX_LOG_ARGS( "index = " << index ) |
|
470 return mRecommendationAlbumArtsMap.value( mRecommendationAlbumArtsLink.at( index ) ); |
|
471 } |