diff --git a/devices/actiondialog.cpp b/devices/actiondialog.cpp index 0c5bfa6a9..5fcda21a2 100644 --- a/devices/actiondialog.cpp +++ b/devices/actiondialog.cpp @@ -523,6 +523,9 @@ void ActionDialog::actionStatus(int status, bool copiedCover) case Device::DownloadFailed: setPage(PAGE_SKIP, i18n("Failed to download track.

%1").arg(formatSong(currentSong))); break; + case Device::FailedToLockDevice: + setPage(PAGE_ERROR, i18n("Failed to lock device.
%1").arg(formatSong(currentSong))); + break; case Device::Cancelled: break; default: diff --git a/devices/audiocddevice.cpp b/devices/audiocddevice.cpp index 2ad3fea10..493a52db1 100644 --- a/devices/audiocddevice.cpp +++ b/devices/audiocddevice.cpp @@ -56,6 +56,7 @@ AudioCdDevice::AudioCdDevice(DevicesModel *m, Solid::Device &dev) block=dev.as(); if (block) { cddb=new Cddb(block->device()); + devPath=QLatin1String("cdda:/")+block->device()+QChar('/'); connect(cddb, SIGNAL(error(QString)), this, SIGNAL(error(QString))); connect(cddb, SIGNAL(initialDetails(CddbAlbum)), this, SLOT(setDetails(CddbAlbum))); connect(this, SIGNAL(lookup()), cddb, SLOT(lookup())); @@ -238,7 +239,7 @@ void AudioCdDevice::cddbMatches(const QList &albums) void AudioCdDevice::setCover(const Song &song, const QImage &img, const QString &file) { - if(song.file.startsWith("cdda://") && song.artist==artist && song.album==album) { + if (song.file.startsWith("cdda://") && song.artist==artist && song.album==album) { coverImage=Covers::Image(img, file); updateStatus(); } diff --git a/devices/audiocddevice.h b/devices/audiocddevice.h index 1261c36e6..32f277fc3 100644 --- a/devices/audiocddevice.h +++ b/devices/audiocddevice.h @@ -55,7 +55,7 @@ public: bool isRefreshing() const { return lookupInProcess; } void toggle(); void stop(); - QString path() const { return QString(); } // audioFolder; } + QString path() const { return devPath; } void addSong(const Song &, bool, bool) { } void copySongTo(const Song &s, const QString &baseDir, const QString &musicPath, bool overwrite, bool copyCover); void removeSong(const Song &) { } @@ -67,6 +67,9 @@ public: void saveOptions() { } QString subText() { return album; } quint32 totalTime(); + #ifdef CDDB_PLAYBACK + bool canPlaySongs() const { return true; } + #endif QString albumName() const { return album; } QString albumArtist() const { return artist; } @@ -94,6 +97,7 @@ private: QString album; QString artist; QString genre; + QString devPath; int year; int disc; quint32 time; diff --git a/devices/cdparanoia.cpp b/devices/cdparanoia.cpp index f5c22d538..06987e58c 100644 --- a/devices/cdparanoia.cpp +++ b/devices/cdparanoia.cpp @@ -22,30 +22,45 @@ */ #include "cdparanoia.h" +#include +#include +#include -CdParanoia::CdParanoia() +static QSet lockedDevices; +static QMutex mutex; + +CdParanoia::CdParanoia(const QString &device, bool full, bool noSkip) : drive(0) , paranoia(0) , paranoiaMode(0) - , neverSkip(true) + , neverSkip(noSkip) , maxRetries(20) { paranoia = 0; drive = 0; - setNeverSkip(true); - setMaxRetries(20); - setParanoiaMode(3); + QMutexLocker locker(&mutex); + if (!lockedDevices.contains(device)) { + dev = device; + if (init()) { + lockedDevices.insert(device); + } else { + dev=QString(); + } + } + + if (!dev.isEmpty()) { + setFullParanoiaMode(full); + setMaxRetries(20); + } } CdParanoia::~CdParanoia() { + QMutexLocker locker(&mutex); free(); -} - -bool CdParanoia::setDevice(const QString& device) -{ - dev = device; - return init(); + if (!dev.isEmpty()) { + lockedDevices.remove(dev); + } } void CdParanoia::setParanoiaMode(int mode) diff --git a/devices/cdparanoia.h b/devices/cdparanoia.h index 6fa090b6e..40d585fd8 100644 --- a/devices/cdparanoia.h +++ b/devices/cdparanoia.h @@ -34,11 +34,13 @@ extern "C" { class CdParanoia { public: - CdParanoia(); + CdParanoia(const QString &device, bool full, bool noSkip); ~CdParanoia(); - bool setDevice(const QString &device); + inline operator bool() const { return !dev.isEmpty(); } + void setParanoiaMode(int mode); + void setFullParanoiaMode(bool f) { setParanoiaMode(f ? 3 : 0); } void setNeverSkip(bool b); void setMaxRetries(int m) { maxRetries=m; } diff --git a/devices/device.h b/devices/device.h index 56e76fd21..54cca6c5f 100644 --- a/devices/device.h +++ b/devices/device.h @@ -74,6 +74,7 @@ public: NoSpace, FailedToUpdateTags, Cancelled, + FailedToLockDevice, // These are for online services... TooManyRedirects, diff --git a/devices/devicespage.cpp b/devices/devicespage.cpp index 1a8860261..b2499f5e0 100644 --- a/devices/devicespage.cpp +++ b/devices/devicespage.cpp @@ -179,6 +179,23 @@ Device * DevicesPage::activeFsDevice() const return activeDev; } +QStringList DevicesPage::playableUrls() const +{ + QModelIndexList selected = view->selectedIndexes(); + + if (0==selected.size()) { + return QStringList(); + } + qSort(selected); + + QModelIndexList mapped; + foreach (const QModelIndex &idx, selected) { + mapped.append(proxy.mapToSource(idx)); + } + + return DevicesModel::self()->playableUrls(mapped); +} + QList DevicesPage::selectedSongs() const { QModelIndexList selected = view->selectedIndexes(); @@ -213,6 +230,20 @@ QList DevicesPage::selectedSongs() const return DevicesModel::self()->songs(mapped); } +void DevicesPage::addSelectionToPlaylist(const QString &name, bool replace, quint8 priorty) +{ + QStringList files=playableUrls(); + + if (!files.isEmpty()) { + if (name.isEmpty()) { + emit add(files, replace, priorty); + } else { + emit addSongsToPlaylist(name, files); + } + view->clearSelection(); + } +} + void DevicesPage::itemDoubleClicked(const QModelIndex &) { // const QModelIndexList selected = view->selectedIndexes(); @@ -247,6 +278,7 @@ void DevicesPage::controlActions() bool deviceSelected=false; bool busyDevice=false; bool audioCd=false; + bool canPlay=false; QString udi; foreach (const QModelIndex &idx, selected) { @@ -277,6 +309,7 @@ void DevicesPage::controlActions() else if (Device::RemoteFs==dev->devType()) { remoteDev=true; } + canPlay=dev->canPlaySongs(); #endif if (udi.isEmpty()) { udi=dev->udi(); @@ -301,6 +334,9 @@ void DevicesPage::controlActions() #endif //StdActions::self()->burnAction->setEnabled(enable && onlyFs); StdActions::self()->organiseFilesAction->setEnabled(!busyDevice && haveTracks && onlyFs && singleUdi && !deviceSelected); + StdActions::self()->addToPlayQueueAction->setEnabled(!selected.isEmpty() && singleUdi && !busyDevice && haveTracks && (audioCd || !deviceSelected)); + StdActions::self()->addWithPriorityAction->setEnabled(StdActions::self()->addToPlayQueueAction->isEnabled()); + StdActions::self()->replacePlayQueueAction->setEnabled(StdActions::self()->addToPlayQueueAction->isEnabled()); #ifdef ENABLE_REMOTE_DEVICES forgetDeviceAction->setEnabled(singleUdi && remoteDev); #endif diff --git a/devices/devicespage.h b/devices/devicespage.h index 15748dab9..46d7c63af 100644 --- a/devices/devicespage.h +++ b/devices/devicespage.h @@ -45,7 +45,9 @@ public: void clear(); QString activeFsDeviceUdi() const; + QStringList playableUrls() const; QList selectedSongs() const; + void addSelectionToPlaylist(const QString &name=QString(), bool replace=false, quint8 priorty=0); void setView(int v) { view->setMode((ItemView::Mode)v); } void focusSearch() { view->focusSearch(); } void goBack() { view->backActivated(); } @@ -71,6 +73,10 @@ private: Device * activeFsDevice() const; Q_SIGNALS: + // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) + void add(const QStringList &files, bool replace, quint8 priorty); + void addSongsToPlaylist(const QString &name, const QStringList &files); + void addToDevice(const QString &from, const QString &to, const QList &songs); void deleteSongs(const QString &from, const QList &songs); diff --git a/devices/extractjob.cpp b/devices/extractjob.cpp index 77459f1ba..49d3c62bb 100644 --- a/devices/extractjob.cpp +++ b/devices/extractjob.cpp @@ -32,20 +32,20 @@ #include #include -static void writeHeader(QIODevice &dev) +void ExtractJob::writeWavHeader(QIODevice &dev) { static const unsigned char riffHeader[] = { - 0x52, 0x49, 0x46, 0x46, // 0 "RIFF" - 0x00, 0x00, 0x00, 0x00, // 4 wavSize - 0x57, 0x41, 0x56, 0x45, // 8 "WAVE" - 0x66, 0x6d, 0x74, 0x20, // 12 "fmt " - 0x10, 0x00, 0x00, 0x00, // 16 - 0x01, 0x00, 0x02, 0x00, // 20 - 0x44, 0xac, 0x00, 0x00, // 24 - 0x10, 0xb1, 0x02, 0x00, // 28 - 0x04, 0x00, 0x10, 0x00, // 32 - 0x64, 0x61, 0x74, 0x61, // 36 "data" - 0x00, 0x00, 0x00, 0x00 // 40 byteCount + 0x52, 0x49, 0x46, 0x46, // 0 "RIFF" + 0x00, 0x00, 0x00, 0x00, // 4 wavSize + 0x57, 0x41, 0x56, 0x45, // 8 "WAVE" + 0x66, 0x6d, 0x74, 0x20, // 12 "fmt " + 0x10, 0x00, 0x00, 0x00, // 16 + 0x01, 0x00, 0x02, 0x00, // 20 + 0x44, 0xac, 0x00, 0x00, // 24 + 0x10, 0xb1, 0x02, 0x00, // 28 + 0x04, 0x00, 0x10, 0x00, // 32 + 0x64, 0x61, 0x74, 0x61, // 36 "data" + 0x00, 0x00, 0x00, 0x00 // 40 byteCount }; dev.write((char*)riffHeader, 44); @@ -72,10 +72,10 @@ void ExtractJob::run() emit result(Device::Cancelled); } else { QStringList encParams=encoder.params(value, "pipe:", destFile); - CdParanoia cdparanoia; + CdParanoia cdparanoia(srcFile, Settings::self()->paranoiaFull(), Settings::self()->paranoiaNeverSkip()); - if (!cdparanoia.setDevice(srcFile)) { - emit result(Device::Failed); + if (!cdparanoia) { + emit result(Device::FailedToLockDevice); return; } QProcess process; @@ -93,12 +93,9 @@ void ExtractJob::run() int total=lastSector-firstSector; int count=0; - cdparanoia.setParanoiaMode(Settings::self()->paranoiaFull() ? 3 : 0); - cdparanoia.setNeverSkip(Settings::self()->paranoiaNeverSkip()); - cdparanoia.seek(firstSector, SEEK_SET); - writeHeader(process); + writeWavHeader(process); while ((firstSector+count) <= lastSector) { qint16 *buf = cdparanoia.read(); if (!buf) { diff --git a/devices/extractjob.h b/devices/extractjob.h index d37a3ab7a..80a378001 100644 --- a/devices/extractjob.h +++ b/devices/extractjob.h @@ -32,6 +32,8 @@ class ExtractJob : public FileJob { Q_OBJECT public: + static void writeWavHeader(QIODevice &dev); + explicit ExtractJob(const Encoders::Encoder &enc, int val, const QString &src, const QString &dest, const Song &s, const QString &cover); virtual ~ExtractJob(); diff --git a/devices/fsdevice.h b/devices/fsdevice.h index 0d4fb1c32..e93d81526 100644 --- a/devices/fsdevice.h +++ b/devices/fsdevice.h @@ -135,6 +135,7 @@ public: void saveCache(); void removeCache(); bool isStdFs() const { return true; } + bool canPlaySongs() const { return true; } Q_SIGNALS: // For talking to scanner... diff --git a/devices/umsdevice.h b/devices/umsdevice.h index 7fb74199b..ded9cd9d7 100644 --- a/devices/umsdevice.h +++ b/devices/umsdevice.h @@ -48,7 +48,6 @@ public: DevType devType() const { return Ums; } void saveOptions(); void configure(QWidget *parent); - virtual bool canPlaySongs() const { return true; } bool supportsDisconnect() const { return true; } private: diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index b18b1b887..0a943735f 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -1985,6 +1985,8 @@ void MainWindow::addToPlayQueue(bool replace, quint8 priority) playlistsPage->addSelectionToPlaylist(replace, priority); } else if (streamsPage->isVisible()) { streamsPage->addSelectionToPlaylist(replace, priority); + } else if (devicesPage->isVisible()) { + devicesPage->addSelectionToPlaylist(QString(), replace, priority); } else if (onlinePage->isVisible()) { onlinePage->addSelectionToPlaylist(QString(), replace, priority); } diff --git a/http/httpserver.cpp b/http/httpserver.cpp index 77bac6823..69c4276bb 100644 --- a/http/httpserver.cpp +++ b/http/httpserver.cpp @@ -149,6 +149,7 @@ QByteArray HttpServer::encodeUrl(const Song &s) const if (s.track) { query.addQueryItem("track", QString::number(s.track)); } + query.addQueryItem("id", QString::number(s.id)); query.addQueryItem("cantata", "song"); #if QT_VERSION >= 0x050000 url.setQuery(query); @@ -164,14 +165,17 @@ QByteArray HttpServer::encodeUrl(const QString &file) const } Song HttpServer::decodeUrl(const QString &url) const +{ + return decodeUrl(QUrl(url)); +} + +Song HttpServer::decodeUrl(const QUrl &url) const { Song s; #if QT_VERSION < 0x050000 - QUrl u(url); - QUrl &q=u; + const QUrl &q=url; #else - QUrl u(url); - QUrlQuery q(u); + QUrlQuery q(url); #endif if (q.hasQueryItem("cantata") && q.queryItemValue("cantata")=="song") { @@ -202,7 +206,15 @@ Song HttpServer::decodeUrl(const QString &url) const if (q.hasQueryItem("track")) { s.track=q.queryItemValue("track").toInt(); } - s.file=u.path(); + if (q.hasQueryItem("id")) { + s.id=q.queryItemValue("id").toInt(); + } + s.file=url.path(); + #ifdef CDDB_FOUND + if (s.file.startsWith("/cdda:/")) { + s.file=s.file.mid(1); + } + #endif } return s; diff --git a/http/httpserver.h b/http/httpserver.h index a907d0770..c355cae03 100644 --- a/http/httpserver.h +++ b/http/httpserver.h @@ -30,6 +30,7 @@ class HttpSocket; class QThread; +class QUrl; class HttpServer { @@ -51,7 +52,8 @@ public: bool isOurs(const QString &url) const; QByteArray encodeUrl(const Song &s) const; QByteArray encodeUrl(const QString &file) const; - Song decodeUrl(const QString &url) const; + Song decodeUrl(const QUrl &url) const; + Song decodeUrl(const QString &file) const; private: QThread *thread; diff --git a/http/httpsocket.cpp b/http/httpsocket.cpp index 3aa6f86d8..bef534b28 100644 --- a/http/httpsocket.cpp +++ b/http/httpsocket.cpp @@ -23,6 +23,8 @@ #include "config.h" #include "httpsocket.h" +#include "httpserver.h" +#include "settings.h" #include #include #include @@ -43,6 +45,11 @@ #include #endif +#ifdef CDDB_PLAYBACK +#include "cdparanoia.h" +#include "extractjob.h" +#endif + static QString detectMimeType(const QString &file) { #ifdef ENABLE_KDE_SUPPORT @@ -118,6 +125,15 @@ static QString detectMimeType(const QString &file) return QString(); } +static void writeMimeType(const QString &mimeType, QTcpSocket *socket) +{ + if (!mimeType.isEmpty()) { + QTextStream os(socket); + os.setAutoDetectUnicode(true); + os << "HTTP/1.0 200 OK\r\nContent-Type: " << mimeType << "\r\n\r\n"; + } +} + // static int level(const QString &s) // { // return QLatin1String("Link-local")==s @@ -239,51 +255,87 @@ void HttpSocket::readClient() bool ok=false; if (q.hasQueryItem("cantata")) { - QFile f(url.path()); + #ifdef CDDB_PLAYBACK + // Not working :-( No sound is played, plus thi seems to continue until Cantata is stopped?? + Song song=HttpServer::self()->decodeUrl(url); + if (song.file.startsWith(QLatin1String("cdda:/"))) { + QStringList parts=song.file.split("/", QString::SkipEmptyParts); + if (parts.length()>=3) { + QString dev=QLatin1Char('/')+parts.at(1)+QLatin1Char('/')+parts.at(2); + CdParanoia cdparanoia(dev, false, false); - if (f.open(QIODevice::ReadOnly)) { - QString mimeType=detectMimeType(url.path()); - if (!mimeType.isEmpty()) { - QTextStream os(socket); - os.setAutoDetectUnicode(true); - os << "HTTP/1.0 200 OK\r\nContent-Type: " << mimeType << "\r\n\r\n"; + if (cdparanoia) { + int firstSector = cdparanoia.firstSectorOfTrack(song.id); + int lastSector = cdparanoia.lastSectorOfTrack(song.id); + int count = 0; + cdparanoia.seek(firstSector, SEEK_SET); + ok=true; + writeMimeType(QLatin1String("audio/x-wav"), socket); + ExtractJob::writeWavHeader(*socket); + while (!terminated && (firstSector+count) <= lastSector) { + qint16 *buf = cdparanoia.read(); + if (!buf) { + ok=false; + break; + } + char *buffer=(char *)buf; + qint64 writePos=0; + do { + qint64 bytesWritten = socket->write(&buffer[writePos], CD_FRAMESIZE_RAW - writePos); + if (-1==bytesWritten) { + ok=false; + break; + } + writePos+=bytesWritten; + } while (!terminated && writePoswrite(&buffer[writePos], bytesRead - writePos); if (terminated) { break; } - if (-1==bytesWritten) { - ok=false; + bytesRead = f.read(buffer, constChunkSize); + readPos+=bytesRead; + if (bytesRead<0 || terminated) { break; } - writePos+=bytesWritten; - } while (writePoswrite(&buffer[writePos], bytesRead - writePos); + if (terminated) { + break; + } + if (-1==bytesWritten) { + ok=false; + break; + } + writePos+=bytesWritten; + } while (writePos(item)->devType()) { actions << configureAction; } + #ifdef CDDB_PLAYBACK + else if (HttpServer::self()->isAlive()) { + actions << StdActions::self()->replacePlayQueueAction; + } + #endif actions << refreshAction; if (static_cast(item)->supportsDisconnect()) { actions << (static_cast(item)->isConnected() ? disconnectAction : connectAction); @@ -352,7 +357,17 @@ QVariant DevicesModel::data(const QModelIndex &index, int role) const #endif v.setValue >(actions); return v; - } + } /*else if (HttpServer::self()->isAlive()) { + MusicLibraryItem *root=static_cast(item); + while (root && MusicLibraryItem::Type_Root!=root->itemType()) { + root=root->parentItem(); + } + if (root && static_cast(root)->canPlaySongs()) { + QVariant v; + v.setValue >(QList() << StdActions::self()->replacePlayQueueAction << StdActions::self()->addToPlayQueueAction); + return v; + } + } */ break; default: return QVariant(); @@ -515,6 +530,16 @@ QStringList DevicesModel::filenames(const QModelIndexList &indexes, bool playabl return fnames; } +QStringList DevicesModel::playableUrls(const QModelIndexList &indexes) const +{ + QList songList=songs(indexes, true, true); + QStringList urls; + foreach (const Song &s, songList) { + urls.append(HttpServer::self()->encodeUrl(s)); + } + return urls; +} + static Song fixPath(Song s, const QString &path) { if (!path.isEmpty()) { @@ -998,7 +1023,7 @@ QMimeData * DevicesModel::mimeData(const QModelIndexList &indexes) const paths.append(HttpServer::self()->encodeUrl(s)); } } else { - paths=filenames(indexes, true, true); + paths=playableUrls(indexes); } if (!paths.isEmpty()) { diff --git a/models/devicesmodel.h b/models/devicesmodel.h index 95f4a45ba..24eae86e5 100644 --- a/models/devicesmodel.h +++ b/models/devicesmodel.h @@ -53,6 +53,7 @@ public: QVariant data(const QModelIndex &, int) const; Qt::ItemFlags flags(const QModelIndex &index) const; QStringList filenames(const QModelIndexList &indexes, bool playableOnly=false, bool fullPath=false) const; + QStringList playableUrls(const QModelIndexList &indexes) const; QList songs(const QModelIndexList &indexes, bool playableOnly=false, bool fullPath=false) const; void clear(bool clearConfig=true); QMenu * menu() { return itemMenu; }