~ubuntu-branches/debian/experimental/udj-desktop-client/experimental

« back to all changes in this revision

Viewing changes to src/DataStore.cpp

  • Committer: Package Import Robot
  • Author(s): Nathan Handler
  • Date: 2012-06-14 10:21:47 UTC
  • Revision ID: package-import@ubuntu.com-20120614102147-0hd10fim35ov37qk
Tags: upstream-0.5.0
ImportĀ upstreamĀ versionĀ 0.5.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * Copyright 2011 Kurtis L. Nusbaum
 
3
 *
 
4
 * This file is part of UDJ.
 
5
 *
 
6
 * UDJ is free software: you can redistribute it and/or modify
 
7
 * it under the terms of the GNU General Public License as published by
 
8
 * the Free Software Foundation, either version 2 of the License, or
 
9
 * (at your option) any later version.
 
10
 *
 
11
 * UDJ is distributed in the hope that it will be useful,
 
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 * GNU General Public License for more details.
 
15
 *
 
16
 * You should have received a copy of the GNU General Public License
 
17
 * along with UDJ.  If not, see <http://www.gnu.org/licenses/>.
 
18
 */
 
19
#include "DataStore.hpp"
 
20
#include "UDJServerConnection.hpp"
 
21
#include "Utils.hpp"
 
22
#include "Logger.hpp"
 
23
 
 
24
#include <QDir>
 
25
#include <QDesktopServices>
 
26
#include <QDir>
 
27
#include <QSqlQuery>
 
28
#include <QVariant>
 
29
#include <QSqlRecord>
 
30
#include <QThread>
 
31
#include <QTimer>
 
32
#include <QDateTime>
 
33
#include <QProgressDialog>
 
34
#include <QSqlError>
 
35
 
 
36
#include <tag.h>
 
37
#include <tstring.h>
 
38
#include <fileref.h>
 
