diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 53c642a50..e104d9726 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -41,6 +41,7 @@ set(SOURCES
core/scopedtransaction.cpp
core/settingsprovider.cpp
core/song.cpp
+ core/songloader.cpp
core/stylesheetloader.cpp
core/utilities.cpp
@@ -151,6 +152,7 @@ set(HEADERS
core/mergedproxymodel.h
core/networkaccessmanager.h
core/player.h
+ core/songloader.h
engines/enginebase.h
diff --git a/src/core/songloader.cpp b/src/core/songloader.cpp
new file mode 100644
index 000000000..2f572bfb7
--- /dev/null
+++ b/src/core/songloader.cpp
@@ -0,0 +1,302 @@
+/* This file is part of Clementine.
+
+ Clementine 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 3 of the License, or
+ (at your option) any later version.
+
+ Clementine 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 Clementine. If not, see .
+*/
+
+#include "songloader.h"
+#include "core/song.h"
+#include "playlistparsers/parserbase.h"
+#include "playlistparsers/playlistparser.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+SongLoader::SongLoader(QObject *parent)
+ : QObject(parent),
+ timeout_timer_(new QTimer(this)),
+ playlist_parser_(new PlaylistParser(this)),
+ state_(WaitingForType),
+ success_(false),
+ parser_(NULL)
+{
+ timeout_timer_->setSingleShot(true);
+ connect(timeout_timer_, SIGNAL(timeout()), SLOT(Timeout()));
+}
+
+SongLoader::Result SongLoader::Load(const QUrl& url, int timeout_msec) {
+ url_ = url;
+
+ if (url_.scheme() == "file") {
+ return LoadLocal();
+ }
+
+ // TODO: Start timeout
+ return LoadRemote();
+}
+
+SongLoader::Result SongLoader::LoadLocal() {
+ // First check to see if it's a directory - if so we can load all the songs
+ // inside right away.
+ QString filename = url_.toLocalFile();
+ if (QFileInfo(filename).isDir()) {
+ return LoadLocalDirectory(filename);
+ }
+
+ // It's a local file, so check if it looks like a playlist.
+ // Read the first few bytes.
+ QFile file(filename);
+ if (!file.open(QIODevice::ReadOnly))
+ return Error;
+ QByteArray data(file.read(PlaylistParser::kMagicSize));
+
+ ParserBase* parser = playlist_parser_->TryMagic(data);
+ if (parser) {
+ // It's a playlist!
+ file.seek(0);
+ songs_ = parser->Load(&file, QFileInfo(filename).path());
+ } else {
+ // Not a playlist, so just assume it's a song
+ Song song;
+ song.InitFromFile(filename, -1);
+ songs_ << song;
+ }
+
+ return Success;
+}
+
+SongLoader::Result SongLoader::LoadLocalDirectory(const QString& filename) {
+ QDirIterator it(filename, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable,
+ QDirIterator::Subdirectories);
+
+ while (it.hasNext()) {
+ QString path = it.next();
+ Song song;
+ song.InitFromFile(path, -1);
+ songs_ << song;
+ }
+
+ return Success;
+}
+
+SongLoader::Result SongLoader::LoadRemote() {
+ // It's not a local file so we have to fetch it to see what it is. We use
+ // gstreamer to do this since it handles funky URLs for us (http://, ssh://,
+ // etc) and also has typefinder plugins.
+ // First we wait for typefinder to tell us what it is. If it's not text/plain
+ // or text/uri-list assume it's a song and return success.
+ // Otherwise wait to get 512 bytes of data and do magic on it - if the magic
+ // fails then we don't no what it is so return failure.
+ // If the magic succeeds then we know for sure it's a playlist - so read the
+ // rest of the file, parse the playlist and return success.
+
+ // Create the pipeline - it gets unreffed if it goes out of scope
+ boost::shared_ptr pipeline(
+ gst_pipeline_new(NULL), boost::bind(&gst_object_unref, _1));
+
+ // Create the source element automatically based on the URL
+ GstElement* source = gst_element_make_from_uri(
+ GST_URI_SRC, url_.toEncoded().constData(), NULL);
+ if (!source) {
+ qWarning() << "Couldn't create gstreamer source element for" << url_;
+ return Error;
+ }
+
+ // Create the other elements and link them up
+ GstElement* typefind = gst_element_factory_make("typefind", NULL);
+ GstElement* fakesink = gst_element_factory_make("fakesink", NULL);
+
+ gst_bin_add_many(GST_BIN(pipeline.get()), source, typefind, fakesink, NULL);
+ gst_element_link_many(source, typefind, fakesink, NULL);
+
+ // Connect callbacks
+ GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline.get()));
+ g_signal_connect(typefind, "have-type", G_CALLBACK(TypeFound), this);
+ gst_bus_set_sync_handler(bus, BusCallbackSync, this);
+ gst_bus_add_watch(bus, BusCallback, this);
+
+ // Add a probe to the sink so we can capture the data if it's a playlist
+ GstPad* pad = gst_element_get_pad(fakesink, "sink");
+ gst_pad_add_buffer_probe(pad, G_CALLBACK(DataReady), this);
+ gst_object_unref(pad);
+
+ // Start "playing"
+ gst_element_set_state(pipeline.get(), GST_STATE_PLAYING);
+ pipeline_ = pipeline;
+ return WillLoadAsync;
+}
+
+void SongLoader::TypeFound(GstElement*, uint, GstCaps* caps, void* self) {
+ SongLoader* instance = static_cast(self);
+
+ if (instance->state_ != WaitingForType)
+ return;
+
+ // Check the mimetype
+ QString mimetype(gst_structure_get_name(gst_caps_get_structure(caps, 0)));
+ if (mimetype == "text/plain" ||
+ mimetype == "text/uri-list") {
+ // Yeah it might be a playlist, let's get some data and have a better look
+ instance->state_ = WaitingForMagic;
+ return;
+ }
+
+ // Nope, not a playlist - we're done
+ instance->StopTypefindAsync(true);
+}
+
+void SongLoader::DataReady(GstPad *, GstBuffer *buf, void *self) {
+ SongLoader* instance = static_cast(self);
+
+ if (instance->state_ == Finished)
+ return;
+
+ // Append the data to the buffer
+ instance->buffer_.append(reinterpret_cast(GST_BUFFER_DATA(buf)),
+ GST_BUFFER_SIZE(buf));
+
+ if (instance->state_ == WaitingForMagic &&
+ instance->buffer_.size() >= PlaylistParser::kMagicSize) {
+ // Got enough that we can test the magic
+ instance->MagicReady();
+ }
+}
+
+gboolean SongLoader::BusCallback(GstBus*, GstMessage* msg, gpointer self) {
+ SongLoader* instance = reinterpret_cast(self);
+
+ switch (GST_MESSAGE_TYPE(msg)) {
+ case GST_MESSAGE_ERROR:
+ instance->ErrorMessageReceived(msg);
+ break;
+
+ default:
+ break;
+ }
+
+ gst_message_unref(msg);
+ return GST_BUS_DROP;
+}
+
+GstBusSyncReply SongLoader::BusCallbackSync(GstBus*, GstMessage* msg, gpointer self) {
+ SongLoader* instance = reinterpret_cast(self);
+ switch (GST_MESSAGE_TYPE(msg)) {
+ case GST_MESSAGE_EOS:
+ instance->EndOfStreamReached();
+ break;
+
+ case GST_MESSAGE_ERROR:
+ instance->ErrorMessageReceived(msg);
+ break;
+
+ default:
+ break;
+ }
+ return GST_BUS_PASS;
+}
+
+void SongLoader::ErrorMessageReceived(GstMessage* msg) {
+ if (state_ == Finished)
+ return;
+
+ GError* error;
+ gchar* debugs;
+
+ gst_message_parse_error(msg, &error, &debugs);
+ qDebug() << error->message;
+ qDebug() << debugs;
+
+ g_error_free(error);
+ free(debugs);
+
+ StopTypefindAsync(false);
+}
+
+void SongLoader::EndOfStreamReached() {
+ switch (state_) {
+ case Finished:
+ break;
+
+ case WaitingForMagic:
+ // Do the magic on the data we have already
+ MagicReady();
+ if (state_ == Finished)
+ break;
+ // It looks like a playlist, so parse it
+
+ // fallthrough
+ case WaitingForData:
+ // It's a playlist and we've got all the data - finish and parse it
+ StopTypefindAsync(true);
+ break;
+
+ case WaitingForType:
+ StopTypefindAsync(false);
+ break;
+ }
+}
+
+void SongLoader::MagicReady() {
+ parser_ = playlist_parser_->TryMagic(buffer_);
+
+ if (!parser_) {
+ // It doesn't look like a playlist, so just finish
+ StopTypefindAsync(false);
+ return;
+ }
+
+ // It is a playlist - we'll get more data and parse the whole thing in
+ // EndOfStreamReached
+ state_ = WaitingForData;
+}
+
+void SongLoader::StopTypefindAsync(bool success) {
+ state_ = Finished;
+ success_ = success;
+
+ metaObject()->invokeMethod(this, "StopTypefind", Qt::QueuedConnection);
+}
+
+void SongLoader::StopTypefind() {
+ // Destroy the pipeline
+ if (pipeline_) {
+ gst_element_set_state(pipeline_.get(), GST_STATE_NULL);
+ pipeline_.reset();
+ }
+ timeout_timer_->stop();
+
+ if (success_ && parser_) {
+ // Parse the playlist
+ QBuffer buf(&buffer_);
+ buf.open(QIODevice::ReadOnly);
+ songs_ = parser_->Load(&buf);
+ } else if (success_) {
+ // It wasn't a playlist - just put the URL in as a stream
+ Song song;
+ song.set_valid(true);
+ song.set_filetype(Song::Type_Stream);
+ song.set_title(url_.toString());
+ songs_ << song;
+ }
+
+ emit LoadFinished(success_);
+}
+
+void SongLoader::Timeout() {
+ Q_ASSERT(0); // TODO
+}
diff --git a/src/core/songloader.h b/src/core/songloader.h
new file mode 100644
index 000000000..f3a9aa40c
--- /dev/null
+++ b/src/core/songloader.h
@@ -0,0 +1,92 @@
+/* This file is part of Clementine.
+
+ Clementine 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 3 of the License, or
+ (at your option) any later version.
+
+ Clementine 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 Clementine. If not, see .
+*/
+
+#ifndef SONGLOADER_H
+#define SONGLOADER_H
+
+#include
+#include
+
+#include "song.h"
+
+#include
+#include
+
+class ParserBase;
+class PlaylistParser;
+
+class SongLoader : public QObject {
+ Q_OBJECT
+public:
+ SongLoader(QObject* parent = 0);
+
+ enum Result {
+ Success,
+ Error,
+ WillLoadAsync,
+ };
+
+ const QUrl& url() const { return url_; }
+ const SongList& songs() const { return songs_; }
+
+ Result Load(const QUrl& url, int timeout_msec = 5000);
+
+signals:
+ void LoadFinished(bool success);
+
+private slots:
+ void Timeout();
+ void StopTypefind();
+
+private:
+ enum State {
+ WaitingForType,
+ WaitingForMagic,
+ WaitingForData,
+ Finished,
+ };
+
+ Result LoadLocal();
+ Result LoadLocalDirectory(const QString& filename);
+ Result LoadRemote();
+
+ // GStreamer callbacks
+ static void TypeFound(GstElement* typefind, uint probability, GstCaps* caps, void* self);
+ static void DataReady(GstPad*, GstBuffer* buf, void* self);
+ static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer);
+ static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
+
+ void StopTypefindAsync(bool success);
+ void ErrorMessageReceived(GstMessage* msg);
+ void EndOfStreamReached();
+ void MagicReady();
+
+private:
+ QUrl url_;
+ SongList songs_;
+
+ QTimer* timeout_timer_;
+ PlaylistParser* playlist_parser_;
+
+ // For async loads
+ State state_;
+ bool success_;
+ boost::shared_ptr pipeline_;
+ ParserBase* parser_;
+ QByteArray buffer_;
+};
+
+#endif // SONGLOADER_H
diff --git a/src/playlistparsers/asxparser.cpp b/src/playlistparsers/asxparser.cpp
index f62734e28..63f3b1cc8 100644
--- a/src/playlistparsers/asxparser.cpp
+++ b/src/playlistparsers/asxparser.cpp
@@ -110,3 +110,7 @@ void ASXParser::Save(const SongList &songs, QIODevice *device, const QDir &dir)
}
writer.writeEndDocument();
}
+
+bool ASXParser::TryMagic(const QByteArray &data) const {
+ return data.toLower().contains("write("\n");
}
}
+
+bool M3UParser::TryMagic(const QByteArray &data) const {
+ return data.contains("#EXTM3U") || data.contains("#EXTINF");
+}
diff --git a/src/playlistparsers/m3uparser.h b/src/playlistparsers/m3uparser.h
index c60bee458..cb41b834d 100644
--- a/src/playlistparsers/m3uparser.h
+++ b/src/playlistparsers/m3uparser.h
@@ -31,6 +31,9 @@ class M3UParser : public ParserBase {
QString name() const { return "M3U"; }
QStringList file_extensions() const { return QStringList() << "m3u"; }
+ QString mime_type() const { return "text/uri-list"; }
+
+ bool TryMagic(const QByteArray &data) const;
SongList Load(QIODevice* device, const QDir& dir = QDir()) const;
void Save(const SongList &songs, QIODevice* device, const QDir& dir = QDir()) const;
diff --git a/src/playlistparsers/parserbase.h b/src/playlistparsers/parserbase.h
index 0068b5f2e..81c6c9cdf 100644
--- a/src/playlistparsers/parserbase.h
+++ b/src/playlistparsers/parserbase.h
@@ -30,6 +30,9 @@ public:
virtual QString name() const = 0;
virtual QStringList file_extensions() const = 0;
+ virtual QString mime_type() const { return QString(); }
+
+ virtual bool TryMagic(const QByteArray& data) const = 0;
virtual SongList Load(QIODevice* device, const QDir& dir = QDir()) const = 0;
virtual void Save(const SongList& songs, QIODevice* device, const QDir& dir = QDir()) const = 0;
diff --git a/src/playlistparsers/playlistparser.cpp b/src/playlistparsers/playlistparser.cpp
index 67b94a9f2..02bdc5c70 100644
--- a/src/playlistparsers/playlistparser.cpp
+++ b/src/playlistparsers/playlistparser.cpp
@@ -22,6 +22,8 @@
#include
+const int PlaylistParser::kMagicSize = 512;
+
PlaylistParser::PlaylistParser(QObject *parent)
: QObject(parent)
{
@@ -60,10 +62,6 @@ QString PlaylistParser::filters() const {
return filters.join(";;");
}
-bool PlaylistParser::can_load(const QString &filename) const {
- return file_extensions().contains(QFileInfo(filename).suffix());
-}
-
ParserBase* PlaylistParser::ParserForExtension(const QString& suffix) const {
foreach (ParserBase* p, parsers_) {
if (p->file_extensions().contains(suffix))
@@ -72,11 +70,23 @@ ParserBase* PlaylistParser::ParserForExtension(const QString& suffix) const {
return NULL;
}
-SongList PlaylistParser::Load(const QString &filename) const {
+ParserBase* PlaylistParser::ParserForData(const QByteArray &data) const {
+ foreach (ParserBase* p, parsers_) {
+ if (p->TryMagic(data))
+ return p;
+ }
+ return NULL;
+}
+
+ParserBase* PlaylistParser::TryMagic(const QByteArray &data) const {
+ return ParserForData(data);
+}
+
+SongList PlaylistParser::Load(const QString &filename, ParserBase* p) const {
QFileInfo info(filename);
// Find a parser that supports this file extension
- ParserBase* parser = ParserForExtension(info.suffix());
+ ParserBase* parser = p ? p : ParserForExtension(info.suffix());
if (!parser) {
qWarning() << "Unknown filetype:" << filename;
return SongList();
diff --git a/src/playlistparsers/playlistparser.h b/src/playlistparsers/playlistparser.h
index 855cc53ec..c5a46340b 100644
--- a/src/playlistparsers/playlistparser.h
+++ b/src/playlistparsers/playlistparser.h
@@ -29,16 +29,19 @@ class PlaylistParser : public QObject {
public:
PlaylistParser(QObject *parent = 0);
+ static const int kMagicSize;
+
QStringList file_extensions() const;
QString filters() const;
- bool can_load(const QString& filename) const;
+ ParserBase* TryMagic(const QByteArray& data) const;
- SongList Load(const QString& filename) const;
+ SongList Load(const QString& filename, ParserBase* parser = 0) const;
void Save(const SongList& songs, const QString& filename) const;
private:
ParserBase* ParserForExtension(const QString& suffix) const;
+ ParserBase* ParserForData(const QByteArray& data) const;
QList parsers_;
};
diff --git a/src/playlistparsers/plsparser.cpp b/src/playlistparsers/plsparser.cpp
index 9486566e5..9c663a78a 100644
--- a/src/playlistparsers/plsparser.cpp
+++ b/src/playlistparsers/plsparser.cpp
@@ -94,3 +94,7 @@ void PLSParser::Save(const SongList &songs, QIODevice *device, const QDir &dir)
temp_file.seek(0);
device->write(temp_file.readAll());
}
+
+bool PLSParser::TryMagic(const QByteArray &data) const {
+ return data.contains("[playlist]");
+}
diff --git a/src/playlistparsers/plsparser.h b/src/playlistparsers/plsparser.h
index 8675023d4..3f3405bd7 100644
--- a/src/playlistparsers/plsparser.h
+++ b/src/playlistparsers/plsparser.h
@@ -28,6 +28,8 @@ public:
QString name() const { return "PLS"; }
QStringList file_extensions() const { return QStringList() << "pls"; }
+ bool TryMagic(const QByteArray &data) const;
+
SongList Load(QIODevice* device, const QDir& dir = QDir()) const;
void Save(const SongList& songs, QIODevice* device, const QDir& dir = QDir()) const;
};
diff --git a/src/playlistparsers/xspfparser.cpp b/src/playlistparsers/xspfparser.cpp
index efa92ebf9..dca0df8dc 100644
--- a/src/playlistparsers/xspfparser.cpp
+++ b/src/playlistparsers/xspfparser.cpp
@@ -137,3 +137,8 @@ void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir)
}
writer.writeEndDocument();
}
+
+bool XSPFParser::TryMagic(const QByteArray &data) const {
+ QByteArray lower(data.toLower());
+ return lower.contains(" urls;
+ /*QList urls;
foreach (const QString& path, file_names) {
if (playlist_parser_->can_load(path)) {
playlists_->current()->InsertSongs(playlist_parser_->Load(path));
@@ -1052,7 +1052,8 @@ void MainWindow::AddFile() {
urls << url;
}
}
- playlists_->current()->InsertPaths(urls);
+ playlists_->current()->InsertPaths(urls);*/
+ // TODO: Fix
}
void MainWindow::AddFolder() {
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 9fc16ea54..ab922fb5a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -105,3 +105,4 @@ add_test_file(fileformats_test.cpp false)
add_test_file(mergedproxymodel_test.cpp false)
add_test_file(plsparser_test.cpp false)
add_test_file(asxparser_test.cpp false)
+add_test_file(songloader_test.cpp false)
diff --git a/tests/data/test.asx b/tests/data/test.asx
new file mode 100644
index 000000000..e0f2c9383
--- /dev/null
+++ b/tests/data/test.asx
@@ -0,0 +1,6 @@
+foobar
+
+ Foo
+ Bar
+ mumble mumble
+
diff --git a/tests/data/test.xspf b/tests/data/test.xspf
new file mode 100644
index 000000000..f965e920e
--- /dev/null
+++ b/tests/data/test.xspf
@@ -0,0 +1,9 @@
+
diff --git a/tests/data/testdata.qrc b/tests/data/testdata.qrc
index 261b2a64a..7b8c6d4a6 100644
--- a/tests/data/testdata.qrc
+++ b/tests/data/testdata.qrc
@@ -10,5 +10,7 @@
pls_one.pls
pls_somafm.pls
test.m3u
+ test.xspf
+ test.asx
diff --git a/tests/metatypes_env.h b/tests/metatypes_env.h
index 73f195a63..b5fd05c8a 100644
--- a/tests/metatypes_env.h
+++ b/tests/metatypes_env.h
@@ -23,6 +23,7 @@
#include
#include "core/song.h"
+#include "core/songloader.h"
#include "library/directory.h"
class MetatypesEnvironment : public ::testing::Environment {
@@ -34,6 +35,7 @@ public:
qRegisterMetaType("SubdirectoryList");
qRegisterMetaType("SongList");
qRegisterMetaType("QModelIndex");
+ qRegisterMetaType("SongLoader::Result");
}
};
diff --git a/tests/songloader_test.cpp b/tests/songloader_test.cpp
new file mode 100644
index 000000000..79231a359
--- /dev/null
+++ b/tests/songloader_test.cpp
@@ -0,0 +1,177 @@
+/* This file is part of Clementine.
+
+ Clementine 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 3 of the License, or
+ (at your option) any later version.
+
+ Clementine 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 Clementine. If not, see .
+*/
+
+#include "test_utils.h"
+#include "gmock/gmock-matchers.h"
+#include "gtest/gtest.h"
+
+#include "core/songloader.h"
+#include "engines/gstengine.h"
+
+#include
+#include
+#include
+
+#include
+
+class SongLoaderTest : public ::testing::Test {
+public:
+ static void SetUpTestCase() {
+ sGstEngine = new GstEngine;
+ ASSERT_TRUE(sGstEngine->Init());
+ }
+
+ static void TearDownTestCase() {
+ delete sGstEngine;
+ sGstEngine = NULL;
+ }
+
+protected:
+ void SetUp() {
+ loader_.reset(new SongLoader);
+ }
+
+ static const char* kRemoteUrl;
+ static GstEngine* sGstEngine;
+
+ boost::scoped_ptr loader_;
+};
+
+const char* SongLoaderTest::kRemoteUrl = "http://remotetestdata.clementine-player.org";
+GstEngine* SongLoaderTest::sGstEngine = NULL;
+
+TEST_F(SongLoaderTest, LoadLocalMp3) {
+ TemporaryResource file(":/testdata/beep.mp3");
+ SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));
+
+ ASSERT_EQ(SongLoader::Success, ret);
+ ASSERT_EQ(1, loader_->songs().count());
+ EXPECT_TRUE(loader_->songs()[0].is_valid());
+ EXPECT_EQ("Beep mp3", loader_->songs()[0].title());
+}
+
+TEST_F(SongLoaderTest, LoadLocalPls) {
+ TemporaryResource file(":/testdata/pls_one.pls");
+ SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));
+
+ ASSERT_EQ(SongLoader::Success, ret);
+ ASSERT_EQ(1, loader_->songs().count());
+ EXPECT_EQ("Title", loader_->songs()[0].title());
+ EXPECT_EQ(123, loader_->songs()[0].length());
+}
+
+TEST_F(SongLoaderTest, LoadLocalM3U) {
+ TemporaryResource file(":/testdata/test.m3u");
+ SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));
+
+ ASSERT_EQ(SongLoader::Success, ret);
+ ASSERT_EQ(239, loader_->songs().count());
+}
+
+TEST_F(SongLoaderTest, LoadLocalXSPF) {
+ TemporaryResource file(":/testdata/test.xspf");
+ SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));
+
+ ASSERT_EQ(SongLoader::Success, ret);
+ ASSERT_EQ(1, loader_->songs().count());
+ EXPECT_EQ("Foo", loader_->songs()[0].title());
+}
+
+TEST_F(SongLoaderTest, LoadLocalASX) {
+ TemporaryResource file(":/testdata/test.asx");
+ SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));
+
+ ASSERT_EQ(SongLoader::Success, ret);
+ ASSERT_EQ(1, loader_->songs().count());
+ EXPECT_EQ("Foo", loader_->songs()[0].title());
+}
+
+TEST_F(SongLoaderTest, LoadRemoteMp3) {
+ SongLoader::Result ret = loader_->Load(QString(kRemoteUrl) + "/beep.mp3");
+ ASSERT_EQ(SongLoader::WillLoadAsync, ret);
+
+ QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));
+
+ // Start an event loop to wait for gstreamer to do its thing
+ QEventLoop loop;
+ QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
+ &loop, SLOT(quit()));
+ loop.exec();
+
+ // Check the signal was emitted with Success
+ ASSERT_EQ(1, spy.count());
+ EXPECT_EQ(true, spy[0][0].toBool());
+
+ // Check the song got loaded
+ ASSERT_EQ(1, loader_->songs().count());
+}
+
+TEST_F(SongLoaderTest, LoadRemote404) {
+ SongLoader::Result ret = loader_->Load(QString(kRemoteUrl) + "/404.mp3");
+ ASSERT_EQ(SongLoader::WillLoadAsync, ret);
+
+ QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));
+
+ // Start an event loop to wait for gstreamer to do its thing
+ QEventLoop loop;
+ QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
+ &loop, SLOT(quit()));
+ loop.exec();
+
+ // Check the signal was emitted with Error
+ ASSERT_EQ(1, spy.count());
+ EXPECT_EQ(false, spy[0][0].toBool());
+}
+
+TEST_F(SongLoaderTest, LoadRemotePls) {
+ SongLoader::Result ret = loader_->Load(QString(kRemoteUrl) + "/pls_somafm.pls");
+ ASSERT_EQ(SongLoader::WillLoadAsync, ret);
+
+ QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));
+
+ // Start an event loop to wait for gstreamer to do its thing
+ QEventLoop loop;
+ QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
+ &loop, SLOT(quit()));
+ loop.exec();
+
+ // Check the signal was emitted with Success
+ ASSERT_EQ(1, spy.count());
+ EXPECT_EQ(true, spy[0][0].toBool());
+
+ // Check some metadata
+ ASSERT_EQ(4, loader_->songs().count());
+ EXPECT_EQ("SomaFM: Groove Salad (#3 128k mp3): A nicely chilled plate of ambient beats and grooves.",
+ loader_->songs()[2].title());
+ EXPECT_EQ("http://ice.somafm.com/groovesalad", loader_->songs()[3].filename());
+}
+
+TEST_F(SongLoaderTest, LoadRemotePlainText) {
+ SongLoader::Result ret = loader_->Load(QString(kRemoteUrl) + "/notaplaylist.txt");
+ ASSERT_EQ(SongLoader::WillLoadAsync, ret);
+
+ QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));
+
+ // Start an event loop to wait for gstreamer to do its thing
+ QEventLoop loop;
+ QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
+ &loop, SLOT(quit()));
+ loop.exec();
+
+ // Check the signal was emitted with Error
+ ASSERT_EQ(1, spy.count());
+ EXPECT_EQ(false, spy[0][0].toBool());
+}
diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp
index 9310e3dc0..eed82058f 100644
--- a/tests/test_utils.cpp
+++ b/tests/test_utils.cpp
@@ -16,6 +16,7 @@
#include "test_utils.h"
+#include
#include
#include
#include
@@ -47,3 +48,15 @@ void PrintTo(const ::QString& str, std::ostream& os) {
void PrintTo(const ::QVariant& var, std::ostream& os) {
os << var.toString().toStdString();
}
+
+TemporaryResource::TemporaryResource(const QString& filename) {
+ setFileTemplate(QDir::tempPath() + "/clementine_test-XXXXXX." +
+ filename.section('.', -1, -1));
+ open();
+
+ QFile resource(filename);
+ resource.open(QIODevice::ReadOnly);
+ write(resource.readAll());
+
+ seek(0);
+}
diff --git a/tests/test_utils.h b/tests/test_utils.h
index 84e5f586f..b833a0fa9 100644
--- a/tests/test_utils.h
+++ b/tests/test_utils.h
@@ -21,6 +21,7 @@
#include
#include
+#include
class QNetworkRequest;
class QString;
@@ -44,4 +45,9 @@ void PrintTo(const ::QVariant& var, std::ostream& os);
Q_DECLARE_METATYPE(QModelIndex);
+class TemporaryResource : public QTemporaryFile {
+public:
+ TemporaryResource(const QString& filename);
+};
+
#endif // TEST_UTILS_H