diff --git a/src/playlistparsers/CMakeLists.txt b/src/playlistparsers/CMakeLists.txt index efe1ddc32..6c53cbc12 100644 --- a/src/playlistparsers/CMakeLists.txt +++ b/src/playlistparsers/CMakeLists.txt @@ -6,6 +6,7 @@ set(SOURCES m3uparser.cpp parserbase.cpp playlistparser.cpp + plsparser.cpp xspfparser.cpp ) @@ -13,6 +14,7 @@ set(HEADERS m3uparser.h parserbase.h playlistparser.h + plsparser.h xspfparser.h ) diff --git a/src/playlistparsers/m3uparser.cpp b/src/playlistparsers/m3uparser.cpp index d4d10d8ca..32b5740d5 100644 --- a/src/playlistparsers/m3uparser.cpp +++ b/src/playlistparsers/m3uparser.cpp @@ -92,40 +92,6 @@ bool M3UParser::ParseMetadata(const QString& line, M3UParser::Metadata* metadata return true; } -bool M3UParser::ParseTrackLocation(const QString& line, const QDir& dir, Song* song) const { - if (line.contains(QRegExp("^[a-z]+://"))) { - // Looks like a url. - QUrl temp(line); - if (temp.isValid()) { - song->set_filename(temp.toString()); - return true; - } else { - return false; - } - } - - // Should be a local path. - if (QDir::isAbsolutePath(line)) { - // Absolute path. - // Fix windows \, eg. C:\foo -> C:/foo. - QString proper_path = QDir::fromNativeSeparators(line); - if (!QFile::exists(proper_path)) { - return false; - } - song->set_filename(proper_path); - } else { - // Relative path. - QString proper_path = QDir::fromNativeSeparators(line); - QString absolute_path = dir.absoluteFilePath(proper_path); - if (!QFile::exists(absolute_path)) { - return false; - } - song->set_filename(absolute_path); - } - song->InitFromFile(song->filename(), -1); - return true; -} - void M3UParser::Save(const SongList &songs, QIODevice *device, const QDir &dir) const { // TODO } diff --git a/src/playlistparsers/m3uparser.h b/src/playlistparsers/m3uparser.h index ed06cfdbd..021c34a29 100644 --- a/src/playlistparsers/m3uparser.h +++ b/src/playlistparsers/m3uparser.h @@ -49,7 +49,6 @@ class M3UParser : public ParserBase { }; bool ParseMetadata(const QString& line, Metadata* metadata) const; - bool ParseTrackLocation(const QString& line, const QDir& dir, Song* song) const; FRIEND_TEST(M3UParserTest, ParsesMetadata); FRIEND_TEST(M3UParserTest, ParsesTrackLocation); diff --git a/src/playlistparsers/parserbase.cpp b/src/playlistparsers/parserbase.cpp index 610070143..553f0c20a 100644 --- a/src/playlistparsers/parserbase.cpp +++ b/src/playlistparsers/parserbase.cpp @@ -16,7 +16,41 @@ #include "parserbase.h" +#include + ParserBase::ParserBase(QObject *parent) : QObject(parent) { } + +bool ParserBase::ParseTrackLocation(const QString& filename_or_url, + const QDir& dir, Song* song) const { + if (filename_or_url.contains(QRegExp("^[a-z]+://"))) { + // Looks like a url. + QUrl temp(filename_or_url); + if (temp.isValid()) { + song->set_filename(temp.toString()); + return true; + } else { + return false; + } + } + + // Should be a local path. + if (QDir::isAbsolutePath(filename_or_url)) { + // Absolute path. + // Fix windows \, eg. C:\foo -> C:/foo. + QString proper_path = QDir::fromNativeSeparators(filename_or_url); + if (!QFile::exists(proper_path)) { + return false; + } + song->set_filename(proper_path); + } else { + // Relative path. + QString proper_path = QDir::fromNativeSeparators(filename_or_url); + QString absolute_path = dir.absoluteFilePath(proper_path); + song->set_filename(absolute_path); + } + song->InitFromFile(song->filename(), -1); + return true; +} diff --git a/src/playlistparsers/parserbase.h b/src/playlistparsers/parserbase.h index 0a3305b43..bbf130adf 100644 --- a/src/playlistparsers/parserbase.h +++ b/src/playlistparsers/parserbase.h @@ -32,6 +32,10 @@ public: 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; + +protected: + bool ParseTrackLocation(const QString& filename_or_url, const QDir& dir, + Song* song) const; }; #endif // PARSERBASE_H diff --git a/src/playlistparsers/playlistparser.cpp b/src/playlistparsers/playlistparser.cpp index 30d951071..b8039c765 100644 --- a/src/playlistparsers/playlistparser.cpp +++ b/src/playlistparsers/playlistparser.cpp @@ -17,6 +17,7 @@ #include "playlistparser.h" #include "xspfparser.h" #include "m3uparser.h" +#include "plsparser.h" #include @@ -25,6 +26,7 @@ PlaylistParser::PlaylistParser(QObject *parent) { parsers_ << new M3UParser(this); parsers_ << new XSPFParser(this); + parsers_ << new PLSParser(this); } QStringList PlaylistParser::file_extensions() const { diff --git a/src/playlistparsers/plsparser.cpp b/src/playlistparsers/plsparser.cpp new file mode 100644 index 000000000..40973c434 --- /dev/null +++ b/src/playlistparsers/plsparser.cpp @@ -0,0 +1,74 @@ +/* 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 "plsparser.h" + +#include +#include +#include + +PLSParser::PLSParser(QObject* parent) + : ParserBase(parent) +{ +} + +SongList PLSParser::Load(QIODevice *device, const QDir &dir) const { + QTemporaryFile temp_file; + temp_file.open(); + temp_file.write(device->readAll()); + temp_file.flush(); + + QSettings s(temp_file.fileName(), QSettings::IniFormat); + + SongList ret; + // Use the first group, probably "playlist" but it doesn't matter + if (s.childGroups().isEmpty()) + return ret; + s.beginGroup(s.childGroups()[0]); + + // We try not to rely on NumberOfEntries (it might not be present), so go + // through each key in the file and look at ones that start with "File" + foreach (const QString& key, s.childKeys()) { + if (!key.toLower().startsWith("file")) + continue; + + bool ok = false; + int n = key.mid(4).toInt(&ok); // 4 == "file".length + + if (!ok) + continue; + + QString filename = s.value(key).toString(); + QString title = s.value("Title" + QString::number(n)).toString(); + int length = s.value("Length" + QString::number(n)).toInt(); + + Song song; + song.set_title(title); + song.set_length(length); + + if (!ParseTrackLocation(filename, dir, &song)) { + qWarning() << "Failed to parse location: " << filename; + } else { + ret << song; + } + } + + return ret; +} + +void PLSParser::Save(const SongList &songs, QIODevice *device, const QDir &dir) const { + +} diff --git a/src/playlistparsers/plsparser.h b/src/playlistparsers/plsparser.h new file mode 100644 index 000000000..b5ab97364 --- /dev/null +++ b/src/playlistparsers/plsparser.h @@ -0,0 +1,34 @@ +/* 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 PLSPARSER_H +#define PLSPARSER_H + +#include "parserbase.h" + +class PLSParser : public ParserBase { + Q_OBJECT + +public: + PLSParser(QObject* parent = 0); + + QStringList file_extensions() const { return QStringList() << "pls"; } + + SongList Load(QIODevice* device, const QDir& dir = QDir()) const; + void Save(const SongList& songs, QIODevice* device, const QDir& dir = QDir()) const; +}; + +#endif // PLSPARSER_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3936f1f7c..0103a19a9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -103,3 +103,4 @@ add_test_file(playlist_test.cpp true) add_test_file(scopedtransaction_test.cpp false) add_test_file(fileformats_test.cpp false) add_test_file(mergedproxymodel_test.cpp false) +add_test_file(plsparser_test.cpp false) diff --git a/tests/data/pls_one.pls b/tests/data/pls_one.pls new file mode 100644 index 000000000..ece1833e4 --- /dev/null +++ b/tests/data/pls_one.pls @@ -0,0 +1,6 @@ +[playlist] +File1=filename with spaces.mp3 +Title1=Title +Length1=123 +NumberOfEntries=1 +Version=2 \ No newline at end of file diff --git a/tests/data/pls_somafm.pls b/tests/data/pls_somafm.pls new file mode 100644 index 000000000..a8ef0d52a --- /dev/null +++ b/tests/data/pls_somafm.pls @@ -0,0 +1,15 @@ +[playlist] +numberofentries=4 +File1=http://streamer-dtc-aa05.somafm.com:80/stream/1018 +Title1=SomaFM: Groove Salad (#1 128k mp3): A nicely chilled plate of ambient beats and grooves. +Length1=-1 +File2=http://streamer-mtc-aa03.somafm.com:80/stream/1018 +Title2=SomaFM: Groove Salad (#2 128k mp3): A nicely chilled plate of ambient beats and grooves. +Length2=-1 +File3=http://streamer-ntc-aa04.somafm.com:80/stream/1018 +Title3=SomaFM: Groove Salad (#3 128k mp3): A nicely chilled plate of ambient beats and grooves. +Length3=-1 +File4=http://ice.somafm.com/groovesalad +Title4=SomaFM: Groove Salad (Firewall-friendly 128k mp3) A nicely chilled plate of ambient beats and grooves. +Length4=-1 +Version=2 diff --git a/tests/data/testdata.qrc b/tests/data/testdata.qrc index caa5611eb..76a0f3437 100644 --- a/tests/data/testdata.qrc +++ b/tests/data/testdata.qrc @@ -6,5 +6,7 @@ beep.spx beep.wav beep.wma + pls_one.pls + pls_somafm.pls diff --git a/tests/plsparser_test.cpp b/tests/plsparser_test.cpp new file mode 100644 index 000000000..ceab4684b --- /dev/null +++ b/tests/plsparser_test.cpp @@ -0,0 +1,62 @@ +/* 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 "gtest/gtest.h" + +#include "playlistparsers/plsparser.h" + +#include +#include + +#include + +using boost::shared_ptr; + +class PLSParserTest : public ::testing::Test { +protected: + shared_ptr Open(const QString& filename) { + shared_ptr ret(new QFile(":/testdata/" + filename)); + if (!ret->open(QIODevice::ReadOnly)) + ret.reset(); + return ret; + } + + PLSParser parser_; +}; + +TEST_F(PLSParserTest, ParseOneTrack) { + shared_ptr file(Open("pls_one.pls")); + + SongList songs = parser_.Load(file.get(), QDir("/relative/to/")); + ASSERT_EQ(1, songs.length()); + EXPECT_EQ("/relative/to/filename with spaces.mp3", songs[0].filename()); + EXPECT_EQ("Title", songs[0].title()); + EXPECT_EQ(123, songs[0].length()); +} + +TEST_F(PLSParserTest, ParseSomaFM) { + shared_ptr file(Open("pls_somafm.pls")); + + SongList songs = parser_.Load(file.get()); + ASSERT_EQ(4, songs.length()); + EXPECT_EQ("http://streamer-dtc-aa05.somafm.com:80/stream/1018", songs[0].filename()); + EXPECT_EQ("http://streamer-mtc-aa03.somafm.com:80/stream/1018", songs[1].filename()); + EXPECT_EQ("http://streamer-ntc-aa04.somafm.com:80/stream/1018", songs[2].filename()); + EXPECT_EQ("http://ice.somafm.com/groovesalad", songs[3].filename()); + EXPECT_EQ(-1, songs[0].length()); + EXPECT_EQ(Song::Type_Stream, songs[0].filetype()); +}