39
 
 
40
 
 
41
namespace UDJ{
 
42
 
 
43
 
 
44
DataStore::DataStore(
 
45
  const QString& username,
 
46
  const QString& password,
 
47
  const QByteArray& ticket,
 
48
  const user_id_t& userId,
 
49
  QObject *parent)
 
50
  :QObject(parent),
 
51
  username(username),
 
52
  password(password),
 
53
  isReauthing(false),
 
54
  currentSongId(-1)
 
55
{
 
56
  serverConnection = new UDJServerConnection(this);
 
57
  serverConnection->setTicket(ticket);
 
58
  serverConnection->setUserId(userId);
 
59
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
60
  if(settings.contains(getPlayerIdSettingName())){
 
61
    serverConnection->setPlayerId(settings.value(getPlayerIdSettingName()).value<player_id_t>());
 
62
  }
 
63
  activePlaylistRefreshTimer = new QTimer(this);
 
64
  activePlaylistRefreshTimer->setInterval(5000);
 
65
  setupDB();
 
66
 
 
67
  connect(
 
68
    serverConnection,
 
69
    SIGNAL(playerPasswordSet(const QString&)),
 
70
    this,
 
71
    SLOT(onPlayerPasswordSet(const QString&)));
 
72
 
 
73
  connect(
 
74
    serverConnection,
 
75
    SIGNAL(playerPasswordSetError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
76
    this,
 
77
    SLOT(onPlayerPasswordSetError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
78
 
 
79
 
 
80
  connect(
 
81
    serverConnection,
 
82
    SIGNAL(playerLocationSet(const QString&, const QString&, const QString&, int)),
 
83
    this,
 
84
    SLOT(onPlayerLocationSet(const QString&, const QString&, const QString&, int)));
 
85
 
 
86
  connect(
 
87
    serverConnection,
 
88
    SIGNAL(playerLocationSetError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
89
    this,
 
90
    SLOT(onPlayerLocationSetError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
91
 
 
92
  connect(
 
93
    serverConnection,
 
94
    SIGNAL(playerPasswordRemoved()),
 
95
    this,
 
96
    SLOT(onPlayerPasswordRemoved()));
 
97
 
 
98
  connect(
 
99
    serverConnection,
 
100
    SIGNAL(playerPasswordRemoveError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
101
    this,
 
102
    SLOT(onPlayerPasswordRemoveError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
103
 
 
104
  connect(
 
105
    serverConnection,
 
106
    SIGNAL(playerNameChanged(const QString&)),
 
107
    this,
 
108
    SLOT(onPlayerNameChanged(const QString&)));
 
109
 
 
110
  connect(
 
111
    serverConnection,
 
112
    SIGNAL(playerNameChangeError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
113
    this,
 
114
    SLOT(onPlayerNameChangeError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
115
 
 
116
  connect(
 
117
    serverConnection,
 
118
    SIGNAL(libSongsSyncedToServer(const QSet<library_song_id_t>&)),
 
119
    this,
 
120
    SLOT(setLibSongsSynced(const QSet<library_song_id_t>&)));
 
121
 
 
122
  connect(
 
123
    serverConnection,
 
124
    SIGNAL(playerCreated(const player_id_t&)),
 
125
    this,
 
126
    SLOT(onPlayerCreate(const player_id_t&)));
 
127
 
 
128
  connect(
 
129
    serverConnection,
 
130
    SIGNAL(playerCreationFailed(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
131
    this,
 
132
    SLOT(onPlayerCreationFailed(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
133
 
 
134
  connect(
 
135
    serverConnection,
 
136
    SIGNAL(newActivePlaylist(const QVariantMap&)),
 
137
    this,
 
138
    SLOT(setActivePlaylist(const QVariantMap&)));
 
139
 
 
140
  connect(
 
141
    serverConnection,
 
142
    SIGNAL(getActivePlaylistFail(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
143
    this,
 
144
    SLOT(onGetActivePlaylistFail(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
145
 
 
146
  connect(activePlaylistRefreshTimer,
 
147
    SIGNAL(timeout()),
 
148
    this,
 
149
    SLOT(refreshActivePlaylist()));
 
150
 
 
151
  connect(
 
152
    serverConnection,
 
153
    SIGNAL(currentSongSet()),
 
154
    this,
 
155
    SLOT(refreshActivePlaylist()));
 
156
 
 
157
  connect(
 
158
    serverConnection,
 
159
    SIGNAL(playerStateSet(const QString&)),
 
160
    this,
 
161
    SLOT(onPlayerStateChanged(const QString&)));
 
162
 
 
163
  connect(
 
164
    serverConnection,
 
165
    SIGNAL(libModError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
166
    this,
 
167
    SLOT(onLibModError(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
168
 
 
169
  connect(
 
170
    serverConnection,
 
171
    SIGNAL(setCurrentSongFailed(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
172
    this,
 
173
    SLOT(onSetCurrentSongFailed(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
174
 
 
175
  connect(
 
176
    serverConnection,
 
177
    SIGNAL(activePlaylistModified(const QSet<library_song_id_t>&, const QSet<library_song_id_t>&)),
 
178
    this,
 
179
    SLOT(onActivePlaylistModified(const QSet<library_song_id_t>&, const QSet<library_song_id_t>&)));
 
180
 
 
181
  connect(
 
182
    serverConnection,
 
183
    SIGNAL(activePlaylistModFailed(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
184
    this,
 
185
    SLOT(onActivePlaylistModFailed(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
186
 
 
187
  connect(
 
188
    serverConnection,
 
189
    SIGNAL(setVolumeFailed(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)),
 
190
    this,
 
191
    SLOT(onSetVolumeFailed(const QString&, int, const QList<QNetworkReply::RawHeaderPair>&)));
 
192
 
 
193
 
 
194
  connect(
 
195
    serverConnection,
 
196
    SIGNAL(authenticated(const QByteArray&, const user_id_t&)),
 
197
    this,
 
198
    SLOT(onReauth(const QByteArray&, const user_id_t&)));
 
199
 
 
200
  connect(
 
201
    serverConnection,
 
202
    SIGNAL(authFailed(const QString&)),
 
203
    this,
 
204
    SLOT(onAuthFail(const QString&)));
 
205
 
 
206
}
 
207
 
 
208
void DataStore::setupDB(){
 
209
  //TODO do all of this stuff in a seperate thread and return right away.
 
210
  QDir dbDir(QDesktopServices::storageLocation(QDesktopServices::DataLocation));
 
211
  if(!dbDir.exists()){
 
212
    Logger::instance()->log("DB dir didn't exists. making it at : " + dbDir.absolutePath());
 
213
    //TODO handle if this fails
 
214
    dbDir.mkpath(dbDir.absolutePath());
 
215
  }
 
216
  QString dbFilePath = dbDir.absoluteFilePath(getPlayerDBName());
 
217
  if(!QFile::exists(dbFilePath)){
 
218
    Logger::instance()->log("DB file didn't exist, so creating it at: " + dbFilePath);
 
219
    QFile dbFile(dbFilePath);
 
220
    dbFile.open(QIODevice::WriteOnly);
 
221
    dbFile.close();
 
222
  }
 
223
  database = QSqlDatabase::addDatabase("QSQLITE", getPlayerDBConnectionName());
 
224
  database.setDatabaseName(dbFilePath);
 
225
  database.open();
 
226
 
 
227
  QSqlQuery setupQuery(database);
 
228
 
 
229
  EXEC_SQL(
 
230
    "Error creating library table",
 
231
    setupQuery.exec(getCreateLibraryQuery()),
 
232
    setupQuery)
 
233
 
 
234
  EXEC_SQL(
 
235
    "Error creating activePlaylist table.",
 
236
    setupQuery.exec(getCreateActivePlaylistQuery()),
 
237
    setupQuery)
 
238
 
 
239
  EXEC_SQL(
 
240
    "Error creating activePlaylist view.",
 
241
    setupQuery.exec(getCreateActivePlaylistViewQuery()),
 
242
    setupQuery)
 
243
 
 
244
}
 
245
 
 
246
void DataStore::removePlayerPassword(){
 
247
  serverConnection->removePlayerPassword();
 
248
}
 
249
 
 
250
void DataStore::onPlayerPasswordRemoved(){
 
251
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
252
  settings.remove(getPlayerPasswordSettingName());
 
253
  emit playerPasswordRemoved();
 
254
}
 
255
 
 
256
void DataStore::onPlayerPasswordRemoveError(
 
257
  const QString& errMessage,
 
258
  int /*errorCode*/,
 
259
  const QList<QNetworkReply::RawHeaderPair>& /*headers*/)
 
260
{
 
261
  //TODO handle reauth error
 
262
  emit playerPasswordRemoveError(errMessage);
 
263
}
 
264
 
 
265
 
 
266
void DataStore::setPlayerPassword(const QString& newPassword){
 
267
  serverConnection->setPlayerPassword(newPassword);
 
268
}
 
269
 
 
270
void DataStore::onPlayerPasswordSet(const QString& password){
 
271
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
272
  settings.setValue(getPlayerPasswordSettingName(), password);
 
273
  emit playerPasswordSet();
 
274
}
 
275
 
 
276
void DataStore::onPlayerPasswordSetError(
 
277
  const QString& errMessage,
 
278
  int /*errorCode*/,
 
279
  const QList<QNetworkReply::RawHeaderPair>& /*headers*/)
 
280
{
 
281
  emit playerPasswordSetError(errMessage);
 
282
}
 
283
 
 
284
void DataStore::setPlayerLocation(
 
285
  const QString& streetAddress,
 
286
  const QString& city,
 
287
  const QString& state,
 
288
  int zipcode
 
289
)
 
290
{
 
291
  serverConnection->setPlayerLocation(streetAddress, city, state, zipcode);
 
292
}
 
293
 
 
294
void DataStore::onPlayerLocationSet(
 
295
  const QString& streetAddress,
 
296
  const QString& city,
 
297
  const QString& state,
 
298
  int zipcode
 
299
)
 
300
{
 
301
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
302
  settings.setValue(getAddressSettingName(), streetAddress);
 
303
  settings.setValue(getCitySettingName(), city);
 
304
  settings.setValue(getStateSettingName(), state);
 
305
  settings.setValue(getZipCodeSettingName(), zipcode);
 
306
  emit playerLocationSet();
 
307
}
 
308
 
 
309
void DataStore::onPlayerLocationSetError(
 
310
  const QString& errMessage,
 
311
  int /*errorCode*/,
 
312
  const QList<QNetworkReply::RawHeaderPair>& /*headers*/)
 
313
{
 
314
  //TODO handle reauth error
 
315
  emit playerLocationSetError(errMessage);
 
316
}
 
317
 
 
318
void DataStore::setPlayerName(const QString& newName){
 
319
  serverConnection->setPlayerName(newName);
 
320
}
 
321
 
 
322
void DataStore::onPlayerNameChanged(const QString& newName){
 
323
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
324
  settings.setValue(getPlayerNameSettingName(), newName);
 
325
  emit playerNameChanged(newName);
 
326
}
 
327
 
 
328
void DataStore::onPlayerNameChangeError(
 
329
  const QString& /*errMessage*/,
 
330
  int errorCode,
 
331
  const QList<QNetworkReply::RawHeaderPair>& /*headers*/)
 
332
{
 
333
  //TODO if reauth error, should reauth
 
334
  if(errorCode == 409){
 
335
    emit playerNameChangeError(tr(
 
336
      "You already have a player with that name."
 
337
    ));
 
338
  }
 
339
  else{
 
340
  emit playerNameChangeError(tr(
 
341
    "We seem to be having some techincal difficulties and couldn't change "
 
342
    "the name of your player. Try again in a little bit."
 
343
  ));
 
344
  }
 
345
}
 
346
void DataStore::pausePlayer(){
 
347
  setPlayerState(getPausedState());
 
348
}
 
349
 
 
350
void DataStore::playPlayer(){
 
351
  setPlayerState(getPlayingState());
 
352
}
 
353
 
 
354
void DataStore::setPlayerState(const QString& newState){
 
355
  serverConnection->setPlayerState(newState);
 
356
}
 
357
 
 
358
 
 
359
void DataStore::addMusicToLibrary(
 
360
  const QList<Phonon::MediaSource>& songs, QProgressDialog* progress)
 
361
{
 
362
  bool isTransacting=database.transaction();
 
363
  if(isTransacting){
 
364
    Logger::instance()->log("Was able to start transaction");
 
365
  }
 
366
  QSqlQuery addQuery(database);
 
367
  addQuery.prepare(
 
368
    "INSERT INTO "+getLibraryTableName()+ 
 
369
    "("+
 
370
    getLibSongColName() + ","+
 
371
    getLibArtistColName() + ","+
 
372
    getLibAlbumColName() + ","+
 
373
    getLibGenreColName() + "," +
 
374
    getLibTrackColName() + "," +
 
375
    getLibFileColName() + "," +
 
376
    getLibDurationColName() +")" +
 
377
    "VALUES ( :song , :artist , :album , :genre, :track, :file, :duration );"
 
378
  );
 
379
 
 
380
  for(int i =0; i<songs.size(); ++i){
 
381
    addSongToLibrary(songs[i], addQuery);
 
382
    if(progress != NULL){
 
383
      progress->setValue(i);
 
384
      if(progress->wasCanceled()){
 
385
        if(isTransacting){
 
386
          Logger::instance()->log("Rolling back transaction");
 
387
          if(!database.rollback()){
 
388
            Logger::instance()->log("Roll back failed");
 
389
          }
 
390
        }
 
391
        return;
 
392
      }
 
393
    }
 
394
  }
 
395
  if(isTransacting){
 
396
    Logger::instance()->log("Committing add transaction");
 
397
    database.commit();
 
398
  }
 
399
}
 
400
 
 
401
void DataStore::addSongToLibrary(const Phonon::MediaSource& song, QSqlQuery &addQuery){
 
402
  QString fileName = song.fileName();
 
403
  QString songName;
 
404
  QString artistName;
 
405
  QString albumName;
 
406
  QString genre;
 
407
  int track;
 
408
  int duration;
 
409
  TagLib::FileRef f(fileName.toStdString().c_str());
 
410
  if(!f.isNull() && f.tag() && f.audioProperties()){
 
411
    TagLib::Tag *tag = f.tag();
 
412
    songName = TStringToQString(tag->title());
 
413
    artistName = TStringToQString(tag->artist());
 
414
    albumName = TStringToQString(tag->album());
 
415
    genre = TStringToQString(tag->genre());
 
416
    duration = f.audioProperties()->length();
 
417
    track = tag->track();
 
418
  }
 
419
  else{
 
420
    //TODO throw error
 
421
    return;
 
422
  }
 
423
 
 
424
  if(songName == ""){
 
425
    songName = unknownSongTitle();
 
426
  }
 
427
  if(artistName == ""){
 
428
    artistName = unknownSongArtist();
 
429
  }
 
430
  if(albumName == ""){
 
431
    albumName = unknownSongAlbum();
 
432
  }
 
433
  if(genre == ""){
 
434
    genre = unknownGenre();
 
435
  }
 
436
 
 
437
  Logger::instance()->log("adding song with title: " + songName + " to database");
 
438
 
 
439
  library_song_id_t hostId =-1;
 
440
 
 
441
  addQuery.bindValue(":song", songName);
 
442
  addQuery.bindValue(":artist", artistName);
 
443
  addQuery.bindValue(":album", albumName);
 
444
  addQuery.bindValue(":genre", genre);
 
445
  addQuery.bindValue(":track", track);
 
446
  addQuery.bindValue(":file", fileName);
 
447
  addQuery.bindValue(":duration", duration);
 
448
  EXEC_INSERT(
 
449
    "Failed to add song library" << songName.toStdString(), 
 
450
    addQuery,
 
451
    hostId,
 
452
    library_song_id_t)
 
453
}
 
454
 
 
455
void DataStore::removeSongsFromLibrary(const QSet<library_song_id_t>& toRemove,
 
456
  QProgressDialog* progress)
 
457
{
 
458
  bool isTransacting = database.transaction();
 
459
  QSqlQuery deleteQuery(database);
 
460
  deleteQuery.prepare("UPDATE " + getLibraryTableName() +  " "
 
461
    "SET " + getLibIsDeletedColName() + "=1, "+
 
462
    getLibSyncStatusColName() + "=" + 
 
463
      QString::number(getLibNeedsDeleteSyncStatus()) + " "
 
464
    "WHERE " + getLibIdColName() + "= ?"); 
 
465
  int i=0;
 
466
  Q_FOREACH(library_song_id_t id, toRemove){
 
467
    deleteQuery.bindValue(0, QVariant::fromValue<library_song_id_t>(id));
 
468
    EXEC_SQL(
 
469
      "Error setting song sync status",
 
470
      deleteQuery.exec(),
 
471
      deleteQuery)
 
472
    if(progress != NULL){
 
473
      progress->setValue(i);
 
474
      if(progress->wasCanceled()){
 
475
        if(isTransacting){
 
476
          database.rollback();
 
477
        }
 
478
        break;
 
479
      }
 
480
    }
 
481
    ++i;
 
482
  }
 
483
  if(isTransacting){
 
484
    database.commit();
 
485
  }
 
486
}
 
487
 
 
488
 
 
489
void DataStore::addSongToActivePlaylist(library_song_id_t libraryId){
 
490
  QSet<library_song_id_t> libIds;
 
491
  libIds.insert(libraryId);
 
492
  addSongsToActivePlaylist(libIds);
 
493
}
 
494
 
 
495
void DataStore::addSongsToActivePlaylist(const QSet<library_song_id_t>& libIds){
 
496
  playlistIdsToAdd.unite(libIds);
 
497
  QSet<library_song_id_t> emptySet;
 
498
  serverConnection->modActivePlaylist(libIds, emptySet);
 
499
}
 
500
 
 
501
void DataStore::removeSongsFromActivePlaylist(const QSet<library_song_id_t>& libIds){
 
502
  playlistIdsToRemove.unite(libIds);
 
503
  QSet<library_song_id_t> emptySet;
 
504
  serverConnection->modActivePlaylist(emptySet, libIds);
 
505
}
 
506
 
 
507
QSqlDatabase DataStore::getDatabaseConnection(){
 
508
  QSqlDatabase toReturn = QSqlDatabase::database(getPlayerDBConnectionName());
 
509
  return toReturn;
 
510
}
 
511
 
 
512
Phonon::MediaSource DataStore::getNextSongToPlay(){
 
513
  QSqlQuery nextSongQuery("SELECT " + getLibFileColName() + " FROM " +
 
514
    getActivePlaylistViewName() + " LIMIT 1;");
 
515
  EXEC_SQL(
 
516
    "Getting next song failed",
 
517
    nextSongQuery.exec(),
 
518
    nextSongQuery)
 
519
  //TODO handle is this returns false
 
520
  if(nextSongQuery.first()){
 
521
    return Phonon::MediaSource(nextSongQuery.value(0).toString());
 
522
  }
 
523
  else{
 
524
    return Phonon::MediaSource("");
 
525
  }
 
526
}
 
527
 
 
528
DataStore::song_info_t DataStore::takeNextSongToPlay(){
 
529
  QSqlQuery nextSongQuery(
 
530
    "SELECT " + getLibFileColName() + ", " + 
 
531
    getLibSongColName() + ", " +
 
532
    getLibArtistColName() + ", " +
 
533
    getActivePlaylistLibIdColName() +" FROM " +
 
534
    getActivePlaylistViewName() + " LIMIT 1;", 
 
535
    database);
 
536
  EXEC_SQL(
 
537
    "Getting next song in take failed",
 
538
    nextSongQuery.exec(),
 
539
    nextSongQuery)
 
540
  nextSongQuery.next();
 
541
  if(!nextSongQuery.isValid()){
 
542
    currentSongId = -1;
 
543
    song_info_t toReturn = {Phonon::MediaSource(""), "", "" };
 
544
    return toReturn;
 
545
  }
 
546
  currentSongId =
 
547
    nextSongQuery.value(3).value<library_song_id_t>();
 
548
 
 
549
  deleteSongFromPlaylist(currentSongId);
 
550
 
 
551
  Logger::instance()->log("Setting current song with id: " + QString::number(currentSongId));
 
552
  serverConnection->setCurrentSong(currentSongId);
 
553
 
 
554
  QString filePath = nextSongQuery.value(0).toString();
 
555
  song_info_t toReturn = {
 
556
    Phonon::MediaSource(filePath),
 
557
    nextSongQuery.value(1).toString(),
 
558
    nextSongQuery.value(2).toString()
 
559
  };
 
560
 
 
561
  return toReturn;
 
562
 
 
563
}
 
564
 
 
565
void DataStore::deleteSongFromPlaylist(library_song_id_t toDelete){
 
566
  QSqlQuery deleteSongQuery(
 
567
    "DELETE FROM " + getActivePlaylistTableName() +
 
568
    " WHERE " + 
 
569
    getActivePlaylistLibIdColName() + " = " + QString::number(toDelete) + ";", 
 
570
    database);
 
571
  EXEC_SQL(
 
572
    "Deleting song from playlist failed",
 
573
    deleteSongQuery.exec(),
 
574
    deleteSongQuery)
 
575
}
 
576
 
 
577
void DataStore::setCurrentSong(const library_song_id_t& songToPlay){
 
578
  QSqlQuery getSongQuery(
 
579
    "SELECT " + getLibFileColName() + ", " +
 
580
    getLibSongColName() + ", " +
 
581
    getLibArtistColName() + " FROM " +
 
582
    getActivePlaylistViewName() + " WHERE " + 
 
583
    getActivePlaylistLibIdColName() + " = " + QString::number(songToPlay) + ";", 
 
584
    database);
 
585
  EXEC_SQL(
 
586
    "Getting song for manual playlist set failed.",
 
587
    getSongQuery.exec(),
 
588
    getSongQuery)
 
589
  getSongQuery.next();
 
590
  if(getSongQuery.isValid()){
 
591
    Logger::instance()->log("Got file, for manual song set");
 
592
    QString filePath = getSongQuery.value(0).toString();
 
593
    currentSongId = songToPlay;
 
594
    serverConnection->setCurrentSong(songToPlay);
 
595
    Logger::instance()->log("Retrieved Artist " + getSongQuery.value(2).toString());
 
596
    song_info_t toEmit = {
 
597
      Phonon::MediaSource(filePath),
 
598
      getSongQuery.value(1).toString(),
 
599
      getSongQuery.value(2).toString()
 
600
    };
 
601
    emit manualSongChange(toEmit);
 
602
  }
 
603
}
 
604
 
 
605
void DataStore::createNewPlayer(
 
606
  const QString& name,
 
607
  const QString& password)
 
608
{
 
609
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
610
  settings.setValue(getPlayerNameSettingName(), name);
 
611
  settings.setValue(getPlayerPasswordSettingName(), password);
 
612
  serverConnection->createPlayer(name, password);
 
613
}
 
614
 
 
615
void DataStore::createNewPlayer(
 
616
  const QString& name,
 
617
  const QString& password,
 
618
  const QString& streetAddress,
 
619
  const QString& city,
 
620
  const QString& state,
 
621
  const int& zipcode)
 
622
{
 
623
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
624
  settings.setValue(getPlayerNameSettingName(), name);
 
625
  settings.setValue(getPlayerPasswordSettingName(), password);
 
626
  settings.setValue(getAddressSettingName(), streetAddress);
 
627
  settings.setValue(getCitySettingName(), city);
 
628
  settings.setValue(getStateSettingName(), state);
 
629
  settings.setValue(getZipCodeSettingName(), zipcode);
 
630
  serverConnection->createPlayer(
 
631
    name,
 
632
    password,
 
633
    streetAddress,
 
634
    city,
 
635
    state,
 
636
    zipcode);
 
637
}
 
638
 
 
639
void DataStore::changeVolumeSilently(qreal newVolume){
 
640
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
641
  Logger::instance()->log("Current volume " + QString::number(settings.value(getPlayerVolumeSettingName()).toReal()));
 
642
  Logger::instance()->log("New volume " + QString::number(newVolume));
 
643
  if((int)(settings.value(getPlayerVolumeSettingName()).toReal()*10) != (int)(newVolume*10)){
 
644
    Logger::instance()->log("Volume was different than current volume, now setting");
 
645
    settings.setValue(getPlayerVolumeSettingName(), newVolume);
 
646
    serverConnection->setVolume((int)(newVolume * 10));
 
647
  }
 
648
}
 
649
 
 
650
 
 
651
 
 
652
 
 
653
 
 
654
 
 
655
void DataStore::syncLibrary(){
 
656
  QSqlQuery needAddSongs(database);
 
657
  Logger::instance()->log("batching up sync");
 
658
  EXEC_SQL(
 
659
    "Error querying for song to add",
 
660
    needAddSongs.exec(
 
661
      "SELECT * FROM " + getLibraryTableName() + " WHERE " + 
 
662
      getLibSyncStatusColName() + "==" + 
 
663
      QString::number(getLibNeedsAddSyncStatus()) + " LIMIT 100;"),
 
664
    needAddSongs)
 
665
 
 
666
  QVariantList songsToAdd;
 
667
  QSqlRecord currentRecord;
 
668
  while(needAddSongs.next()){
 
669
    currentRecord = needAddSongs.record();
 
670
    QVariantMap songToAdd;
 
671
    songToAdd["id"] = currentRecord.value(getLibIdColName());
 
672
    QString title = currentRecord.value(getLibSongColName()).toString();
 
673
    title.truncate(199);
 
674
    songToAdd["title"] = title;
 
675
    QString artist = currentRecord.value(getLibArtistColName()).toString();
 
676
    artist.truncate(199);
 
677
    songToAdd["artist"] = artist;
 
678
    QString album = currentRecord.value(getLibAlbumColName()).toString();
 
679
    album.truncate(199);
 
680
    songToAdd["album"] = album; 
 
681
    songToAdd["duration"] = currentRecord.value(getLibDurationColName());
 
682
    songToAdd["track"] = currentRecord.value(getLibTrackColName()).toInt();
 
683
    QString genre = currentRecord.value(getLibGenreColName()).toString();
 
684
    genre.truncate(49);
 
685
    songToAdd["genre"] = genre;
 
686
    songsToAdd.append(songToAdd);
 
687
  }
 
688
 
 
689
  QSqlQuery needDeleteSongs(database);
 
690
  EXEC_SQL(
 
691
    "Error querying for songs to delete",
 
692
    needDeleteSongs.exec(
 
693
      "SELECT * FROM " + getLibraryTableName() + " WHERE " + 
 
694
      getLibSyncStatusColName() + "==" + 
 
695
      QString::number(getLibNeedsDeleteSyncStatus()) + " LIMIT 100;"),
 
696
    needDeleteSongs)
 
697
 
 
698
  QVariantList songsToDelete;
 
699
  while(needDeleteSongs.next()){
 
700
    currentRecord = needDeleteSongs.record();
 
701
    songsToDelete.append(currentRecord.value(getLibIdColName()));
 
702
  }
 
703
 
 
704
  Logger::instance()->log("Found " + QString::number(songsToDelete.size()) + " songs which need deleting");
 
705
  Logger::instance()->log("Found " + QString::number(songsToAdd.size()) + " songs which need adding");
 
706
  if(songsToDelete.size() > 0 || songsToAdd.size() > 0){
 
707
    serverConnection->modLibContents(songsToAdd, songsToDelete);
 
708
  }
 
709
}
 
710
 
 
711
void DataStore::setLibSongSynced(library_song_id_t song){
 
712
  QSet<library_song_id_t> songSet;
 
713
  songSet.insert(song);
 
714
  setLibSongsSynced(songSet);
 
715
}
 
716
 
 
717
void DataStore::setLibSongsSynced(const QSet<library_song_id_t>& songs){
 
718
  setLibSongsSyncStatus(songs, getLibIsSyncedStatus());
 
719
}
 
720
 
 
721
void DataStore::setLibSongsSyncStatus(
 
722
  const QSet<library_song_id_t>& songs,
 
723
  const lib_sync_status_t syncStatus)
 
724
{
 
725
  Logger::instance()->log("Setting songs to synced");
 
726
  bool isTransacting = database.transaction();
 
727
  QSqlQuery syncQuery(database);
 
728
  syncQuery.prepare("UPDATE " + getLibraryTableName() +  " "
 
729
    "SET " + getLibSyncStatusColName() + "=" + 
 
730
      QString::number(syncStatus) + " "
 
731
    "WHERE " + getLibIdColName() + "= ?"); 
 
732
  Q_FOREACH(library_song_id_t id, songs){
 
733
    syncQuery.bindValue(0, QVariant::fromValue<library_song_id_t>(id));
 
734
    EXEC_SQL(
 
735
      "Error setting song sync status",
 
736
      syncQuery.exec(),
 
737
      syncQuery)
 
738
    QSet<library_song_id_t> modId;
 
739
    modId.insert(id);
 
740
    emit libSongsModified(modId);
 
741
  }
 
742
  if(isTransacting){
 
743
    database.commit();
 
744
  }
 
745
 
 
746
  if(hasUnsyncedSongs()){
 
747
    Logger::instance()->log("more stuff to sync");
 
748
    syncLibrary();
 
749
  }
 
750
  else{
 
751
    emit allSynced();
 
752
    Logger::instance()->log("syncing done");
 
753
  }
 
754
}
 
755
 
 
756
bool DataStore::hasUnsyncedSongs() const{
 
757
  return getTotalUnsynced() != 0;
 
758
}
 
759
 
 
760
int DataStore::getTotalUnsynced() const{
 
761
  QSqlQuery unsyncedQuery(database);
 
762
  EXEC_SQL(
 
763
    "Error querying for unsynced songs",
 
764
    unsyncedQuery.exec(
 
765
      "SELECT COUNT(*) FROM " + getLibraryTableName() + " WHERE " + 
 
766
      getLibSyncStatusColName() + "!=" + 
 
767
      QString::number(getLibIsSyncedStatus()) + ";"),
 
768
    unsyncedQuery)
 
769
  if(unsyncedQuery.next()){
 
770
    return unsyncedQuery.record().value(0).toInt();
 
771
  }
 
772
  else{
 
773
    return 0;
 
774
  }
 
775
}
 
776
 
 
777
 
 
778
void DataStore::clearActivePlaylist(){
 
779
  QSqlQuery deleteActivePlayilstQuery(database);
 
780
  EXEC_SQL(
 
781
    "Error clearing active playlist table.",
 
782
    deleteActivePlayilstQuery.exec(getClearActivePlaylistQuery()),
 
783
    deleteActivePlayilstQuery)
 
784
}
 
785
 
 
786
void DataStore::addSong2ActivePlaylistFromQVariant(
 
787
  const QVariantMap &songToAdd, int priority)
 
788
{
 
789
  QSqlQuery addQuery(
 
790
    "INSERT INTO "+getActivePlaylistTableName()+ 
 
791
    "("+
 
792
    getActivePlaylistLibIdColName() + ","+
 
793
    getDownVoteColName() + ","+
 
794
    getUpVoteColName() + "," +
 
795
    getPriorityColName() + "," +
 
796
    getTimeAddedColName() +"," +
 
797
    getAdderUsernameColName() +"," +
 
798
    getAdderIdColName() + ")" +
 
799
    " VALUES ( :libid , :down , :up, :pri , :time , :username, :adder );",
 
800
    database);
 
801
 
 
802
  addQuery.bindValue(":libid", songToAdd["song"].toMap()["id"]);
 
803
  addQuery.bindValue(":down", songToAdd["downvoters"].toList().size());
 
804
  addQuery.bindValue(":up", songToAdd["upvoters"].toList().size());
 
805
  addQuery.bindValue(":pri", priority);
 
806
  addQuery.bindValue(":time", songToAdd["time_added"]);
 
807
  addQuery.bindValue(":username", songToAdd["adder"].toMap()["username"]);
 
808
  addQuery.bindValue(":adder", songToAdd["adder"].toMap()["id"]);
 
809
 
 
810
  long insertId;
 
811
  EXEC_INSERT(
 
812
    "Failed to add song library" << songToAdd["song"].toString().toStdString(),
 
813
    addQuery,
 
814
    insertId,
 
815
    long)
 
816
}
 
817
 
 
818
void DataStore::setActivePlaylist(const QVariantMap& newPlaylist){
 
819
 
 
820
  int retrievedVolume = newPlaylist["volume"].toInt();
 
821
  if(retrievedVolume != (int)(getPlayerVolume()*10)){
 
822
    QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
823
    settings.setValue(getPlayerVolumeSettingName(), retrievedVolume/10.0);
 
824
    emit volumeChanged(retrievedVolume/10.0);
 
825
  }
 
826
 
 
827
  onPlayerStateChanged(newPlaylist["state"].toString());
 
828
 
 
829
  library_song_id_t retrievedCurrentId =
 
830
    newPlaylist["current_song"].toMap()["song"].toMap()["id"].value<library_song_id_t>();
 
831
  if(retrievedCurrentId != currentSongId){
 
832
    QSqlQuery getSongQuery(
 
833
      "SELECT " + getLibFileColName() + ", " +
 
834
      getLibSongColName() + ", " +
 
835
      getLibArtistColName() + " FROM " +
 
836
      getActivePlaylistViewName() + " WHERE " + 
 
837
      getActivePlaylistLibIdColName() + " = " + QString::number(retrievedCurrentId) + ";", 
 
838
      database);
 
839
    EXEC_SQL(
 
840
      "Getting song for manual playlist set failed.",
 
841
      getSongQuery.exec(),
 
842
      getSongQuery)
 
843
    getSongQuery.next();
 
844
    if(getSongQuery.isValid()){
 
845
      Logger::instance()->log("Got file, for manual song set");
 
846
      QString filePath = getSongQuery.value(0).toString();
 
847
      currentSongId = retrievedCurrentId;
 
848
      song_info_t toEmit = {
 
849
        Phonon::MediaSource(filePath),
 
850
        getSongQuery.value(1).toString(),
 
851
        getSongQuery.value(2).toString()
 
852
      };
 
853
      emit manualSongChange(toEmit);
 
854
    }
 
855
  }
 
856
  clearActivePlaylist();
 
857
  QVariantList newSongs = newPlaylist["active_playlist"].toList();
 
858
  for(int i=0; i<newSongs.size(); ++i){
 
859
    addSong2ActivePlaylistFromQVariant(newSongs[i].toMap(), i); 
 
860
  }
 
861
  emit activePlaylistModified();
 
862
  
 
863
}
 
864
 
 
865
void DataStore::onGetActivePlaylistFail(
 
866
  const QString& errMessage,
 
867
  int errorCode,
 
868
  const QList<QNetworkReply::RawHeaderPair>& headers)
 
869
{
 
870
  Logger::instance()->log("Playlist error: " + QString::number(errorCode) + " " + errMessage);
 
871
  if(isTicketAuthError(errorCode, headers)){
 
872
    Logger::instance()->log("Got the ticket-hash challenge");
 
873
    reauthActions.insert(GET_ACTIVE_PLAYLIST);
 
874
    initReauth();
 
875
  }
 
876
  //TODO handle other possible errors?
 
877
}
 
878
 
 
879
void DataStore::onActivePlaylistModified(
 
880
  const QSet<library_song_id_t>& added,
 
881
  const QSet<library_song_id_t>& removed)
 
882
{
 
883
  playlistIdsToAdd.subtract(added);
 
884
  playlistIdsToRemove.subtract(removed);
 
885
  refreshActivePlaylist();
 
886
}
 
887
 
 
888
void DataStore::onActivePlaylistModFailed(
 
889
  const QString& /*errMessage*/,
 
890
  int errorCode,
 
891
  const QList<QNetworkReply::RawHeaderPair>& headers)
 
892
{
 
893
  Logger::instance()->log("Active playlist mod failed with code " + QString::number(errorCode));
 
894
  if(isTicketAuthError(errorCode, headers)){
 
895
    Logger::instance()->log("Got the ticket-hash challenge");
 
896
    reauthActions.insert(MOD_PLAYLIST);
 
897
    initReauth();
 
898
  }
 
899
  //TODO do stuff on failure
 
900
}
 
901
 
 
902
void DataStore::refreshActivePlaylist(){
 
903
  serverConnection->getActivePlaylist();
 
904
}
 
905
 
 
906
 
 
907
void DataStore::onPlayerCreate(const player_id_t& issuedId){
 
908
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
909
  settings.setValue(getPlayerIdSettingName(), QVariant::fromValue(issuedId));
 
910
  serverConnection->setPlayerId(issuedId);
 
911
  setPlayerState(getPlayingState());
 
912
  emit playerCreated();
 
913
}
 
914
 
 
915
void DataStore::onPlayerCreationFailed(const QString& errMessage, int /*errorCode*/,
 
916
    const QList<QNetworkReply::RawHeaderPair>& /*headers*/)
 
917
{
 
918
  //TODO do other stuff as well. like do reauth
 
919
  emit playerCreationFailed(errMessage);
 
920
}
 
921
 
 
922
 
 
923
void DataStore::onPlayerStateChanged(const QString& newState){
 
924
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
925
  if(newState != settings.value(getPlayerStateSettingName())){
 
926
    settings.setValue(getPlayerStateSettingName(), newState);
 
927
    emit playerStateChanged(newState);
 
928
  }
 
929
 
 
930
  //If this player state change is the result of starting up the player for the first time
 
931
  //we need to do a few things
 
932
  if(!activePlaylistRefreshTimer->isActive()){
 
933
    refreshActivePlaylist();
 
934
    activePlaylistRefreshTimer->start();
 
935
  }
 
936
}
 
937
 
 
938
 
 
939
void DataStore::onLibModError(
 
940
    const QString& errMessage, int errorCode, const QList<QNetworkReply::RawHeaderPair>& headers)
 
941
{
 
942
  Logger::instance()->log("Got bad libmod " + QString::number(errorCode));
 
943
  if(isTicketAuthError(errorCode, headers)){
 
944
    Logger::instance()->log("Got the ticket-hash challenge");
 
945
    reauthActions.insert(SYNC_LIB);
 
946
    initReauth();
 
947
  }
 
948
  else{
 
949
    Logger::instance()->log("Bad lib mod message " + errMessage);
 
950
    emit libModError(errMessage);
 
951
  }
 
952
}
 
953
 
 
954
void DataStore::onSetCurrentSongFailed(
 
955
  const QString& errMessage, int errorCode, const QList<QNetworkReply::RawHeaderPair>& headers)
 
956
{
 
957
  Logger::instance()->log("Setting current song failed: " + QString::number(errorCode) + " " + errMessage);
 
958
  if(isTicketAuthError(errorCode, headers)){
 
959
    Logger::instance()->log("Got the ticket-hash challenge");
 
960
    reauthActions.insert(SET_CURRENT_SONG);
 
961
    initReauth();
 
962
  }
 
963
}
 
964
 
 
965
void DataStore::onSetVolumeFailed(
 
966
  const QString& errMessage, int errorCode, const QList<QNetworkReply::RawHeaderPair>& headers)
 
967
{
 
968
  Logger::instance()->log("Setting volume failed " + 
 
969
    QString::number(errorCode) + " " + errMessage);
 
970
  if(isTicketAuthError(errorCode, headers)){
 
971
    Logger::instance()->log("Got the ticket-hash challenge");
 
972
    reauthActions.insert(SET_CURRENT_VOLUME);
 
973
    initReauth();
 
974
  }
 
975
}
 
976
 
 
977
 
 
978
 
 
979
 
 
980
 
 
981
void DataStore::onReauth(const QByteArray& ticketHash, const user_id_t& userId){
 
982
  Logger::instance()->log("in on reauth");
 
983
  isReauthing=false;
 
984
  serverConnection->setTicket(ticketHash);
 
985
  serverConnection->setUserId(userId);
 
986
 
 
987
  Q_FOREACH(ReauthAction r, reauthActions){
 
988
    doReauthAction(r);
 
989
  }
 
990
 
 
991
  reauthActions.clear();
 
992
}
 
993
 
 
994
void DataStore::doReauthAction(const ReauthAction& action){
 
995
  switch(action){
 
996
    case SYNC_LIB:
 
997
      syncLibrary();
 
998
      break;
 
999
    case GET_ACTIVE_PLAYLIST:
 
1000
      refreshActivePlaylist();
 
1001
      break;
 
1002
    case SET_CURRENT_SONG:
 
1003
      if(currentSongId != -1){
 
1004
        serverConnection->setCurrentSong(currentSongId);
 
1005
      }
 
1006
      break;
 
1007
    case MOD_PLAYLIST:
 
1008
      serverConnection->modActivePlaylist(playlistIdsToAdd, playlistIdsToRemove);
 
1009
      break;
 
1010
    case SET_CURRENT_VOLUME:
 
1011
      QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
1012
      serverConnection->setVolume((int)(getPlayerVolume() * 10));
 
1013
      break;
 
1014
  }
 
1015
}
 
1016
 
 
1017
void DataStore::onAuthFail(const QString& /*errMessage*/){
 
1018
  isReauthing=false;
 
1019
  Logger::instance()->log("BAD STUFF, BAD AUTH CREDS, BAD REAUTH");
 
1020
  //TODO need to do something here
 
1021
}
 
1022
 
 
1023
void DataStore::initReauth(){
 
1024
  if(!isReauthing){
 
1025
    isReauthing=true;
 
1026
    serverConnection->authenticate(getUsername(), getPassword());
 
1027
  }
 
1028
}
 
1029
 
 
1030
QByteArray DataStore::getHeaderValue(
 
1031
    const QByteArray& headerName,
 
1032
    const QList<QNetworkReply::RawHeaderPair>& headers)
 
1033
{
 
1034
  //Yes yes, I know this is an O(n) search. But it's fine. 
 
1035
  //This list of headers shouldn't be that long.
 
1036
  Q_FOREACH(const QNetworkReply::RawHeaderPair& pair, headers){
 
1037
    if(headerName == pair.first){
 
1038
      return pair.second;
 
1039
    }
 
1040
  }
 
1041
  return "";
 
1042
}
 
1043
 
 
1044
 
 
1045
bool DataStore::getDontShowPlaybackErrorSetting(){
 
1046
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
1047
  return settings.value(getDontShowPlaybackErrorSettingName(), false ).toBool();
 
1048
}
 
1049
 
 
1050
void DataStore::setDontShowPlaybackError(bool checked){
 
1051
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
1052
  settings.setValue(getDontShowPlaybackErrorSettingName(), checked);
 
1053
}
 
1054
 
 
1055
 
 
1056
void DataStore::saveCredentials(
 
1057
  const QString& username, const QString& password)
 
1058
{
 
1059
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
1060
  SimpleCrypt crypt = Utils::getCryptoObject();
 
1061
  QString cryptUsername = crypt.encryptToString(username);
 
1062
  QString cryptPassword = crypt.encryptToString(password);
 
1063
  settings.setValue(getHasValidCredsSettingName(), true);
 
1064
  settings.setValue(getUsernameSettingName(), cryptUsername);
 
1065
  settings.setValue(getPasswordSettingName(), cryptPassword);
 
1066
}
 
1067
 
 
1068
void DataStore::setCredentialsDirty(){
 
1069
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
1070
  settings.setValue(getHasValidCredsSettingName(), false);
 
1071
}
 
1072
 
 
1073
bool DataStore::hasValidSavedCredentials(){
 
1074
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
1075
  return settings.value(getHasValidCredsSettingName()).toBool();
 
1076
}
 
1077
 
 
1078
void DataStore::getSavedCredentials(QString* username, QString* password){
 
1079
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
1080
  SimpleCrypt crypt = Utils::getCryptoObject();
 
1081
  QString encryptedUsername = settings.value(getUsernameSettingName()).toString();
 
1082
  QString encryptedPassword = settings.value(getPasswordSettingName()).toString();
 
1083
  *username = crypt.decryptToString(encryptedUsername);
 
1084
  *password = crypt.decryptToString(encryptedPassword);
 
1085
}
 
1086
 
 
1087
void DataStore::clearSavedCredentials(){
 
1088
  QSettings settings(QSettings::UserScope, getSettingsOrg(), getSettingsApp());
 
1089
  settings.setValue(getHasValidCredsSettingName(), false);
 
1090
  settings.setValue(getUsernameSettingName(), "");
 
1091
  settings.setValue(getPasswordSettingName(), "");
 
1092
}
 
1093
 
 
1094
 
 
1095
 
 
1096
} //end namespace