~ubuntu-branches/ubuntu/saucy/clementine/saucy

« back to all changes in this revision

Viewing changes to src/internet/spotifyservice.cpp

  • Committer: Package Import Robot
  • Author(s): Thomas PIERSON
  • Date: 2012-01-01 20:43:39 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20120101204339-lsb6nndwhfy05sde
Tags: 1.0.1+dfsg-1
New upstream release. (Closes: #653926, #651611, #657391)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#include "config.h"
 
2
#include "internetmodel.h"
 
3
#include "spotifyblobdownloader.h"
 
4
#include "spotifyserver.h"
 
5
#include "spotifyservice.h"
 
6
#include "spotifysearchplaylisttype.h"
 
7
#include "core/database.h"
 
8
#include "core/logging.h"
 
9
#include "core/player.h"
 
10
#include "core/taskmanager.h"
 
11
#include "core/timeconstants.h"
 
12
#include "core/utilities.h"
 
13
#include "globalsearch/globalsearch.h"
 
14
#include "globalsearch/spotifysearchprovider.h"
 
15
#include "playlist/playlist.h"
 
16
#include "playlist/playlistcontainer.h"
 
17
#include "playlist/playlistmanager.h"
 
18
#include "spotifyblob/common/blobversion.h"
 
19
#include "spotifyblob/common/spotifymessagehandler.h"
 
20
#include "widgets/didyoumean.h"
 
21
#include "ui/iconloader.h"
 
22
 
 
23
#include <QCoreApplication>
 
24
#include <QFile>
 
25
#include <QFileInfo>
 
26
#include <QMenu>
 
27
#include <QMessageBox>
 
28
#include <QProcess>
 
29
#include <QSettings>
 
30
#include <QVariant>
 
31
 
 
32
Q_DECLARE_METATYPE(QStandardItem*);
 
33
 
 
34
const char* SpotifyService::kServiceName = "Spotify";
 
35
const char* SpotifyService::kSettingsGroup = "Spotify";
 
36
const char* SpotifyService::kBlobDownloadUrl = "http://spotify.clementine-player.org/";
 
37
const int SpotifyService::kSearchDelayMsec = 400;
 
38
 
 
39
SpotifyService::SpotifyService(InternetModel* parent)
 
40
    : InternetService(kServiceName, parent, parent),
 
41
      server_(NULL),
 
42
      blob_process_(NULL),
 
43
      root_(NULL),
 
44
      search_(NULL),
 
45
      starred_(NULL),
 
46
      inbox_(NULL),
 
47
      login_task_id_(0),
 
48
      pending_search_playlist_(NULL),
 
49
      context_menu_(NULL),
 
50
      search_delay_(new QTimer(this)),
 
51
      login_state_(LoginState_OtherError),
 
52
      bitrate_(spotify_pb::Bitrate320k),
 
53
      volume_normalisation_(false)
 
54
{
 
55
  // Build the search path for the binary blob.
 
56
  // Look for one distributed alongside clementine first, then check in the
 
57
  // user's home directory for any that have been downloaded.
 
58
#ifdef Q_OS_MAC
 
59
  system_blob_path_ = QCoreApplication::applicationDirPath() +
 
60
      "/../PlugIns/clementine-spotifyblob";
 
61
#else
 
62
  system_blob_path_ = QCoreApplication::applicationDirPath() +
 
63
      "/clementine-spotifyblob" CMAKE_EXECUTABLE_SUFFIX;
 
64
#endif
 
65
 
 
66
  local_blob_version_ = QString("version%1-%2bit").arg(SPOTIFY_BLOB_VERSION).arg(sizeof(void*) * 8);
 
67
  local_blob_path_    = Utilities::GetConfigPath(Utilities::Path_LocalSpotifyBlob) +
 
68
                        "/" + local_blob_version_ + "/blob";
 
69
 
 
70
  qLog(Debug) << "Spotify system blob path:" << system_blob_path_;
 
71
  qLog(Debug) << "Spotify local blob path:" << local_blob_path_;
 
72
 
 
73
  model()->player()->playlists()->RegisterSpecialPlaylistType(
 
74
        new SpotifySearchPlaylistType(this));
 
75
 
 
76
  model()->global_search()->AddProvider(new SpotifySearchProvider(this));
 
77
 
 
78
  search_delay_->setInterval(kSearchDelayMsec);
 
79
  search_delay_->setSingleShot(true);
 
80
  connect(search_delay_, SIGNAL(timeout()), SLOT(DoSearch()));
 
81
}
 
82
 
 
83
SpotifyService::~SpotifyService() {
 
84
  if (blob_process_ && blob_process_->state() == QProcess::Running) {
 
85
    qLog(Info) << "Terminating blob process...";
 
86
    blob_process_->terminate();
 
87
    blob_process_->waitForFinished(1000);
 
88
  }
 
89
}
 
90
 
 
91
QStandardItem* SpotifyService::CreateRootItem() {
 
92
  root_ = new QStandardItem(QIcon(":icons/22x22/spotify.png"), kServiceName);
 
93
  root_->setData(true, InternetModel::Role_CanLazyLoad);
 
94
  return root_;
 
95
}
 
96
 
 
97
void SpotifyService::LazyPopulate(QStandardItem* item) {
 
98
  switch (item->data(InternetModel::Role_Type).toInt()) {
 
99
    case InternetModel::Type_Service:
 
100
      EnsureServerCreated();
 
101
      break;
 
102
 
 
103
    case Type_SearchResults:
 
104
      break;
 
105
 
 
106
    case Type_InboxPlaylist:
 
107
      EnsureServerCreated();
 
108
      server_->LoadInbox();
 
109
      break;
 
110
 
 
111
    case Type_StarredPlaylist:
 
112
      EnsureServerCreated();
 
113
      server_->LoadStarred();
 
114
      break;
 
115
 
 
116
    case InternetModel::Type_UserPlaylist:
 
117
      EnsureServerCreated();
 
118
      server_->LoadUserPlaylist(item->data(Role_UserPlaylistIndex).toInt());
 
119
      break;
 
120
 
 
121
    default:
 
122
      break;
 
123
  }
 
124
 
 
125
  return;
 
126
}
 
127
 
 
128
QModelIndex SpotifyService::GetCurrentIndex() {
 
129
  return QModelIndex();
 
130
}
 
131
 
 
132
void SpotifyService::Login(const QString& username, const QString& password) {
 
133
  Logout();
 
134
  EnsureServerCreated(username, password);
 
135
}
 
136
 
 
137
void SpotifyService::LoginCompleted(bool success, const QString& error,
 
138
                                    spotify_pb::LoginResponse_Error error_code) {
 
139
  if (login_task_id_) {
 
140
    model()->task_manager()->SetTaskFinished(login_task_id_);
 
141
    login_task_id_ = 0;
 
142
  }
 
143
 
 
144
  if (!success) {
 
145
    bool show_error_dialog = true;
 
146
    QString error_copy(error);
 
147
 
 
148
    switch (error_code) {
 
149
    case spotify_pb::LoginResponse_Error_BadUsernameOrPassword:
 
150
      login_state_ = LoginState_BadCredentials;
 
151
      break;
 
152
 
 
153
    case spotify_pb::LoginResponse_Error_UserBanned:
 
154
      login_state_ = LoginState_Banned;
 
155
      break;
 
156
 
 
157
    case spotify_pb::LoginResponse_Error_UserNeedsPremium:
 
158
      login_state_ = LoginState_NoPremium;
 
159
      break;
 
160
 
 
161
    case spotify_pb::LoginResponse_Error_ReloginFailed:
 
162
      if (login_state_ == LoginState_LoggedIn) {
 
163
        // This is the first time the relogin has failed - show a message this
 
164
        // time only.
 
165
        error_copy = tr("You have been logged out of Spotify, please re-enter your password in the Settings dialog.");
 
166
      } else {
 
167
        show_error_dialog = false;
 
168
      }
 
169
 
 
170
      login_state_ = LoginState_ReloginFailed;
 
171
      break;
 
172
 
 
173
    default:
 
174
      login_state_ = LoginState_OtherError;
 
175
      break;
 
176
    }
 
177
 
 
178
    if (show_error_dialog) {
 
179
      QMessageBox::warning(NULL, tr("Spotify login error"), error_copy, QMessageBox::Close);
 
180
    }
 
181
  } else {
 
182
    login_state_ = LoginState_LoggedIn;
 
183
  }
 
184
 
 
185
  QSettings s;
 
186
  s.beginGroup(kSettingsGroup);
 
187
  s.setValue("login_state", login_state_);
 
188
 
 
189
  emit LoginFinished(success);
 
190
}
 
191
 
 
192
void SpotifyService::BlobProcessError(QProcess::ProcessError error) {
 
193
  qLog(Error) << "Spotify blob process failed:" << error;
 
194
  blob_process_->deleteLater();
 
195
  blob_process_ = NULL;
 
196
 
 
197
  if (login_task_id_) {
 
198
    model()->task_manager()->SetTaskFinished(login_task_id_);
 
199
  }
 
200
}
 
201
 
 
202
void SpotifyService::ReloadSettings() {
 
203
  QSettings s;
 
204
  s.beginGroup(kSettingsGroup);
 
205
 
 
206
  login_state_ = LoginState(s.value("login_state", LoginState_OtherError).toInt());
 
207
  bitrate_ = static_cast<spotify_pb::Bitrate>(
 
208
        s.value("bitrate", spotify_pb::Bitrate320k).toInt());
 
209
  volume_normalisation_ = s.value("volume_normalisation", false).toBool();
 
210
 
 
211
  if (server_ && blob_process_) {
 
212
    server_->SetPlaybackSettings(bitrate_, volume_normalisation_);
 
213
  }
 
214
}
 
215
 
 
216
void SpotifyService::EnsureServerCreated(const QString& username,
 
217
                                         const QString& password) {
 
218
  if (server_ && blob_process_) {
 
219
    return;
 
220
  }
 
221
 
 
222
  delete server_;
 
223
  server_ = new SpotifyServer(this);
 
224
 
 
225
  connect(server_, SIGNAL(LoginCompleted(bool,QString,spotify_pb::LoginResponse_Error)),
 
226
                   SLOT(LoginCompleted(bool,QString,spotify_pb::LoginResponse_Error)));
 
227
  connect(server_, SIGNAL(PlaylistsUpdated(spotify_pb::Playlists)),
 
228
          SLOT(PlaylistsUpdated(spotify_pb::Playlists)));
 
229
  connect(server_, SIGNAL(InboxLoaded(spotify_pb::LoadPlaylistResponse)),
 
230
          SLOT(InboxLoaded(spotify_pb::LoadPlaylistResponse)));
 
231
  connect(server_, SIGNAL(StarredLoaded(spotify_pb::LoadPlaylistResponse)),
 
232
          SLOT(StarredLoaded(spotify_pb::LoadPlaylistResponse)));
 
233
  connect(server_, SIGNAL(UserPlaylistLoaded(spotify_pb::LoadPlaylistResponse)),
 
234
          SLOT(UserPlaylistLoaded(spotify_pb::LoadPlaylistResponse)));
 
235
  connect(server_, SIGNAL(PlaybackError(QString)),
 
236
          SIGNAL(StreamError(QString)));
 
237
  connect(server_, SIGNAL(SearchResults(spotify_pb::SearchResponse)),
 
238
          SLOT(SearchResults(spotify_pb::SearchResponse)));
 
239
  connect(server_, SIGNAL(ImageLoaded(QString,QImage)),
 
240
          SIGNAL(ImageLoaded(QString,QImage)));
 
241
  connect(server_, SIGNAL(SyncPlaylistProgress(spotify_pb::SyncPlaylistProgress)),
 
242
          SLOT(SyncPlaylistProgress(spotify_pb::SyncPlaylistProgress)));
 
243
 
 
244
  server_->Init();
 
245
 
 
246
  login_task_id_ = model()->task_manager()->StartTask(tr("Connecting to Spotify"));
 
247
 
 
248
  QString login_username = username;
 
249
  QString login_password = password;
 
250
 
 
251
  if (username.isEmpty()) {
 
252
    QSettings s;
 
253
    s.beginGroup(kSettingsGroup);
 
254
 
 
255
    login_username = s.value("username").toString();
 
256
    login_password = QString();
 
257
  }
 
258
 
 
259
  server_->Login(login_username, login_password, bitrate_, volume_normalisation_);
 
260
 
 
261
  StartBlobProcess();
 
262
}
 
263
 
 
264
void SpotifyService::StartBlobProcess() {
 
265
  // Try to find an executable to run
 
266
  QString blob_path;
 
267
  QProcessEnvironment env(QProcessEnvironment::systemEnvironment());
 
268
 
 
269
  // Look in the system search path first
 
270
  if (QFile::exists(system_blob_path_)) {
 
271
    blob_path = system_blob_path_;
 
272
  }
 
273
 
 
274
  // Next look in the local path
 
275
  if (blob_path.isEmpty()) {
 
276
    if (QFile::exists(local_blob_path_)) {
 
277
      blob_path = local_blob_path_;
 
278
      env.insert("LD_LIBRARY_PATH", QFileInfo(local_blob_path_).path());
 
279
    }
 
280
  }
 
281
 
 
282
  if (blob_path.isEmpty()) {
 
283
    // If the blob still wasn't found then we'll prompt the user to download one
 
284
    if (login_task_id_) {
 
285
      model()->task_manager()->SetTaskFinished(login_task_id_);
 
286
    }
 
287
 
 
288
    #ifdef Q_OS_LINUX
 
289
      if (SpotifyBlobDownloader::Prompt()) {
 
290
        InstallBlob();
 
291
      }
 
292
    #endif
 
293
 
 
294
    return;
 
295
  }
 
296
 
 
297
  delete blob_process_;
 
298
  blob_process_ = new QProcess(this);
 
299
  blob_process_->setProcessChannelMode(QProcess::ForwardedChannels);
 
300
  blob_process_->setProcessEnvironment(env);
 
301
 
 
302
  connect(blob_process_,
 
303
          SIGNAL(error(QProcess::ProcessError)),
 
304
          SLOT(BlobProcessError(QProcess::ProcessError)));
 
305
 
 
306
  qLog(Info) << "Starting" << blob_path;
 
307
  blob_process_->start(
 
308
        blob_path, QStringList() << QString::number(server_->server_port()));
 
309
}
 
310
 
 
311
bool SpotifyService::IsBlobInstalled() const {
 
312
  return QFile::exists(system_blob_path_) ||
 
313
         QFile::exists(local_blob_path_);
 
314
}
 
315
 
 
316
void SpotifyService::InstallBlob() {
 
317
  // The downloader deletes itself when it finishes
 
318
  SpotifyBlobDownloader* downloader = new SpotifyBlobDownloader(
 
319
        local_blob_version_, QFileInfo(local_blob_path_).path(), this);
 
320
  connect(downloader, SIGNAL(Finished()), SLOT(BlobDownloadFinished()));
 
321
  connect(downloader, SIGNAL(Finished()), SIGNAL(BlobStateChanged()));
 
322
  downloader->Start();
 
323
}
 
324
 
 
325
void SpotifyService::BlobDownloadFinished() {
 
326
  EnsureServerCreated();
 
327
}
 
328
 
 
329
void SpotifyService::PlaylistsUpdated(const spotify_pb::Playlists& response) {
 
330
  if (login_task_id_) {
 
331
    model()->task_manager()->SetTaskFinished(login_task_id_);
 
332
    login_task_id_ = 0;
 
333
  }
 
334
 
 
335
  // Create starred and inbox playlists if they're not here already
 
336
  if (!search_) {
 
337
    search_ = new QStandardItem(IconLoader::Load("edit-find"),
 
338
                                tr("Search Spotify (opens a new tab)"));
 
339
    search_->setData(Type_SearchResults, InternetModel::Role_Type);
 
340
    search_->setData(InternetModel::PlayBehaviour_DoubleClickAction,
 
341
                             InternetModel::Role_PlayBehaviour);
 
342
 
 
343
    starred_ = new QStandardItem(QIcon(":/star-on.png"), tr("Starred"));
 
344
    starred_->setData(Type_StarredPlaylist, InternetModel::Role_Type);
 
345
    starred_->setData(true, InternetModel::Role_CanLazyLoad);
 
346
 
 
347
    inbox_ = new QStandardItem(IconLoader::Load("mail-message"), tr("Inbox"));
 
348
    inbox_->setData(Type_InboxPlaylist, InternetModel::Role_Type);
 
349
    inbox_->setData(true, InternetModel::Role_CanLazyLoad);
 
350
 
 
351
    root_->appendRow(search_);
 
352
    root_->appendRow(starred_);
 
353
    root_->appendRow(inbox_);
 
354
  }
 
355
 
 
356
  // Don't do anything if the playlists haven't changed since last time.
 
357
  if (!DoPlaylistsDiffer(response)) {
 
358
    qLog(Debug) << "Playlists haven't changed - not updating";
 
359
    return;
 
360
  }
 
361
 
 
362
  // Remove and recreate the other playlists
 
363
  foreach (QStandardItem* item, playlists_) {
 
364
    item->parent()->removeRow(item->row());
 
365
  }
 
366
  playlists_.clear();
 
367
 
 
368
  for (int i=0 ; i<response.playlist_size() ; ++i) {
 
369
    const spotify_pb::Playlists::Playlist& msg = response.playlist(i);
 
370
 
 
371
    QStandardItem* item = new QStandardItem(QStringFromStdString(msg.name()));
 
372
    item->setData(InternetModel::Type_UserPlaylist, InternetModel::Role_Type);
 
373
    item->setData(true, InternetModel::Role_CanLazyLoad);
 
374
    item->setData(msg.index(), Role_UserPlaylistIndex);
 
375
    item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
 
376
 
 
377
    root_->appendRow(item);
 
378
    playlists_ << item;
 
379
 
 
380
    // Preload the playlist items so that drag & drop works immediately.
 
381
    LazyPopulate(item);
 
382
  }
 
383
}
 
384
 
 
385
bool SpotifyService::DoPlaylistsDiffer(const spotify_pb::Playlists& response) const {
 
386
  if (playlists_.count() != response.playlist_size()) {
 
387
    return true;
 
388
  }
 
389
 
 
390
  for (int i=0 ; i<response.playlist_size() ; ++i) {
 
391
    const spotify_pb::Playlists::Playlist& msg = response.playlist(i);
 
392
    const QStandardItem* item = PlaylistBySpotifyIndex(msg.index());
 
393
 
 
394
    if (!item) {
 
395
      return true;
 
396
    }
 
397
 
 
398
    if (QStringFromStdString(msg.name()) != item->text()) {
 
399
      return true;
 
400
    }
 
401
  }
 
402
 
 
403
  return false;
 
404
}
 
405
 
 
406
void SpotifyService::InboxLoaded(const spotify_pb::LoadPlaylistResponse& response) {
 
407
  FillPlaylist(inbox_, response);
 
408
}
 
409
 
 
410
void SpotifyService::StarredLoaded(const spotify_pb::LoadPlaylistResponse& response) {
 
411
  FillPlaylist(starred_, response);
 
412
}
 
413
 
 
414
QStandardItem* SpotifyService::PlaylistBySpotifyIndex(int index) const {
 
415
  foreach (QStandardItem* item, playlists_) {
 
416
    if (item->data(Role_UserPlaylistIndex).toInt() == index) {
 
417
      return item;
 
418
    }
 
419
  }
 
420
  return NULL;
 
421
}
 
422
 
 
423
void SpotifyService::UserPlaylistLoaded(const spotify_pb::LoadPlaylistResponse& response) {
 
424
  // Find a playlist with this index
 
425
  QStandardItem* item = PlaylistBySpotifyIndex(response.request().user_playlist_index());
 
426
  if (item) {
 
427
    FillPlaylist(item, response);
 
428
  }
 
429
}
 
430
 
 
431
void SpotifyService::FillPlaylist(QStandardItem* item, const spotify_pb::LoadPlaylistResponse& response) {
 
432
  qLog(Debug) << "Filling playlist:" << item->text();
 
433
  if (item->hasChildren())
 
434
    item->removeRows(0, item->rowCount());
 
435
 
 
436
  for (int i=0 ; i<response.track_size() ; ++i) {
 
437
    Song song;
 
438
    SongFromProtobuf(response.track(i), &song);
 
439
 
 
440
    QStandardItem* child = new QStandardItem(song.PrettyTitleWithArtist());
 
441
    child->setData(Type_Track, InternetModel::Role_Type);
 
442
    child->setData(QVariant::fromValue(song), InternetModel::Role_SongMetadata);
 
443
    child->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
 
444
    child->setData(song.url(), InternetModel::Role_Url);
 
445
 
 
446
    item->appendRow(child);
 
447
  }
 
448
}
 
449
 
 
450
void SpotifyService::SongFromProtobuf(const spotify_pb::Track& track, Song* song) {
 
451
  song->set_rating(track.starred() ? 1.0 : 0.0);
 
452
  song->set_title(QStringFromStdString(track.title()));
 
453
  song->set_album(QStringFromStdString(track.album()));
 
454
  song->set_length_nanosec(track.duration_msec() * kNsecPerMsec);
 
455
  song->set_score(track.popularity());
 
456
  song->set_disc(track.disc());
 
457
  song->set_track(track.track());
 
458
  song->set_year(track.year());
 
459
  song->set_url(QUrl(QStringFromStdString(track.uri())));
 
460
  song->set_art_automatic("spotify://image/" + QStringFromStdString(track.album_art_id()));
 
461
 
 
462
  QStringList artists;
 
463
  for (int i=0 ; i<track.artist_size() ; ++i) {
 
464
    artists << QStringFromStdString(track.artist(i));
 
465
  }
 
466
 
 
467
  song->set_artist(artists.join(", "));
 
468
 
 
469
  song->set_filetype(Song::Type_Stream);
 
470
  song->set_valid(true);
 
471
  song->set_directory_id(0);
 
472
  song->set_mtime(0);
 
473
  song->set_ctime(0);
 
474
  song->set_filesize(0);
 
475
}
 
476
 
 
477
PlaylistItem::Options SpotifyService::playlistitem_options() const {
 
478
  return PlaylistItem::PauseDisabled | PlaylistItem::SeekDisabled;
 
479
}
 
480
 
 
481
void SpotifyService::EnsureMenuCreated() {
 
482
  if (context_menu_)
 
483
    return;
 
484
 
 
485
  context_menu_ = new QMenu;
 
486
 
 
487
  context_menu_->addActions(GetPlaylistActions());
 
488
  context_menu_->addSeparator();
 
489
  context_menu_->addAction(IconLoader::Load("edit-find"), tr("Search Spotify (opens a new tab)..."), this, SLOT(OpenSearchTab()));
 
490
  context_menu_->addSeparator();
 
491
  context_menu_->addAction(IconLoader::Load("configure"), tr("Configure Spotify..."), this, SLOT(ShowConfig()));
 
492
 
 
493
  playlist_context_menu_ = new QMenu;
 
494
  playlist_sync_action_ = playlist_context_menu_->addAction(
 
495
      IconLoader::Load("view-refresh"),
 
496
      tr("Make playlist available offline"),
 
497
      this,
 
498
      SLOT(SyncPlaylist()));
 
499
}
 
500
 
 
501
void SpotifyService::SyncPlaylist() {
 
502
  QStandardItem* item = playlist_sync_action_->data().value<QStandardItem*>();
 
503
  Q_ASSERT(item);
 
504
 
 
505
  switch (item->data(InternetModel::Role_Type).toInt()) {
 
506
    case InternetModel::Type_UserPlaylist: {
 
507
      int index = item->data(Role_UserPlaylistIndex).toInt();
 
508
      server_->SyncUserPlaylist(index);
 
509
      playlist_sync_ids_[index] =
 
510
          model()->task_manager()->StartTask(tr("Syncing Spotify playlist"));
 
511
      break;
 
512
    }
 
513
    case Type_InboxPlaylist:
 
514
      server_->SyncInbox();
 
515
      inbox_sync_id_ = model()->task_manager()->StartTask(tr("Syncing Spotify inbox"));
 
516
      break;
 
517
    case Type_StarredPlaylist:
 
518
      server_->SyncStarred();
 
519
      starred_sync_id_ = model()->task_manager()->StartTask(tr("Syncing Spotify starred tracks"));
 
520
      break;
 
521
    default:
 
522
      break;
 
523
  }
 
524
}
 
525
 
 
526
void SpotifyService::Search(const QString& text, Playlist* playlist, bool now) {
 
527
  EnsureServerCreated();
 
528
 
 
529
  pending_search_ = text;
 
530
  pending_search_playlist_ = playlist;
 
531
 
 
532
  if (now) {
 
533
    search_delay_->stop();
 
534
    DoSearch();
 
535
  } else {
 
536
    search_delay_->start();
 
537
  }
 
538
}
 
539
 
 
540
void SpotifyService::DoSearch() {
 
541
  if (!pending_search_.isEmpty()) {
 
542
    server_->Search(pending_search_, 200);
 
543
  }
 
544
}
 
545
 
 
546
void SpotifyService::SearchResults(const spotify_pb::SearchResponse& response) {
 
547
  if (QStringFromStdString(response.request().query()) != pending_search_) {
 
548
    qLog(Debug) << "Old search result for"
 
549
                << QStringFromStdString(response.request().query())
 
550
                << "expecting" << pending_search_;
 
551
    return;
 
552
  }
 
553
  pending_search_.clear();
 
554
 
 
555
  SongList songs;
 
556
  for (int i=0 ; i<response.result_size() ; ++i) {
 
557
    Song song;
 
558
    SongFromProtobuf(response.result(i), &song);
 
559
    songs << song;
 
560
  }
 
561
 
 
562
  qLog(Debug) << "Got" << songs.count() << "results";
 
563
 
 
564
  pending_search_playlist_->Clear();
 
565
  pending_search_playlist_->InsertSongs(songs);
 
566
 
 
567
  const QString did_you_mean = QStringFromStdString(response.did_you_mean());
 
568
  if (!did_you_mean.isEmpty()) {
 
569
    model()->player()->playlists()->playlist_container()->did_you_mean()->Show(did_you_mean);
 
570
  }
 
571
}
 
572
 
 
573
SpotifyServer* SpotifyService::server() const {
 
574
  SpotifyService* nonconst_this = const_cast<SpotifyService*>(this);
 
575
 
 
576
  if (QThread::currentThread() != thread()) {
 
577
    metaObject()->invokeMethod(nonconst_this, "EnsureServerCreated",
 
578
                               Qt::BlockingQueuedConnection);
 
579
  } else {
 
580
    nonconst_this->EnsureServerCreated();
 
581
  }
 
582
 
 
583
  return server_;
 
584
}
 
585
 
 
586
void SpotifyService::ShowContextMenu(const QModelIndex& index, const QPoint& global_pos) {
 
587
  EnsureMenuCreated();
 
588
  QStandardItem* item = model()->itemFromIndex(index);
 
589
  if (item) {
 
590
    int type = item->data(InternetModel::Role_Type).toInt();
 
591
    if (type == Type_InboxPlaylist ||
 
592
        type == Type_StarredPlaylist ||
 
593
        type == InternetModel::Type_UserPlaylist) {
 
594
      playlist_sync_action_->setData(qVariantFromValue(item));
 
595
      playlist_context_menu_->popup(global_pos);
 
596
      return;
 
597
    }
 
598
  }
 
599
 
 
600
  context_menu_->popup(global_pos);
 
601
}
 
602
 
 
603
void SpotifyService::OpenSearchTab() {
 
604
  model()->player()->playlists()->New(tr("Search Spotify"), SongList(),
 
605
                                      SpotifySearchPlaylistType::kName);
 
606
}
 
607
 
 
608
void SpotifyService::ItemDoubleClicked(QStandardItem* item) {
 
609
  if (item == search_) {
 
610
    OpenSearchTab();
 
611
  }
 
612
}
 
613
 
 
614
void SpotifyService::LoadImage(const QString& id) {
 
615
  EnsureServerCreated();
 
616
  server_->LoadImage(id);
 
617
}
 
618
 
 
619
void SpotifyService::SyncPlaylistProgress(
 
620
    const spotify_pb::SyncPlaylistProgress& progress) {
 
621
  qLog(Debug) << "Sync progress:" << progress.sync_progress();
 
622
  int task_id = -1;
 
623
  switch (progress.request().type()) {
 
624
    case spotify_pb::Inbox:
 
625
      task_id = inbox_sync_id_;
 
626
      break;
 
627
    case spotify_pb::Starred:
 
628
      task_id = starred_sync_id_;
 
629
      break;
 
630
    case spotify_pb::UserPlaylist: {
 
631
      QMap<int, int>::const_iterator it = playlist_sync_ids_.constFind(
 
632
          progress.request().user_playlist_index());
 
633
      if (it != playlist_sync_ids_.constEnd()) {
 
634
        task_id = it.value();
 
635
      }
 
636
      break;
 
637
    }
 
638
    default:
 
639
      break;
 
640
  }
 
641
  if (task_id == -1) {
 
642
    qLog(Warning) << "Received sync progress for unknown playlist";
 
643
    return;
 
644
  }
 
645
  model()->task_manager()->SetTaskProgress(task_id, progress.sync_progress(), 100);
 
646
  if (progress.sync_progress() == 100) {
 
647
    model()->task_manager()->SetTaskFinished(task_id);
 
648
    if (progress.request().type() == spotify_pb::UserPlaylist) {
 
649
      playlist_sync_ids_.remove(task_id);
 
650
    }
 
651
  }
 
652
}
 
653
 
 
654
void SpotifyService::ShowConfig() {
 
655
  emit OpenSettingsAtPage(SettingsDialog::Page_Spotify);
 
656
}
 
657
 
 
658
void SpotifyService::Logout() {
 
659
  delete server_;
 
660
  delete blob_process_;
 
661
  server_ = NULL;
 
662
  blob_process_ = NULL;
 
663
 
 
664
  login_state_ = LoginState_OtherError;
 
665
 
 
666
  QSettings s;
 
667
  s.beginGroup(kSettingsGroup);
 
668
  s.setValue("login_state", login_state_);
 
669
}