diff --git a/CMakeLists.txt b/CMakeLists.txt index 27760a204..d8500ca4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ SET( CANTATA_SRCS gui/externalsettings.cpp gui/playbacksettings.cpp gui/serversettings.cpp + gui/httpserversettings.cpp gui/outputsettings.cpp gui/streamspage.cpp gui/streamdialog.cpp @@ -81,6 +82,8 @@ SET( CANTATA_SRCS libmaia/maiaFault.cpp libmaia/maiaXmlRpcClient.cpp devices/utils.cpp + http/httpserver.cpp + http/httpsocket.cpp ) SET( CANTATA_MOC_HDRS @@ -130,6 +133,7 @@ SET( CANTATA_MOC_HDRS libmaia/maiaObject.h libmaia/maiaFault.h libmaia/maiaXmlRpcClient.h + http/httpsocket.h ) SET( CANTATA_UIS @@ -144,6 +148,7 @@ SET( CANTATA_UIS gui/externalsettings.ui gui/playbacksettings.ui gui/serversettings.ui + gui/httpserversettings.ui gui/outputsettings.ui lyrics/lyricspage.ui lyrics/lyricsettings.ui @@ -160,6 +165,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/libmaia ${CMAKE_SOURCE_DIR}/lyrics ${CMAKE_SOURCE_DIR}/network ${CMAKE_SOURCE_DIR}/dbus + ${CMAKE_SOURCE_DIR}/http ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ) diff --git a/ChangeLog b/ChangeLog index fca81c542..bad70dbf2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,22 +1,25 @@ 0.5.0 ----- 1. Add ability to calculate replaygain tags. (KDE only) - 2. Add "Open File Manager" action to folder page context menu. (KDE only) - 3. Open local files passed on commandline - if using local socket. (KDE only) - 4. When loose MPD connection, indicate via message widget. - 5. Dont show base dir when editing tags of tracks on UMS device. - 6. In tag editor dialog, if a field in the 'All tracks' section has multiple + 2. Add a VERY basic http server so that we can play local files even when + connected to MPD from a non-local socket. + 3. Add "Open File Manager" action to folder page context menu. (KDE only) + 4. Open local files passed on commandline - if using local socket, or http + server. (KDE only) + 5. When loose MPD connection, indicate via message widget. + 6. Dont show base dir when editing tags of tracks on UMS device. + 7. In tag editor dialog, if a field in the 'All tracks' section has multiple values - set the placeholder text to '(Various)' - 7. Add a cmake option CANTATA_TRANSLATIONS, used to specify which translations + 8. Add a cmake option CANTATA_TRANSLATIONS, used to specify which translations should be built and installed. e.g. -DCANTATA_TRANSLATIONS="en;pl" - 8. Add some basic documentation. - 9. When playing, centre on current track. -10. Use correct value for transcoder parameter. -11. When saving cache file, if a track is filed under 'single artists' then + 9. Add some basic documentation. +10. When playing, centre on current track. +11. Use correct value for transcoder parameter. +12. When saving cache file, if a track is filed under 'single artists' then store its actual album name in the cache file. Then when cache is loaded, we have the correct album to use for the tag editor. -12. Fix crash when deleting streams. +13. Fix crash when deleting streams. 0.4.0 ----- diff --git a/devices/device.h b/devices/device.h index b3a359075..3fc4222de 100644 --- a/devices/device.h +++ b/devices/device.h @@ -185,6 +185,9 @@ public: bool abortRequested() const { return jobAbortRequested; } + virtual bool canPlaySongs() const { + return false; + } public Q_SLOTS: void setStatusMessage(const QString &message); diff --git a/devices/remotedevice.cpp b/devices/remotedevice.cpp index cf7aaf50d..d45238d90 100644 --- a/devices/remotedevice.cpp +++ b/devices/remotedevice.cpp @@ -27,6 +27,7 @@ #include "devicepropertieswidget.h" #include "actiondialog.h" #include "network.h" +#include "httpserver.h" #include #include #include @@ -395,6 +396,11 @@ void RemoteDevice::configure(QWidget *parent) : DevicePropertiesWidget::Prop_All); } +bool RemoteDevice::canPlaySongs() const +{ + return Prot_File==details.protocol || HttpServer::self()->isAlive(); +} + static inline QString toString(bool b) { return b ? QLatin1String("true") : QLatin1String("false"); diff --git a/devices/remotedevice.h b/devices/remotedevice.h index 7615af1e3..d08fa35b0 100644 --- a/devices/remotedevice.h +++ b/devices/remotedevice.h @@ -91,6 +91,7 @@ public: QString icon() const { return QLatin1String(Prot_File==details.protocol ? "inode-directory" : "network-server"); } + virtual bool canPlaySongs() const; Q_SIGNALS: void udiChanged(const QString &from, const QString &to); diff --git a/devices/umsdevice.h b/devices/umsdevice.h index efeaa8944..deb45c058 100644 --- a/devices/umsdevice.h +++ b/devices/umsdevice.h @@ -41,6 +41,9 @@ public: Type type() const { return Ums; } void saveOptions(); void configure(QWidget *parent); + virtual bool canPlaySongs() const { + return true; + } private: void setup(); diff --git a/gui/httpserversettings.cpp b/gui/httpserversettings.cpp new file mode 100644 index 000000000..67d665d9e --- /dev/null +++ b/gui/httpserversettings.cpp @@ -0,0 +1,45 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 Craig Drummond + * + * ---- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "httpserversettings.h" +#include "settings.h" + +HttpServerSettings::HttpServerSettings(QWidget *p) + : QWidget(p) +{ + setupUi(this); +}; + +void HttpServerSettings::load() +{ + enableHttp->setChecked(Settings::self()->enableHttp()); + alwaysUseHttp->setChecked(Settings::self()->alwaysUseHttp()); + httpPort->setValue(Settings::self()->httpPort()); +} + +void HttpServerSettings::save() +{ + Settings::self()->saveEnableHttp(enableHttp->isChecked()); + Settings::self()->saveAlwaysUseHttp(alwaysUseHttp->isChecked()); + Settings::self()->saveHttpPort(httpPort->value()); +} diff --git a/gui/httpserversettings.h b/gui/httpserversettings.h new file mode 100644 index 000000000..e7f98f23f --- /dev/null +++ b/gui/httpserversettings.h @@ -0,0 +1,39 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 Craig Drummond + * + * ---- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef HTTPSERVERSETTINGS_H +#define HTTPSERVERSETTINGS_H + +#include "ui_httpserversettings.h" + +class HttpServerSettings : public QWidget, private Ui::HttpServerSettings +{ +public: + HttpServerSettings(QWidget *p); + virtual ~HttpServerSettings() { } + + void load(); + void save(); +}; + +#endif diff --git a/gui/httpserversettings.ui b/gui/httpserversettings.ui new file mode 100644 index 000000000..537c838d4 --- /dev/null +++ b/gui/httpserversettings.ui @@ -0,0 +1,151 @@ + + + HttpServerSettings + + + + 0 + 0 + 384 + 254 + + + + + 0 + + + + + + + Enable HTTP server: + + + enableHttp + + + + + + + + + + + + + + Always use server: + + + alwaysUseHttp + + + + + + + + + + + + + + Port: + + + httpPort + + + + + + + 1 + + + 65535 + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 16 + + + + + + + + <i><b>NOTE:</b> MPD usually only plays songs that are stored within its folders. If you have connected via a local socket, then MPD can also play files located on your filesystem. If you are using a non local socket (e.g. you have entered a hostname or IP address in the "Host" field of the "Server" settings page), then Cantata contains a minimal HTTP server that can be used to server files to MPD. This, however, will only work whilst Cantata is running. If you enable "Always use server", then Cantata will always pass server URLs to MPD for files, even when you have connected via a local socket.</i> + + + true + + + + + + + Qt::Vertical + + + + 20 + 13 + + + + + + + + + + enableHttp + toggled(bool) + httpPort + setEnabled(bool) + + + 120 + 7 + + + 148 + 61 + + + + + enableHttp + toggled(bool) + alwaysUseHttp + setEnabled(bool) + + + 126 + 19 + + + 128 + 44 + + + + + diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 7f1e05480..c47f77150 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -95,6 +95,7 @@ #include "mpris.h" #include "dockmanager.h" #include "messagewidget.h" +#include "httpserver.h" #include "debugtimer.h" enum Tabs @@ -867,6 +868,9 @@ MainWindow::MainWindow(QWidget *parent) MPDConnection::self()->moveToThread(mpdThread); mpdThread->start(); connectToMpd(); + if (Settings::self()->enableHttp()) { + HttpServer::self()->setPort(Settings::self()->httpPort()); + } #ifdef ENABLE_REMOTE_DEVICES DevicesModel::self()->loadRemote(); #endif @@ -1116,6 +1120,7 @@ void MainWindow::updateSettings() } connectToMpd(); + HttpServer::self()->setPort(Settings::self()->enableHttp() ? Settings::self()->httpPort() : 0); #ifdef ENABLE_DEVICES_SUPPORT copyToDeviceAction->setEnabled(QDir(Settings::self()->mpdDir()).isReadable()); deleteSongsAction->setEnabled(copyToDeviceAction->isEnabled()); @@ -1437,6 +1442,15 @@ void MainWindow::updateCurrentSong(const Song &song) current=song; + if (current.file.startsWith("http") && HttpServer::self()->isOurs(current.file)) { + Song mod=HttpServer::self()->decodeUrl(current.file); + if (!mod.title.isEmpty()) { + current=mod; + current.id=song.id; + current.file="XXX"; + } + } + positionSlider->setEnabled(!currentIsStream()); // Determine if album cover should be updated diff --git a/gui/preferencesdialog.cpp b/gui/preferencesdialog.cpp index 0c86aad78..efb512183 100644 --- a/gui/preferencesdialog.cpp +++ b/gui/preferencesdialog.cpp @@ -29,6 +29,7 @@ #include "playbacksettings.h" #include "outputsettings.h" #include "serversettings.h" +#include "httpserversettings.h" #include "lyricsettings.h" #include "lyricspage.h" #ifdef ENABLE_KDE_SUPPORT @@ -87,12 +88,14 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, LyricsPage *lp) output = new OutputSettings(widget); interface = new InterfaceSettings(widget); ext = new ExternalSettings(widget); + http = new HttpServerSettings(widget); lyrics = new LyricSettings(widget); server->load(); playback->load(); output->load(); interface->load(); ext->load(); + http->load(); const QList &lprov=lp->getProviders(); lyrics->Load(lprov); #ifdef ENABLE_KDE_SUPPORT @@ -111,6 +114,9 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, LyricsPage *lp) page=widget->addPage(ext, i18n("External")); page->setHeader(i18n("External Settings")); page->setIcon(KIcon("video-display")); + page=widget->addPage(http, i18n("HTTP Server")); + page->setHeader(i18n("HTTP Server Settings")); + page->setIcon(KIcon("network-server")); page=widget->addPage(lyrics, i18n("Lyrics")); page->setHeader(i18n("Lyrics Settings")); page->setIcon(KIcon("view-media-lyrics")); @@ -129,6 +135,8 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, LyricsPage *lp) QIcon::fromTheme("preferences-desktop-color"), tr("Interface")); widget->AddTab(new ConfigPage(this, tr("External Settings"), QIcon::fromTheme("video-display"), ext), QIcon::fromTheme("video-display"), tr("External")); + widget->AddTab(new ConfigPage(this, tr("HTTP Server Settings"), QIcon::fromTheme("network-server"), http), + QIcon::fromTheme("network-server"), tr("HTTP Server")); widget->AddTab(new ConfigPage(this, tr("Lyrics Settings"), QIcon::fromTheme("view-media-lyrics"), lyrics), QIcon::fromTheme("view-media-lyrics"), tr("Lyrics")); proxy = new ProxySettings(this); @@ -147,7 +155,7 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, LyricsPage *lp) connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); #endif - resize(600, 400); + resize(600, 480); } void PreferencesDialog::writeSettings() @@ -158,6 +166,7 @@ void PreferencesDialog::writeSettings() output->save(); interface->save(); ext->save(); + http->save(); #ifndef ENABLE_KDE_SUPPORT proxy->save(); #endif diff --git a/gui/preferencesdialog.h b/gui/preferencesdialog.h index 7e44fa9b5..f6fcc3aee 100644 --- a/gui/preferencesdialog.h +++ b/gui/preferencesdialog.h @@ -40,6 +40,7 @@ class InterfaceSettings; class LyricSettings; class LyricsPage; class ExternalSettings; +class HttpServerSettings; #ifdef ENABLE_KDE_SUPPORT class PreferencesDialog : public KDialog @@ -73,6 +74,7 @@ private: InterfaceSettings *interface; ExternalSettings *ext; LyricSettings *lyrics; + HttpServerSettings *http; #ifndef ENABLE_KDE_SUPPORT QDialogButtonBox *buttonBox; ProxySettings *proxy; diff --git a/gui/settings.cpp b/gui/settings.cpp index cc63c9e4e..90532b306 100644 --- a/gui/settings.cpp +++ b/gui/settings.cpp @@ -323,7 +323,22 @@ int Settings::stopFadeDuration() return v; } -void Settings::Settings::saveConnectionHost(const QString &v) +int Settings::httpPort() +{ + return GET_INT("httpPort", 9001); +} + +bool Settings::enableHttp() +{ + return GET_BOOL("enableHttp", false); +} + +bool Settings::alwaysUseHttp() +{ + return GET_BOOL("alwaysUseHttp", false); +} + +void Settings::saveConnectionHost(const QString &v) { SET_VALUE("connectionHost", v); } @@ -509,6 +524,21 @@ void Settings::saveStopFadeDuration(int v) SET_VALUE("stopFadeDuration", v); } +void Settings::saveHttpPort(int v) +{ + SET_VALUE("httpPort", v); +} + +void Settings::saveEnableHttp(bool v) +{ + SET_VALUE("enableHttp", v); +} + +void Settings::saveAlwaysUseHttp(bool v) +{ + SET_VALUE("alwaysUseHttp", v); +} + void Settings::save(bool force) { if (force) { diff --git a/gui/settings.h b/gui/settings.h index e3fdc2d8b..846d32218 100644 --- a/gui/settings.h +++ b/gui/settings.h @@ -92,6 +92,9 @@ public: #endif int version(); int stopFadeDuration(); + int httpPort(); + bool enableHttp(); + bool alwaysUseHttp(); void saveConnectionHost(const QString &v); void saveConnectionPasswd(const QString &v); @@ -129,6 +132,9 @@ public: void saveDevicesView(int v); #endif void saveStopFadeDuration(int v); + void saveHttpPort(int v); + void saveEnableHttp(bool v); + void saveAlwaysUseHttp(bool v); void save(bool force=false); #ifdef ENABLE_KDE_SUPPORT bool openWallet(); diff --git a/http/httpserver.cpp b/http/httpserver.cpp new file mode 100644 index 000000000..f0bd6dd4a --- /dev/null +++ b/http/httpserver.cpp @@ -0,0 +1,199 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 Craig Drummond + * + * ---- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "httpserver.h" +#include "httpsocket.h" +#include +#include +#ifdef ENABLE_KDE_SUPPORT +#include +K_GLOBAL_STATIC(HttpServer, instance) +#endif + +struct Thread : public QThread +{ + static void sleep() { QThread::msleep(100); } +}; + +HttpServer * HttpServer::self() +{ + #ifdef ENABLE_KDE_SUPPORT + return instance; + #else + static HttpServer *instance=0;; + if(!instance) { + instance=new HttpServer; + } + return instance; + #endif +} + +void HttpServer::stop() +{ + if (socket) { + socket->terminate(); + socket=0; + } + + if (thread) { + thread->quit(); + for(int i=0; i<10 && thread->isRunning(); ++i) { + Thread::sleep(); + } + } +} + +bool HttpServer::setPort(quint16 port) +{ + if (socket && port==socket->port()) { + return true; + } + + if (socket) { + socket->terminate(); + socket=0; + } + + if (thread) { + thread->quit(); + thread=0; + } + + if (0!=port) { + thread=new QThread(0); + socket=new HttpSocket(port); + socket->moveToThread(thread); + thread->start(); + return socket->isListening(); + } else { + return true; + } +} + +bool HttpServer::isAlive() const +{ + return socket && socket->isListening(); +} + +QString HttpServer::address() const +{ + if (!isAlive()) { + return QString(); + } + return QLatin1String("http://127.0.0.1:")+QString::number(socket->port()); +} + +bool HttpServer::isOurs(const QString &url) const +{ + if (!isAlive()) { + return false; + } + return url.startsWith(address()+"/"); +} + +QByteArray HttpServer::encodeUrl(const Song &s) const +{ + if (!isAlive()) { + return QByteArray(); + } + QUrl url; + url.setScheme("http"); + url.setHost("127.0.0.1"); + url.setPort(socket->port()); + url.setPath(s.file); + if (!s.album.isEmpty()) { + url.addQueryItem("album", s.album); + } + if (!s.artist.isEmpty()) { + url.addQueryItem("artist", s.artist); + } + if (!s.albumartist.isEmpty()) { + url.addQueryItem("albumartist", s.albumartist); + } + if (!s.title.isEmpty()) { + url.addQueryItem("title", s.title); + } + if (!s.genre.isEmpty()) { + url.addQueryItem("genre", s.genre); + } + if (s.disc) { + url.addQueryItem("disc", QString::number(s.disc)); + } + if (s.year) { + url.addQueryItem("year", QString::number(s.year)); + } + if (s.time) { + url.addQueryItem("time", QString::number(s.time)); + } + url.addQueryItem("cantata", "song"); + return url.toEncoded(); +} + +QByteArray HttpServer::encodeUrl(const QString &file) const +{ + QUrl url; + url.setScheme("http"); + url.setHost("127.0.0.1"); + url.setPort(socket->port()); + url.setPath(file); + url.addQueryItem("cantata", "file"); + return url.toEncoded(); +} + +Song HttpServer::decodeUrl(const QString &url) const +{ + Song s; + + if (isAlive()) { + QUrl u(url); + + if (u.hasQueryItem("cantata") && u.queryItemValue("cantata")=="song") { + if (u.hasQueryItem("album")) { + s.album=u.queryItemValue("album"); + } + if (u.hasQueryItem("artist")) { + s.artist=u.queryItemValue("artist"); + } + if (u.hasQueryItem("albumartist")) { + s.albumartist=u.queryItemValue("albumartist"); + } + if (u.hasQueryItem("title")) { + s.title=u.queryItemValue("title"); + } + if (u.hasQueryItem("genre")) { + s.genre=u.queryItemValue("genre"); + } + if (u.hasQueryItem("disc")) { + s.disc=u.queryItemValue("disc").toInt(); + } + if (u.hasQueryItem("year")) { + s.year=u.queryItemValue("year").toInt(); + } + if (u.hasQueryItem("time")) { + s.time=u.queryItemValue("time").toInt(); + } + } + } + + return s; +} diff --git a/http/httpserver.h b/http/httpserver.h new file mode 100644 index 000000000..1b598e5bb --- /dev/null +++ b/http/httpserver.h @@ -0,0 +1,62 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 Craig Drummond + * + * ---- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _HTTP_SERVER_H +#define _HTTP_SERVER_H + +#include +#include +#include "song.h" + +class HttpSocket; +class QThread; + +class HttpServer +{ +public: + static HttpServer * self(); + + HttpServer() + : thread(0) + , socket(0) { + } + + virtual ~HttpServer() { + } + + void stop(); + bool isAlive() const; + bool setPort(quint16 port); + QString address() const; + bool isOurs(const QString &url) const; + QByteArray encodeUrl(const Song &s) const; + QByteArray encodeUrl(const QString &file) const; + Song decodeUrl(const QString &url) const; + +private: + QThread *thread; + HttpSocket *socket; +}; + +#endif + diff --git a/http/httpsocket.cpp b/http/httpsocket.cpp new file mode 100644 index 000000000..1859122e9 --- /dev/null +++ b/http/httpsocket.cpp @@ -0,0 +1,104 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 Craig Drummond + * + * ---- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "httpsocket.h" +#include +#include +#include +#include +#include + +HttpSocket::HttpSocket(quint16 p) + : QTcpServer(0) + , portNumber(p) + , terminated(false) +{ + listen(QHostAddress::LocalHost, portNumber); +} + +void HttpSocket::terminate() +{ + terminated=true; + deleteLater(); +} + +void HttpSocket::incomingConnection(int socket) +{ + QTcpSocket *s = new QTcpSocket(this); + connect(s, SIGNAL(readyRead()), this, SLOT(readClient())); + connect(s, SIGNAL(disconnected()), this, SLOT(discardClient())); + s->setSocketDescriptor(socket); +} + +void HttpSocket::readClient() +{ + if (terminated) { + return; + } + + QTcpSocket *socket = (QTcpSocket*)sender(); + if (socket->canReadLine()) { + QString line=socket->readLine(); + QStringList tokens = line.split(QRegExp("[ \r\n][ \r\n]*")); + if (QLatin1String("GET")==tokens[0]) { + QUrl url(tokens[1]); + bool ok=false; + if (url.hasQueryItem("cantata")) { + QFile f(url.path()); + + if (f.open(QIODevice::ReadOnly)) { + ok=true; + for(; !terminated;) { + QByteArray buffer=f.read(4096); + if (buffer.size()>0) { + socket->write(buffer); + } else { + break; + } + } + } + } + + if (!ok) { + QTextStream os(socket); + os.setAutoDetectUnicode(true); + os << "HTTP/1.0 404 Ok\r\n" + "Content-Type: text/html; charset=\"utf-8\"\r\n" + "\r\n" + "

Nothing to see here

\n"; + } + + socket->close(); + + if (QTcpSocket::UnconnectedState==socket->state()) { + delete socket; + } + } + } +} + +void HttpSocket::discardClient() +{ + QTcpSocket *socket = (QTcpSocket*)sender(); + socket->deleteLater(); +} diff --git a/http/httpsocket.h b/http/httpsocket.h new file mode 100644 index 000000000..28fb0286e --- /dev/null +++ b/http/httpsocket.h @@ -0,0 +1,54 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 Craig Drummond + * + * ---- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _HTTP_SOCKET_H_ +#define _HTTP_SOCKET_H_ + +#include + +class HttpSocket : public QTcpServer +{ + Q_OBJECT + +public: + HttpSocket(quint16 p); + + virtual ~HttpSocket() { + } + + void terminate(); + void incomingConnection(int socket); + quint16 port() const { + return portNumber; + } + +private Q_SLOTS: + void readClient(); + void discardClient(); + +private: + quint16 portNumber; + bool terminated; +}; + +#endif diff --git a/models/devicesmodel.cpp b/models/devicesmodel.cpp index bae5a9023..47ba4dc9a 100644 --- a/models/devicesmodel.cpp +++ b/models/devicesmodel.cpp @@ -33,6 +33,7 @@ #include "mpdparseutils.h" #include "mediadevicecache.h" #include "umsdevice.h" +#include "httpserver.h" #include #include #include @@ -488,19 +489,19 @@ Qt::ItemFlags DevicesModel::flags(const QModelIndex &index) const return Qt::ItemIsEnabled; } -QStringList DevicesModel::filenames(const QModelIndexList &indexes, bool umsOnly) const +QStringList DevicesModel::filenames(const QModelIndexList &indexes, bool playableOnly) const { QStringList fnames; foreach(QModelIndex index, indexes) { MusicLibraryItem *item = static_cast(index.internalPointer()); - if (!umsOnly) { + if (playableOnly) { MusicLibraryItem *parent=item; while (parent->parent()) { parent=parent->parent(); } - if (parent && Device::Ums!=static_cast(parent)->type()) { + if (parent && !static_cast(parent)->canPlaySongs()) { continue; } } @@ -549,13 +550,23 @@ QStringList DevicesModel::filenames(const QModelIndexList &indexes, bool umsOnly return fnames; } -QList DevicesModel::songs(const QModelIndexList &indexes) const +QList DevicesModel::songs(const QModelIndexList &indexes, bool playableOnly) const { QList songs; foreach(QModelIndex index, indexes) { MusicLibraryItem *item = static_cast(index.internalPointer()); + if (playableOnly) { + MusicLibraryItem *parent=item; + while (parent->parent()) { + parent=parent->parent(); + } + if (parent && !static_cast(parent)->canPlaySongs()) { + continue; + } + } + switch (item->type()) { case MusicLibraryItem::Type_Root: foreach (const MusicLibraryItem *artist, item->children()) { @@ -690,7 +701,15 @@ void DevicesModel::updateItemMenu() QMimeData * DevicesModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData=0; - QStringList paths=filenames(indexes, true); + QStringList paths; + if (HttpServer::self()->isAlive()) { + QList songList=songs(indexes, true); + foreach (const Song &s, songList) { + paths.append(HttpServer::self()->encodeUrl(s)); + } + } else { + paths=filenames(indexes, true); + } if (!paths.isEmpty()) { mimeData=new QMimeData(); diff --git a/models/devicesmodel.h b/models/devicesmodel.h index 61cb9f755..86142aba3 100644 --- a/models/devicesmodel.h +++ b/models/devicesmodel.h @@ -54,8 +54,8 @@ public: int columnCount(const QModelIndex &) const; QVariant data(const QModelIndex &, int) const; Qt::ItemFlags flags(const QModelIndex &index) const; - QStringList filenames(const QModelIndexList &indexes, bool umsOnly=false) const; - QList songs(const QModelIndexList &indexes) const; + QStringList filenames(const QModelIndexList &indexes, bool playableOnly=false) const; + QList songs(const QModelIndexList &indexes, bool playableOnly=false) const; void clear(); QMenu * menu() { return itemMenu; } Device * device(const QString &udi); diff --git a/models/playqueuemodel.cpp b/models/playqueuemodel.cpp index 981953876..20447fda6 100644 --- a/models/playqueuemodel.cpp +++ b/models/playqueuemodel.cpp @@ -41,6 +41,8 @@ #include "mpdstats.h" #include "mpdstatus.h" #include "streamfetcher.h" +#include "httpserver.h" +#include "settings.h" #include "debugtimer.h" static QStringList reverseList(const QStringList &orig) @@ -275,7 +277,7 @@ QStringList PlayQueueModel::mimeTypes() const QStringList types; types << constMoveMimeType; types << constFileNameMimeType; - if (MPDConnection::self()->isLocal()) { + if (MPDConnection::self()->isLocal() || HttpServer::self()->isAlive()) { types << constUriMimeType; } return types; @@ -356,13 +358,23 @@ bool PlayQueueModel::dropMimeData(const QMimeData *data, } else if(data->hasFormat(constUriMimeType)/* && MPDConnection::self()->isLocal()*/) { QStringList orig=reverseList(decode(*data, constUriMimeType)); QStringList useable; - bool allowLocal=MPDConnection::self()->isLocal(); + bool haveHttp=HttpServer::self()->isAlive(); + bool alwaysUseHttp=haveHttp && Settings::self()->alwaysUseHttp(); + bool mpdLocal=MPDConnection::self()->isLocal(); + bool allowLocal=haveHttp || mpdLocal; foreach (QString u, orig) { - if (allowLocal && u.startsWith('/')) { - useable.append(QLatin1String("file://")+unencodeUrl(u)); - } else if ((allowLocal && u.startsWith("file:///")) || u.startsWith("http://")) { - useable.append(unencodeUrl(u)); + if (u.startsWith("http://")) { + useable.append(u); + } else if (allowLocal && (u.startsWith('/') || u.startsWith("file:///"))) { + if (u.startsWith("file://")) { + u=u.mid(7); + } + if (alwaysUseHttp || !mpdLocal) { + useable.append(HttpServer::self()->encodeUrl(unencodeUrl(u))); + } else { + useable.append(QLatin1String("file://")+unencodeUrl(u)); + } } } if (useable.count()) { @@ -473,7 +485,25 @@ void PlayQueueModel::updatePlaylist(const QList &songs) { TF_DEBUG beginResetModel(); - this->songs = songs; + if (HttpServer::self()->isAlive()) { + QList songList; + foreach (const Song &s, songs) { + if (s.file.startsWith("http") && HttpServer::self()->isOurs(s.file)) { + Song mod=HttpServer::self()->decodeUrl(s.file); + if (mod.title.isEmpty()) { + songList.append(s); + } else { + mod.id=s.id; + songList.append(mod); + } + } else { + songList.append(s); + } + } + this->songs = songList; + } else { + this->songs = songs; + } endResetModel(); } diff --git a/models/streamfetcher.cpp b/models/streamfetcher.cpp index 8a9d800e5..990c6a934 100644 --- a/models/streamfetcher.cpp +++ b/models/streamfetcher.cpp @@ -24,6 +24,7 @@ #include "streamfetcher.h" #include "networkaccessmanager.h" #include "mpdconnection.h" +#include "httpserver.h" #include #include #include @@ -185,7 +186,7 @@ void StreamFetcher::doNext() current=todo.takeFirst(); QUrl u(current); - if (QLatin1String("http")==u.scheme()) { + if (QLatin1String("http")==u.scheme() && !HttpServer::self()->isOurs(current)) { data.clear(); job=manager->get(u); connect(job, SIGNAL(readyRead()), this, SLOT(dataReady()));