Playlist tweaks. remove dead / orphaned nodes that vlc doesn't clean up in teh xml.
[vlc-remote] / playlistmainwindow.cpp
1 /*   VLC-REMOTE for MAEMO 5
2  *   Copyright (C) 2010 Schutz Sacha <istdasklar@gmail.com>, Dru Moore <usr@dru-id.co.uk>, Yann Nave <yannux@onbebop.net>
3  *   This program is free software; you can redistribute it and/or modify
4  *   it under the terms of the GNU General Public License version 2,
5  *   or (at your option) any later version, as published by the Free
6  *   Software Foundation
7  *
8  *   This program is distributed in the hope that it will be useful,
9  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *   GNU General Public License for more details
12  *
13  *   You should have received a copy of the GNU General Public
14  *   License along with this program; if not, write to the
15  *   Free Software Foundation, Inc.,
16  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18 #include "playlistmainwindow.h"
19 #include "ui_playlistmainwindow.h"
20 #include <QPushButton>
21 #include <QSettings>
22 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
23 #include <QMaemo5InformationBox>
24 #endif
25 #include "configdialog.h"
26 #include "aboutdialog.h"
27 #include "appsettings.h"
28 #include "vlcstatus.h"
29
30 PlayListMainWindow::PlayListMainWindow(QWidget *parent) :
31         QMainWindow(parent),
32         ui(new Ui::PlayListMainWindow)
33 {
34
35     ui->setupUi(this);
36     mTimer = new QTimer(this);
37     setWindowTitle("Vlc remote");
38
39     mCurrentDepth = 0;
40     mCurrentVlcIndex = 0;
41
42
43     mNetManager = new QNetworkAccessManager(this);
44
45     mContents = new QList<VlcPlayListElementSimple>();
46
47     ui->listWidget->setTextElideMode(Qt::ElideLeft);
48     ui->listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
49
50     ui->playButton->setIcon(QIcon::fromTheme("camera_playback"));
51     ui->clearButton->setIcon(QIcon::fromTheme("general_delete"));
52     ui->shuffleButton->setIcon(QIcon::fromTheme("mediaplayer_default_shuffle"));
53     ui->loopButton->setIcon(QIcon::fromTheme("general_refresh"));
54     ui->repeatButton->setIcon(QIcon::fromTheme("general_redo"));
55     ui->removeButton->setIcon(QIcon::fromTheme("general_close"));
56
57     ui->clearButton->setDisabled(false);
58     ui->shuffleButton->setDisabled(false);
59     ui->loopButton->setDisabled(false);
60     ui->repeatButton->setDisabled(false);
61     ui->removeButton->setDisabled(true);
62     ui->playButton->setDisabled(true);
63
64     connect(ui->playButton,SIGNAL(clicked()),this,SLOT(onPlay()));
65     connect(ui->removeButton,SIGNAL(clicked()),this,SLOT(onRemove()));
66     connect(ui->repeatButton,SIGNAL(clicked()),this,SLOT(onRepeat()));
67     connect(ui->loopButton,SIGNAL(clicked()),this,SLOT(onLoop()));
68     connect(ui->shuffleButton,SIGNAL(clicked()),this,SLOT(onShuffle()));
69     connect(ui->clearButton,SIGNAL(clicked()),this,SLOT(onClear()));
70     connect(ui->listWidget, SIGNAL(itemSelectionChanged()), this, SLOT(onListSelectionChanged()));
71
72     init();
73
74 }
75 void PlayListMainWindow::init()  // CALL WHEN CONFIG CHANGES
76 {
77     mIp = AppSettings::getCurrentIp(); // AccountDialog::currentIp();
78 }
79 void PlayListMainWindow::showPlayList()  // CALL WHEN SHOWN
80 {
81     requestPlayList();
82 }
83
84 PlayListMainWindow::~PlayListMainWindow()
85 {
86     delete ui;
87 }
88
89 void PlayListMainWindow::changeEvent(QEvent *e)
90 {
91     QMainWindow::changeEvent(e);
92     switch (e->type()) {
93     case QEvent::LanguageChange:
94         ui->retranslateUi(this);
95         break;
96     default:
97         break;
98     }
99 }
100
101 void PlayListMainWindow::onListSelectionChanged() {
102     QList<QListWidgetItem *> items = ui->listWidget->selectedItems();
103     if (0 < items.count()) {
104         mCurrentElement = getElementFromText(items.at(0)->text());
105         mCurrentVlcIndex = items.at(0)->type() - LIST_ITEM_TYPE_OFFSET; // Qt reserves types up to 1000, we use an offset beyond that for index tracking. May prove to be too hacky!
106         ui->removeButton->setDisabled(false);
107         ui->playButton->setDisabled(false);
108     }
109     else {
110         mCurrentVlcIndex = 0;
111         ui->removeButton->setDisabled(true);
112         ui->playButton->setDisabled(true);
113     }
114 }
115
116 void PlayListMainWindow::onRemove() {
117     if (0 < this->mCurrentVlcIndex) {
118         /*QNetworkReply * reply = */ mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_delete&id=" + QString::number(this->mCurrentVlcIndex))));
119         connect(mNetManager,SIGNAL(finished(QNetworkReply *)),this,SLOT(requestPlayList()));
120     }
121 }
122 void PlayListMainWindow::onPlay() {
123     if (0 < this->mCurrentVlcIndex) {
124         /*QNetworkReply * reply = */ mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_play&id=" + QString::number(this->mCurrentVlcIndex))));
125     }
126 }
127 void PlayListMainWindow::onRepeat() {
128     /*QNetworkReply * reply = */ mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_repeat")));
129 }
130 void PlayListMainWindow::onLoop() {
131     /*QNetworkReply * reply = */ mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_loop")));
132 }
133 void PlayListMainWindow::onShuffle() {
134     /*QNetworkReply * reply = */ mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_random")));
135 }
136 void PlayListMainWindow::onClear() {
137     /*QNetworkReply * reply = */ mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_empty")));
138     connect(mNetManager,SIGNAL(finished(QNetworkReply *)),this,SLOT(requestPlayList()));
139 }
140 void PlayListMainWindow::requestPlayList() {
141   mContents->clear();
142   ui->listWidget->clear();
143   mResponse.clear();
144   ui->removeButton->setDisabled(true);
145   ui->playButton->setDisabled(true);
146 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
147     this->setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true);
148 #endif
149   QNetworkReply * reply =  mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/playlist.xml")));
150   disconnect(mNetManager,SIGNAL(finished(QNetworkReply *)),this,SLOT(requestPlayList()));
151   connect(reply,SIGNAL(readyRead()),this,SLOT(readReady()));
152   connect(reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(error(QNetworkReply::NetworkError)));
153   connect(mNetManager,SIGNAL(finished(QNetworkReply *)),this,SLOT(finished(QNetworkReply *)));
154 }
155 void PlayListMainWindow::error(QNetworkReply::NetworkError code) {
156 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
157     this->setAttribute(Qt::WA_Maemo5ShowProgressIndicator, false);
158 #endif
159     qDebug() << code;
160 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
161     QMaemo5InformationBox::information(this, tr("Playlist could not be retrieved."), QMaemo5InformationBox::DefaultTimeout);
162 #endif
163 }
164 void PlayListMainWindow::readReady() {
165   QNetworkReply * reply = qobject_cast<QNetworkReply*>(sender());
166   // append to buffer
167   mResponse += reply->readAll();
168 }
169 void PlayListMainWindow::finished(QNetworkReply * reply) {
170   // now we can call parseXmlList to process the full buffers
171   this->parseXmlPlayList();
172   // only interested in finished signals
173   disconnect(mNetManager,SIGNAL(finished(QNetworkReply *)),this,SLOT(finished(QNetworkReply *)));
174 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
175     this->setAttribute(Qt::WA_Maemo5ShowProgressIndicator, false);
176 #endif
177 }
178
179 void PlayListMainWindow::parseXmlPlayList() {
180   QDomDocument doc;
181   doc.setContent(this->mResponse);
182   QDomElement docElem = doc.documentElement();
183   QDomNodeList nodes = docElem.elementsByTagName("node");
184   int depth = 0;
185
186   int currentLeafId = 0;
187   bool hasArt = false;
188   QString extension = "";
189
190   int ct = nodes.count();
191   for (int idx = 0; idx < ct; ++idx) {
192     QDomNode node = nodes.at(idx);
193     QString current = "";
194     //QString name = node.attributes().namedItem("name").nodeValue().replace("\\\\", "\\");
195     int id = node.attributes().namedItem("id").nodeValue().toInt();
196     if (4 > id && 0 == QString::compare(node.attributes().namedItem("ro").nodeValue(), "ro")) {
197       // got the main playlist, let's build it up
198       if (node.hasChildNodes()) {
199         QDomNodeList leafs = node.childNodes();
200         int leafct = leafs.count();
201         if (0 < leafct) {
202           depth = 1;
203           for (int jdx = 0; jdx < leafct; ++jdx) {
204             QDomNode leaf = leafs.at(jdx);
205             parsePlayListItem(&leaf, &extension, &hasArt, &currentLeafId, 1);
206             /*
207             VlcPlayListElementSimple* el = new VlcPlayListElementSimple();
208             el->id = leaf.attributes().namedItem("id").nodeValue().toInt();
209             el->path = leaf.attributes().namedItem("uri").nodeValue();
210             el->name = leaf.attributes().namedItem("name").nodeValue().replace("\\\\", "\\");
211             current = leaf.attributes().namedItem("current").nodeValue();
212             el->playing = (0 < current.length());
213             el->depth = depth;
214             if (0 == QString::compare(leaf.nodeName(), "node")) {
215               el->type = "node";
216               el->extension = getExtension(el->path, NULL);
217               this->mContents->append(*el);
218               // now parse the child nodes as leafs.
219               if (leaf.hasChildNodes()) {
220                 QDomNodeList items = leaf.childNodes();
221                 int itemct = items.count();
222                 if (0 < itemct) {
223                   depth = 2;
224                   for (int kdx = 0; kdx < itemct; ++kdx) {
225                     QDomNode item = items.at(kdx);
226                     VlcPlayListElementSimple* it = new VlcPlayListElementSimple();
227                     it->id = item.attributes().namedItem("id").nodeValue().toInt();
228                     it->path = item.attributes().namedItem("uri").nodeValue();
229                     it->name = item.attributes().namedItem("name").nodeValue().replace("\\\\", "\\");
230                     it->extension = getExtension(it->path, NULL);
231                     it->depth = 2;
232                     it->type = "leaf";
233                     current = item.attributes().namedItem("current").nodeValue();
234                     it->playing = (0 < current.length());
235                     if (it->playing) {
236                         currentLeafId = it->id;
237                         QString art = item.toElement().namedItem("art_url").toElement().text();
238                         hasArt = (!art.isNull() && !art.isEmpty());
239                         extension = getExtension(it->path, NULL);
240                     }
241                     this->mContents->append(*it);
242                     delete it;
243                   }
244                 }
245               }
246             }
247             else {
248               el->type = "leaf";
249               el->extension = getExtension(el->path, NULL);
250               if (el->playing) {
251                   currentLeafId = el->id;
252                   QString art = leaf.toElement().namedItem("art_url").toElement().text();
253                   hasArt = (!art.isNull() && !art.isEmpty());
254                   extension = getExtension(el->path, NULL);
255               }
256               this->mContents->append(*el);
257             }
258             delete el;
259             */
260           }
261         }
262       }
263
264     }
265   }
266
267   mResponse.clear();
268
269   emit this->idUpdated(currentLeafId, hasArt, extension);
270   this->updateList();
271
272
273 }
274
275
276 void PlayListMainWindow::parsePlayListItem(QDomNode *node, QString *extension, bool *hasArt, int *currentLeafId, int depth) {
277     if (NULL != node) {
278         QString current;
279         VlcPlayListElementSimple* el = new VlcPlayListElementSimple();
280         el->id = node->attributes().namedItem("id").nodeValue().toInt();
281         el->path = node->attributes().namedItem("uri").nodeValue();
282         el->name = node->attributes().namedItem("name").nodeValue().replace("\\\\", "\\");
283         current = node->attributes().namedItem("current").nodeValue();
284         el->playing = (0 < current.length());
285         el->depth = depth;
286         if (0 != QString::compare(node->nodeName(), "node")) {
287             el->type = "leaf";
288             el->extension = getExtension(el->path, NULL);
289             if (el->playing) {
290                 *currentLeafId = el->id;
291                 QString art = node->toElement().namedItem("art_url").toElement().text();
292                 *hasArt = (!art.isNull() && !art.isEmpty());
293                 *extension = getExtension(el->path, NULL);
294             }
295             this->mContents->append(*el);
296             delete el;
297         }
298         else {
299             el->type = "node";
300             el->extension = getExtension(el->path, NULL);
301             // empty nodes appear in the playlist when they can't be played!
302             if (node->hasChildNodes()) {
303                 this->mContents->append(*el);
304             }
305             delete el;
306             // now parse the child nodes as leaf.
307             if (node->hasChildNodes()) {
308                 QDomNodeList items = node->childNodes();
309                 int itemct = items.count();
310                 if (0 < itemct) {
311                   ++depth;
312                   for (int kdx = 0; kdx < itemct; ++kdx) {
313                     QDomNode item = items.at(kdx);
314                     parsePlayListItem(&item, extension, hasArt, currentLeafId, depth);
315                   }
316                 }
317             }
318         }
319     }
320 }
321
322 QString PlayListMainWindow::getExtension(QString path, QString extension) {
323     // return extension if exists
324     if (!extension.isNull() && !extension.isEmpty()) return extension;
325     // return blank if no path
326     if (path.isNull() || path.isEmpty()) return "";
327     // otherwise extract the extension
328     int dot_pos = path.lastIndexOf('.');
329     if (0 < dot_pos) {
330         return path.right(path.length() - (dot_pos + 1));
331     }
332     else { // no dot
333         return "";
334     }
335 }
336
337 VlcPlayListElementSimple PlayListMainWindow::getElementFromText(QString text) {
338   //if (0 != QString::compare("", text)) {
339     for (int idx = 0; idx < mContents->count(); ++idx) {
340       if (0 == QString::compare(text, mContents->at(idx).name)) {
341         return mContents->at(idx);
342       }
343     }
344     //}
345     return *(new VlcPlayListElementSimple());
346 }
347
348 void PlayListMainWindow::updateList() {
349   int ct = this->mContents->count();
350   if (0 < ct) {
351     QIcon icon_audio  = QIcon::fromTheme("general_audio_file");
352     QIcon icon_video  = QIcon::fromTheme("general_video_file");
353     QIcon icon_image  = QIcon::fromTheme("general_image");
354     QIcon icon_flash  = QIcon::fromTheme("filemanager_flash_file");
355     QIcon icon_media  = QIcon::fromTheme("filemanager_media_folder");
356     QIcon icon_real   = QIcon::fromTheme("filemanager_real_music");
357     QIcon icon_unknown= QIcon::fromTheme("filemanager_unknown_file");
358     for (int idx = 0; idx < ct; ++idx) {
359       VlcPlayListElementSimple el = mContents->at(idx);
360       QListWidgetItem* item;
361       if (0 == QString::compare("node", el.type)) {
362         item = new QListWidgetItem(icon_media, el.name, ui->listWidget, LIST_ITEM_TYPE_OFFSET + el.id);
363       }
364       else {
365           if ( 0 == QString::compare(el.extension, "jpg")  ||
366                0 == QString::compare(el.extension, "jpeg") ||
367                0 == QString::compare(el.extension, "gif")  ||
368                0 == QString::compare(el.extension, "png")  ||
369                0 == QString::compare(el.extension, "bmp")  ) {
370               item = new QListWidgetItem(icon_image, el.name, ui->listWidget, LIST_ITEM_TYPE_OFFSET + el.id); // .jpg, .jpeg, .gif, .png, .bmp
371           }
372           else if ( 0 == QString::compare(el.extension, "mp3")  ||
373                     0 == QString::compare(el.extension, "m4a")  ||
374                     0 == QString::compare(el.extension, "ogg")  ||
375                     0 == QString::compare(el.extension, "oga")  ||
376                     0 == QString::compare(el.extension, "wav")  ||
377                     0 == QString::compare(el.extension, "flac")  ) {
378               item = new QListWidgetItem(icon_audio, el.name, ui->listWidget, LIST_ITEM_TYPE_OFFSET + el.id); // .mp3, .m4a, .ogg, .oga, .wav, .flac
379           }
380           else if ( 0 == QString::compare(el.extension, "avi")  ||
381                     0 == QString::compare(el.extension, "mpeg") ||
382                     0 == QString::compare(el.extension, "mpg")  ||
383                     0 == QString::compare(el.extension, "mov")  ||
384                     0 == QString::compare(el.extension, "mp4")  ||
385                     0 == QString::compare(el.extension, "m4v")  ||
386                     0 == QString::compare(el.extension, "wmv")  ||
387                     0 == QString::compare(el.extension, "mkv")  ||
388                     0 == QString::compare(el.extension, "ogv")  ) {
389               item = new QListWidgetItem(icon_video, el.name, ui->listWidget, LIST_ITEM_TYPE_OFFSET + el.id); // .avi, .mpg, .mpeg, .mov, .m4v, .mp4, .wmv, .mkv, .ogv
390           }
391           else if ( 0 == QString::compare(el.extension, "rm")  ||
392                     0 == QString::compare(el.extension, "ra")  ||
393                     0 == QString::compare(el.extension, "ram")  ) {
394               item = new QListWidgetItem(icon_real, el.name, ui->listWidget, LIST_ITEM_TYPE_OFFSET + el.id); // .ram, 'rm, 'ra
395           }
396           else if ( 0 == QString::compare(el.extension, "flv")  ) {
397               item = new QListWidgetItem(icon_flash, el.name, ui->listWidget, LIST_ITEM_TYPE_OFFSET + el.id); // .flv
398           }
399           //else if ( 0 == QString::compare(el.extension, "")  ) {
400           //    item = new QListWidgetItem(icon_unknown, el.name, ui->listWidget, LIST_ITEM_TYPE_OFFSET + el.id); // .flv
401           //}
402           else {
403               if (el.name.contains("Flash")) {
404                   item = new QListWidgetItem(icon_flash, el.name, ui->listWidget, LIST_ITEM_TYPE_OFFSET + el.id);
405               }
406               else {
407                   item = new QListWidgetItem(icon_media, el.name, ui->listWidget, LIST_ITEM_TYPE_OFFSET + el.id);
408               }
409           }
410       }
411       item->setSelected(el.playing);
412       ui->listWidget->addItem(item);
413       if (el.playing) {
414           ui->listWidget->scrollToItem(item, QAbstractItemView::PositionAtCenter);
415       }
416     }
417   }
418 }
419 void PlayListMainWindow::updateUiWithCurrentStatus(VlcStatus * status) {
420     ui->loopButton->setChecked(status->loop);
421     ui->repeatButton->setChecked(status->repeat);
422     ui->shuffleButton->setChecked(status->random);
423 }