defuze.me  Client
playqueue.cpp
00001 /**************************************************************************
00002 ** defuze.me Epitech Innovative Project
00003 **
00004 ** Copyright 2010
00005 **   Athena Calmettes - Jocelyn De La Rosa - Francois Gaillard
00006 **   Adrien Jarthon - Alexandre Moore - Luc Peres - Arnaud Sellier
00007 **
00008 ** All rights reserved.
00009 **************************************************************************/
00010 
00011 #include "playqueue.hpp"
00012 #include "audiotrack.hpp"
00013 #include "queuebreak.hpp"
00014 #include "queuetrack.hpp"
00015 #include "playqueuewidget.hpp"
00016 #include "jsonparser.hpp"
00017 #include "exception.hpp"
00018 #include "scheduler.hpp"
00019 #include "listsplugin.hpp"
00020 #include <QSqlQuery>
00021 #include <QSqlError>
00022 #include <QDebug>
00023 #include <QDateTime>
00024 #include <algorithm>
00025 
00026 using namespace Queue;
00027 
00028 PlayQueue::PlayQueue(QObject *parent) :
00029     QAbstractListModel(parent), altering(false)
00030 {
00031     widget = new PlayQueueWidget(this);
00032     connect(widget, SIGNAL(deleteElements()), SLOT(removeSelected()));
00033     connect(widget, SIGNAL(clearListFocus()), SLOT(closeControls()));
00034     uiModule = Gui::ModuleFactory::create("PlayQueue", QPoint(1, 1), widget);
00035     uiModule->setSizePolicy(QSizePolicy::MinimumExpanding);
00036     uiModule->submitForDisplay();
00037     populateFromDB();
00038     updateTimes();
00039     selection = widget->selectionModel();
00040 //  connect(widget->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(updateSelection(QItemSelection,QItemSelection)));
00041 //  connect(widget->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), SLOT(updateFocus(QModelIndex, QModelIndex)));
00042     QTimer  *updateTimer = new QTimer(this);
00043     updateTimer->start(10000);
00044     connect(updateTimer, SIGNAL(timeout()), SLOT(updateTimes()));
00045     connect(updateTimer, SIGNAL(timeout()), widget, SLOT(update()));
00046 }
00047 
00048 PlayQueue::~PlayQueue()
00049 {
00050     delete widget;
00051     while (!queue.empty())
00052     {
00053         Queueable   *elem = queue.back();
00054         delete elem;
00055         queue.pop_back();
00056     }
00057 }
00058 
00059 void    PlayQueue::aboutToQuit()
00060 {
00061     QueueableDeque::iterator    it;
00062     for (it = queue.begin(); it != queue.end(); it++)
00063     {
00064         Queueable   *elem = (*it);
00065         elem->saveQueueAttributes();
00066     }
00067 }
00068 
00069 void PlayQueue::init()
00070 {
00071     scheduler = plugins->cast<Scheduler::SchedulerPlugin>("scheduler");
00072 
00073     connect(scheduler, SIGNAL(newEvent(Scheduler::EventModel*)), SLOT(newEvent(Scheduler::EventModel*)));
00074     connect(scheduler, SIGNAL(updateEvent(Scheduler::EventModel*)), SLOT(updateEvent(Scheduler::EventModel*)));
00075     connect(scheduler, SIGNAL(removeEvent(Scheduler::EventModel*)), SLOT(removeEvent(Scheduler::EventModel*)));
00076     connect(scheduler, SIGNAL(loadEvent(Scheduler::EventModel*)), SLOT(newEvent(Scheduler::EventModel*)));
00077     emit initialized();
00078 }
00079 
00080 Scheduler::SchedulerPlugin* PlayQueue::getSchedulerPlugin() const
00081 {
00082     return scheduler;
00083 }
00084 
00085 const QueueableDeque&   PlayQueue::getQueue() const
00086 {
00087     return queue;
00088 }
00089 
00090 void        PlayQueue::populateFromDB()
00091 {
00092     QSqlQuery query("SELECT position, queueable_type, queueable_id, attributes FROM queue ORDER BY position ASC");
00093     if (!query.exec())
00094         throw_exception(0x01, tr("Can't load play queue: %1").arg(query.lastError().text()));
00095     int position = 0;
00096     while (query.next()) {
00097         Queueable   *elem = NULL;
00098         int         DBposition = query.value(0).toInt();
00099         int         id = query.value(2).toInt();
00100         QString     type = query.value(1).toString();
00101 
00102         // TODO: Validate position errors
00103         if (type == "QueueTrack")
00104         {
00105             Library::AudioTrack *track = Library::AudioTrack::getTrack(id);
00106             if (track)
00107             {
00108                 elem = new QueueTrack(*track);
00109                 connect(track, SIGNAL(removed(Library::AudioTrack*)), SLOT(removeAll(Library::AudioTrack*)));
00110             }
00111         }
00112         else if (type == "QueueBreak")
00113             elem = new QueueBreak();
00114         else
00115             qDebug() << "Unknow queue elem type: " << type;
00116 
00117         if (elem)
00118         {
00119             elem->attributes = Network::JsonParser().parse(query.value(3).toByteArray()).toMap();
00120             elem->position = position;
00121             if (DBposition != position)
00122                 elem->correctPosition(DBposition);
00123             connect(elem, SIGNAL(remove(Queueable*)), SLOT(removeOne(Queueable*)));
00124             beginInsertRows(QModelIndex(), queue.size(), queue.size());
00125             queue.push_back(elem);
00126             endInsertRows();
00127             position++;
00128         }
00129         else
00130         {
00131             Queueable::remove(DBposition);
00132         }
00133     }
00134 //  QueueBreak  *b = new QueueBreak;
00135 //  b->setEnd(QDateTime::currentDateTime().addSecs(6600));
00136 //  add(b, 5);
00137 //  for(int i = 6; i < 11; i++)
00138 //      queue[i]->setEvent(4);
00139 }
00140 
00141 Queueable*  PlayQueue::head() const
00142 {
00143     if (queue.size() > 0)
00144         return queue[0];
00145     else
00146         return NULL;
00147 }
00148 
00149 Queueable*  PlayQueue::next() const
00150 {
00151     if (queue.size() > 1)
00152         return queue[1];
00153     else
00154         return NULL;
00155 }
00156 
00157 Queueable*  PlayQueue::nextNext() const
00158 {
00159     if (queue.size() > 2)
00160         return queue[2];
00161     else
00162         return NULL;
00163 }
00164 
00165 void        PlayQueue::pop()
00166 {
00167     QSqlQuery   query;
00168     query.exec("BEGIN");
00169     remove(0);
00170     query.exec("END");
00171     emit popQueue();
00172     emitAltered();
00173 }
00174 
00175 void        PlayQueue::shiftDBPositions(int pos, int shift)
00176 {
00177     QSqlQuery   query;
00178     bool        failed = false;
00179     //query.exec("BEGIN");
00180     //QString       sql = "";
00181     query.prepare("UPDATE queue SET position = position + :shift WHERE position = :position");
00182     if (shift > 0)
00183     {
00184         for (int p = queue.size() - 1; p >= pos; p--)
00185 //          sql += QString("UPDATE queue SET position = position + %1 WHERE position = %2; ").arg(shift).arg(p);
00186         {
00187             query.bindValue(":position", p);
00188             query.bindValue(":shift", shift);
00189             failed |= !query.exec();
00190         }
00191     }
00192     else if (shift < 0)
00193     {
00194         for (unsigned p = pos; p < queue.size(); p++)
00195 //          sql += QString("UPDATE queue SET position = position + %1 WHERE position = %2; ").arg(shift).arg(p);
00196         {
00197             query.bindValue(":position", p);
00198             query.bindValue(":shift", shift);
00199             failed |= !query.exec();
00200         }
00201     }
00202     //sql += "";
00203     //query.exec("COMMIT");
00204     if (failed)
00205         throw_exception(0x02, tr("Can't shift queue position: %1").arg(query.lastError().databaseText()));
00206 }
00207 
00208 int         PlayQueue::rowCount(const QModelIndex &) const
00209 {
00210     return queue.size();
00211 }
00212 
00213 void        PlayQueue::updateFocus(const QModelIndex &current, const QModelIndex &previous)
00214 {
00215     Q_UNUSED(current);
00216     Q_UNUSED(previous);
00217     /*
00218     if (current.isValid())
00219         queue[current.row()]->showControls();
00220     if (previous.isValid())
00221         queue[previous.row()]->hideControls();*/
00222 }
00223 
00224 void        PlayQueue::closeControls()
00225 {
00226     /*
00227     if (widget->selectionModel()->currentIndex().isValid())
00228         queue[widget->selectionModel()->currentIndex().row()]->hideControls();*/
00229 }
00230 
00231 QVariant    PlayQueue::data(const QModelIndex &index, int role) const
00232 {
00233     if (role == Qt::DisplayRole)
00234         return qVariantFromValue((void*)queue[index.row()]);
00235     else
00236         return QVariant();
00237 }
00238 
00239 Qt::ItemFlags   PlayQueue::flags(const QModelIndex &) const
00240 {
00241     return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
00242 }
00243 
00244 void        PlayQueue::updateTimes(int shift)
00245 {
00246     QDateTime                   t = QDateTime::currentDateTime();
00247     QueueableDeque::iterator    it;
00248     bool                        finite = true;
00249 
00250     t = t.addSecs(-shift);
00251     for (it = queue.begin(); it != queue.end(); it++)
00252     {
00253         Queueable   *elem = (*it);
00254         if (finite)
00255         {
00256             elem->setPlayTime(t);
00257             if (elem->queueIsFinite())
00258             {
00259                 t = t.addSecs(elem->queueDuration(t));
00260             }
00261             else
00262                 finite = false;
00263         }
00264         else
00265             elem->setPlayTime(QDateTime());
00266     }
00267 }
00268 
00269 void        PlayQueue::updatePositions()
00270 {
00271     for (unsigned pos = 0; pos < queue.size(); pos++)
00272         queue[pos]->position = pos;
00273 }
00274 
00275 QWidget*    PlayQueue::getWidget()
00276 {
00277     return widget;
00278 }
00279 
00280 void        PlayQueue::add(Queueable *elem, int position)
00281 {
00282     if (position == -1)
00283     {
00284         beginInsertRows(QModelIndex(), queue.size(), queue.size());
00285         elem->queueAt(queue.size());
00286         queue.push_back(elem);
00287     }
00288     else
00289     {
00290         beginInsertRows(QModelIndex(), position, position);
00291         shiftDBPositions(position, +1);
00292         elem->queueAt(position);
00293         queue.insert(queue.begin() + position, elem);
00294         updatePositions();
00295     }
00296     if (elem->isTrack())
00297     {
00298         QueueTrack *track = static_cast<QueueTrack*>(elem);
00299         connect(track->getTrack(), SIGNAL(removed(Library::AudioTrack*)), SLOT(removeAll(Library::AudioTrack*)));
00300     }
00301     connect(elem, SIGNAL(remove(Queueable*)), SLOT(removeOne(Queueable*)));
00302     endInsertRows();
00303     updateTimes();
00304     emit addQueueElem(elem);
00305 }
00306 
00307 void        PlayQueue::add(Container *elem, int position)
00308 {
00309     throw_exception(0x03, tr("PlayQueue::add(Container *elem, int position) is not implemented yet"));
00310     if (position == -1)
00311         queue.insert(queue.end(), elem->getChildren().begin(), elem->getChildren().end());
00312     else
00313         queue.insert(queue.begin() + position, elem->getChildren().begin(), elem->getChildren().end());
00314 }
00315 
00316 void        PlayQueue::remove(int position)
00317 {
00318     Queueable   *elem;
00319     QSqlQuery   query;
00320 
00321     if (position >= (int)queue.size() || position < 0)
00322         return;
00323     query.prepare("DELETE FROM queue WHERE position = :position");
00324     query.bindValue(":position", position);
00325     if (!query.exec())
00326         throw_exception(0x04, tr("Can't remove queueable: %1").arg(query.lastError().text()));
00327     beginRemoveRows(QModelIndex(), position, position);
00328     shiftDBPositions(position, -1);
00329     elem = queue.at(position);
00330     queue.erase(queue.begin() + position);
00331     updatePositions();
00332     emit removeQueueElem(elem);
00333     delete elem;
00334     updateTimes();
00335     endRemoveRows();
00336 }
00337 
00338 void        PlayQueue::remove(QList<int> positions)
00339 {
00340     QSqlQuery   query;
00341     query.exec("BEGIN");
00342     qSort(positions);
00343     for (int i = positions.size() - 1; i >= 0; --i)
00344         remove(positions[i]);
00345     query.exec("COMMIT");
00346 }
00347 
00348 void        PlayQueue::removeAll(Library::AudioTrack* track)
00349 {
00350     QList<int>  positions;
00351     QueueableDeque::iterator    it;
00352     for (it = queue.begin(); it != queue.end(); it++)
00353     {
00354         Queueable   *elem = *it;
00355         if (elem->isTrack() && elem->toQueueTrack()->getTrack() == track)
00356             positions << elem->toQueueTrack()->getPosition();
00357     }
00358     remove(positions);
00359 }
00360 
00361 void        PlayQueue::removeSelected()
00362 {
00363     QList<int>  positions;
00364     foreach(const QModelIndex &index, selection->selectedIndexes())
00365         positions << index.row();
00366     startAlteringALot();
00367     remove(positions);
00368     finishAlteringALot();
00369 }
00370 
00371 void        PlayQueue::removeOne(Queueable *elem)
00372 {
00373     if (queue[elem->getPosition()] == elem)
00374     {
00375         remove(elem->getPosition());
00376         emitAltered();
00377     }
00378     else
00379         qDebug() << "bad remove position";
00380 }
00381 
00382 void        PlayQueue::bulkMove(QList<int> positions, int destination)
00383 {
00384     qSort(positions);
00385     QSqlQuery   query;
00386 
00387     startAlteringALot();
00388     query.exec("BEGIN");
00389     // Get element's references
00390     QList<Queueable*>   queueables;
00391     foreach(int pos, positions)
00392         if (queue[pos])
00393         {
00394             qDebug() << "move " << queue[pos];
00395             queueables << queue[pos];
00396         }
00397 
00398     // Clone and insert
00399     int shift = 0;
00400     foreach(Queueable* queueable, queueables)
00401     {
00402         add(queueable->clone(), destination + shift);
00403         if (destination >= 0)
00404             shift++;
00405     }
00406 
00407     // Remove
00408     foreach(Queueable* queueable, queueables)
00409         removeOne(queueable);
00410     query.exec("COMMIT");
00411     finishAlteringALot();
00412 }
00413 
00414 void        PlayQueue::emitAltered()
00415 {
00416     emit altered();
00417 }
00418 
00419 QStringList     PlayQueue::mimeTypes() const
00420 {
00421     QStringList types;
00422     types << "application/x-defuzeme-audiotrack";
00423     return types;
00424 }
00425 
00426 QMimeData   *PlayQueue::mimeData(const QModelIndexList &indexes) const
00427 {
00428     QMimeData *mimeData = new QMimeData();
00429     QByteArray queueables, audioTracks;
00430 
00431     // Generate list of queueable positions (for local moves)
00432     QDataStream sQueueables(&queueables, QIODevice::WriteOnly);
00433     // Generate list of audiotrack id (for other widgets like playlist)
00434     QDataStream sAudioTracks(&audioTracks, QIODevice::WriteOnly);
00435     foreach (const QModelIndex &index, indexes)
00436     {
00437         if (index.isValid() && queue[index.row()])
00438         {
00439             sQueueables << index.row();
00440             if (queue[index.row()]->isTrack())
00441                 sAudioTracks << queue[index.row()]->toQueueTrack()->getTrack()->getUid();
00442         }
00443     }
00444     mimeData->setData("application/x-defuzeme-queueable", queueables);
00445     mimeData->setData("application/x-defuzeme-audiotrack", audioTracks);
00446     return mimeData;
00447 }
00448 
00449 
00450 bool PlayQueue::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
00451 {
00452     Q_UNUSED(action);
00453     Q_UNUSED(column);
00454     Q_UNUSED(parent);
00455 
00456     QByteArray  encodedData;
00457     bool        isQueueable = data->hasFormat("application/x-defuzeme-queueable");
00458 
00459     if (isQueueable)
00460         encodedData = data->data("application/x-defuzeme-queueable");
00461     else if (data->hasFormat("application/x-defuzeme-audiotrack"))
00462         encodedData = data->data("application/x-defuzeme-audiotrack");
00463     else
00464         return false;
00465 
00466     QDataStream stream(&encodedData, QIODevice::ReadOnly);
00467     QList<int> ids;
00468 
00469     while (!stream.atEnd()) {
00470         int id;
00471         stream >> id;
00472         ids << id;
00473     }
00474 
00475     if (isQueueable)
00476         bulkMove(ids, row);
00477     else
00478     {
00479         // When multiple tracks are dropped, increment position in order not to inverse them
00480         QSqlQuery   query;
00481         query.exec("BEGIN");
00482         int shift = 0;
00483         foreach(int id, ids)
00484         {
00485             qDebug() << "drop multiple tracks: " << id;
00486             Library::AudioTrack *track = Library::AudioTrack::getTrack(id);
00487             if (track)
00488             {
00489                 add(new Queue::QueueTrack(*track), row + shift);
00490                 shift++;
00491             }
00492         }
00493         query.exec("COMMIT");
00494         emitAltered();
00495     }
00496 
00497     return true;
00498 }
00499 
00500 bool    PlayQueue::isBeingAlteredALot() const
00501 {
00502     return altering;
00503 }
00504 
00505 void    PlayQueue::startAlteringALot()
00506 {
00507     if (isBeingAlteredALot())
00508         qDebug() << "Warning: recursive bulk altering on queue list!";
00509     else
00510         altering = true;
00511 }
00512 
00513 void    PlayQueue::finishAlteringALot()
00514 {
00515     if (isBeingAlteredALot())
00516     {
00517         altering = false;
00518         emitAltered();
00519     }
00520     else
00521         qDebug() << "Warning: finishing bulk altering not started on queue list!";
00522 }
00523 
00524 QList<int>  PlayQueue::findByEvent(Scheduler::EventModel *event)
00525 {
00526     QList<int>  elems;
00527     for(unsigned i = 0; i < queue.size(); i++)
00528         if (queue[i]->getEvent() == event->getId())
00529             elems << i;
00530     return elems;
00531 }
00532 
00533 void    PlayQueue::newEvent(Scheduler::EventModel* event)
00534 {
00535     QSqlQuery   query;
00536     qDebug() << "newEvent" << event->getTitle();
00537 
00538     // Check existing items
00539     if (!findByEvent(event).empty())
00540         return;
00541 
00542     // Find starting position
00543     QDateTime   start = event->nextInstance();
00544     unsigned    position = 0;
00545     while (position < queue.size() && queue[position]->getPlayTime() < start)
00546         position++;
00547     if (position > 0 && queue[position - 1]->isBreak())
00548         position--;
00549     // position is now just after event start
00550     // optionnally test if it's better to take the previous
00551 
00552     // Fetch tracks from playlists
00553     QList<int>  playlists = event->getPlaylists();
00554     QList<int>  tracks;
00555     foreach (int playlist, playlists)
00556         tracks.append(Lists::ListsPlugin::getTracksForNormalPlaylist(playlist));
00557 
00558     if (tracks.empty())
00559         return;
00560 
00561     startAlteringALot();
00562     query.exec("BEGIN");
00563     // Insert break if needed
00564     if (position >= queue.size() || queue[position]->getPlayTime() < start)
00565     {
00566         // best position is end of queue, insert break to fill
00567         add(new QueueBreak(start), position);
00568         position++;
00569     }
00570 
00571     // Random order
00572     std::random_shuffle(tracks.begin(), tracks.end());
00573 
00574     // Insert into queue
00575     int     duration = 0;
00576     int     t = 0;
00577     while (duration < event->getDuration() * 60)
00578     {
00579         Queueable   *track = new QueueTrack(*Library::AudioTrack::getTrack(tracks[t]));
00580         duration += track->queueDuration();
00581         track->setEvent(event->getId());
00582         add(track, position);
00583         position++;
00584         // Cycle through tracks
00585         t++;
00586         if (t >= tracks.size())
00587             t = 0;
00588     }
00589     query.exec("COMMIT");
00590     finishAlteringALot();
00591 }
00592 
00593 void    PlayQueue::updateEvent(Scheduler::EventModel* event)
00594 {
00595     qDebug() << "updateEvent" << event->getTitle();
00596 
00597     removeEvent(event);
00598     newEvent(event);
00599 }
00600 
00601 void    PlayQueue::removeEvent(Scheduler::EventModel* event)
00602 {
00603     qDebug() << "removeEvent" << event->getTitle();
00604 
00605     // Find event elements
00606     QList<int> elems = findByEvent(event);
00607     if (elems.empty())
00608         return;
00609 
00610     // Add optional break
00611     if (elems.first() > 0 && queue[elems.first() - 1]->isBreak())
00612         elems << elems.first() - 1;
00613 
00614     startAlteringALot();
00615     remove(elems);
00616     finishAlteringALot();
00617 }