From f609bc793fda07557dda1d59cf664749ddc2bda7 Mon Sep 17 00:00:00 2001 From: santigl Date: Thu, 16 Mar 2017 23:23:32 -0300 Subject: [PATCH] Fix remote-playlist load --- src/core/songloader.cpp | 92 ++++++++++++++++++++++---- src/core/songloader.h | 10 ++- src/playlistparsers/playlistparser.cpp | 20 ++++++ src/playlistparsers/playlistparser.h | 3 + src/playlistparsers/plsparser.h | 1 + 5 files changed, 110 insertions(+), 16 deletions(-) diff --git a/src/core/songloader.cpp b/src/core/songloader.cpp index d2c13f6c0..6c442dd3f 100644 --- a/src/core/songloader.cpp +++ b/src/core/songloader.cpp @@ -34,6 +34,7 @@ #include "config.h" #include "core/logging.h" #include "core/player.h" +#include "core/utilities.h" #include "core/signalchecker.h" #include "core/song.h" #include "core/tagreaderclient.h" @@ -115,6 +116,11 @@ SongLoader::Result SongLoader::Load(const QUrl& url) { return Success; } + // It could be a playlist, we give it a shot. + if (LoadRemotePlaylist(url_)) { + return Success; + } + url_ = PodcastUrlLoader::FixPodcastUrl(url_); preload_func_ = std::bind(&SongLoader::LoadRemote, this); @@ -144,10 +150,10 @@ SongLoader::Result SongLoader::LoadLocalPartial(const QString& filename) { SongLoader::Result SongLoader::LoadAudioCD() { #ifdef HAVE_AUDIOCD CddaSongLoader* cdda_song_loader = new CddaSongLoader; - connect(cdda_song_loader, SIGNAL(SongsDurationLoaded(SongList)), - this, SLOT(AudioCDTracksLoadedSlot(SongList))); - connect(cdda_song_loader, SIGNAL(SongsMetadataLoaded(SongList)), - this, SLOT(AudioCDTracksTagsLoaded(SongList))); + connect(cdda_song_loader, SIGNAL(SongsDurationLoaded(SongList)), this, + SLOT(AudioCDTracksLoadedSlot(SongList))); + connect(cdda_song_loader, SIGNAL(SongsMetadataLoaded(SongList)), this, + SLOT(AudioCDTracksTagsLoaded(SongList))); cdda_song_loader->LoadSongs(); return Success; #else // HAVE_AUDIOCD @@ -194,8 +200,7 @@ SongLoader::Result SongLoader::LoadLocal(const QString& filename) { } // It's not in the database, load it asynchronously. - preload_func_ = - std::bind(&SongLoader::LoadLocalAsync, this, filename); + preload_func_ = std::bind(&SongLoader::LoadLocalAsync, this, filename); return BlockingLoadRequired; } @@ -217,8 +222,8 @@ void SongLoader::LoadLocalAsync(const QString& filename) { if (!parser) { // Check the file extension as well, maybe the magic failed, or it was a // basic M3U file which is just a plain list of filenames. - parser = playlist_parser_-> - ParserForExtension(QFileInfo(filename).suffix().toLower()); + parser = playlist_parser_->ParserForExtension( + QFileInfo(filename).suffix().toLower()); } if (parser) { @@ -410,8 +415,7 @@ void SongLoader::LoadRemote() { // Add a probe to the sink so we can capture the data if it's a playlist GstPad* pad = gst_element_get_static_pad(fakesink, "sink"); - gst_pad_add_probe( - pad, GST_PAD_PROBE_TYPE_BUFFER, &DataReady, this, NULL); + gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, &DataReady, this, NULL); gst_object_unref(pad); QEventLoop loop; @@ -447,12 +451,11 @@ void SongLoader::TypeFound(GstElement*, uint, GstCaps* caps, void* self) { instance->StopTypefindAsync(true); } -GstPadProbeReturn SongLoader::DataReady( - GstPad*, GstPadProbeInfo* info, gpointer self) { +GstPadProbeReturn SongLoader::DataReady(GstPad*, GstPadProbeInfo* info, + gpointer self) { SongLoader* instance = reinterpret_cast(self); - if (instance->state_ == Finished) - return GST_PAD_PROBE_OK; + if (instance->state_ == Finished) return GST_PAD_PROBE_OK; GstBuffer* buffer = gst_pad_probe_info_get_buffer(info); GstMapInfo map; @@ -617,3 +620,64 @@ void SongLoader::StopTypefindAsync(bool success) { metaObject()->invokeMethod(this, "StopTypefind", Qt::QueuedConnection); } + +bool SongLoader::LoadRemotePlaylist(const QUrl& url) { + // This function makes a remote request for the given URL and, if its MIME + // type corresponds to a known playlist type, saves the content to a + // temporary file, loads it, and returns true. + // If the URL does not point to a playlist file we could handle, + // it returns false. + + NetworkAccessManager manager; + QNetworkRequest req = QNetworkRequest(url); + + // Getting headers: + QNetworkReply* reply = manager.head(req); + { + QEventLoop loop; + QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + } + if (reply->error() != QNetworkReply::NoError) { + qLog(Debug) << url.toString() << reply->errorString(); + return false; + } + + // Now we check if there is a parser that can handle that MIME type. + QString mime_type = + reply->header(QNetworkRequest::ContentTypeHeader).toString(); + + ParserBase* parser = playlist_parser_->ParserForMimeType(mime_type); + if (parser == nullptr) { + qLog(Debug) << url.toString() << "seems to not be a playlist"; + return false; + } + + // We know it is a playlist! + // Getting its contents: + reply = manager.get(req); + { + QEventLoop loop; + QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + } + if (reply->error() != QNetworkReply::NoError) { + qLog(Debug) << url.toString() << reply->errorString(); + return false; + } + + // Save them to a temporary file... + QString playlist_filename = Utilities::GetTemporaryFileName(); + QFile file(playlist_filename); + file.open(QIODevice::WriteOnly); + file.write(reply->readAll()); + file.close(); + qLog(Debug) << url.toString() << "with MIME" << mime_type << "saved to" + << playlist_filename; + + // ...and load it. + LoadPlaylist(parser, playlist_filename); + + file.remove(); + return true; +} diff --git a/src/core/songloader.h b/src/core/songloader.h index 2d8114f09..494a4fc9d 100644 --- a/src/core/songloader.h +++ b/src/core/songloader.h @@ -79,7 +79,7 @@ class SongLoader : public QObject { void LoadMetadataBlocking(); Result LoadAudioCD(); - signals: +signals: void AudioCDTracksLoaded(); void LoadAudioCDFinished(bool success); void LoadRemoteFinished(); @@ -93,7 +93,12 @@ class SongLoader : public QObject { #endif // HAVE_AUDIOCD private: - enum State { WaitingForType, WaitingForMagic, WaitingForData, Finished, }; + enum State { + WaitingForType, + WaitingForMagic, + WaitingForData, + Finished, + }; Result LoadLocal(const QString& filename); void LoadLocalAsync(const QString& filename); @@ -105,6 +110,7 @@ class SongLoader : public QObject { void AddAsRawStream(); void LoadRemote(); + bool LoadRemotePlaylist(const QUrl& url); // GStreamer callbacks static void TypeFound(GstElement* typefind, uint probability, GstCaps* caps, diff --git a/src/playlistparsers/playlistparser.cpp b/src/playlistparsers/playlistparser.cpp index 3ea1dcba5..8d3c2d5bc 100644 --- a/src/playlistparsers/playlistparser.cpp +++ b/src/playlistparsers/playlistparser.cpp @@ -53,6 +53,17 @@ QStringList PlaylistParser::file_extensions() const { return ret; } +QStringList PlaylistParser::mime_types() const { + QStringList ret; + + for (ParserBase* parser : parsers_) { + if (!parser->mime_type().isEmpty()) ret << parser->mime_type(); + } + + qStableSort(ret); + return ret; +} + QString PlaylistParser::filters() const { QStringList filters; QStringList all_extensions; @@ -91,6 +102,15 @@ ParserBase* PlaylistParser::ParserForExtension(const QString& suffix) const { return nullptr; } +ParserBase* PlaylistParser::ParserForMimeType(const QString& mime_type) const { + for (ParserBase* p : parsers_) { + if (!p->mime_type().isEmpty() && + (QString::compare(p->mime_type(), mime_type, Qt::CaseInsensitive) == 0)) + return p; + } + return nullptr; +} + ParserBase* PlaylistParser::ParserForMagic(const QByteArray& data, const QString& mime_type) const { for (ParserBase* p : parsers_) { diff --git a/src/playlistparsers/playlistparser.h b/src/playlistparsers/playlistparser.h index 7cc92e064..c8e3651d4 100644 --- a/src/playlistparsers/playlistparser.h +++ b/src/playlistparsers/playlistparser.h @@ -38,12 +38,15 @@ class PlaylistParser : public QObject { QStringList file_extensions() const; QString filters() const; + QStringList mime_types() const; + QString default_extension() const; QString default_filter() const; ParserBase* ParserForMagic(const QByteArray& data, const QString& mime_type = QString()) const; ParserBase* ParserForExtension(const QString& suffix) const; + ParserBase* ParserForMimeType(const QString& mime) const; SongList LoadFromFile(const QString& filename) const; SongList LoadFromDevice(QIODevice* device, diff --git a/src/playlistparsers/plsparser.h b/src/playlistparsers/plsparser.h index 22898a4cb..c1fb19a19 100644 --- a/src/playlistparsers/plsparser.h +++ b/src/playlistparsers/plsparser.h @@ -28,6 +28,7 @@ class PLSParser : public ParserBase { QString name() const { return "PLS"; } QStringList file_extensions() const { return QStringList() << "pls"; } + QString mime_type() const { return "audio/x-scpls"; } bool TryMagic(const QByteArray& data) const;