1
0
mirror of https://github.com/clementine-player/Clementine synced 2024-12-18 12:28:31 +01:00

Move the playlist parsers to another directory, and add a PlaylistParser class that magically uses the right parser.

This commit is contained in:
David Sansome 2010-05-22 20:06:19 +00:00
parent d054e06444
commit c9c44ca592
16 changed files with 332 additions and 124 deletions

View File

@ -32,6 +32,7 @@ add_subdirectory(core)
add_subdirectory(engines)
add_subdirectory(library)
add_subdirectory(playlist)
add_subdirectory(playlistparsers)
add_subdirectory(radio)
add_subdirectory(transcoder)
add_subdirectory(ui)
@ -59,6 +60,7 @@ target_link_libraries(clementine_lib
clementine_engines
clementine_library
clementine_playlist
clementine_playlistparsers
clementine_radio
clementine_transcoder
clementine_ui

View File

@ -12,7 +12,6 @@ set(SOURCES
globalshortcutbackend.cpp
globalshortcuts.cpp
gnomeglobalshortcutbackend.cpp
m3uparser.cpp
mergedproxymodel.cpp
networkaccessmanager.cpp
player.cpp
@ -22,7 +21,6 @@ set(SOURCES
song.cpp
stylesheetloader.cpp
utilities.cpp
xspfparser.cpp
)
set(HEADERS
@ -32,11 +30,9 @@ set(HEADERS
database.h
globalshortcuts.h
gnomeglobalshortcutbackend.h
m3uparser.h
mergedproxymodel.h
networkaccessmanager.h
player.h
xspfparser.h
)
if(NOT APPLE AND NOT WIN32)

View File

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 2.6)
include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
set(SOURCES
m3uparser.cpp
parserbase.cpp
playlistparser.cpp
xspfparser.cpp
)
set(HEADERS
m3uparser.h
parserbase.h
playlistparser.h
xspfparser.h
)
qt4_wrap_cpp(MOC ${HEADERS})
add_library(clementine_playlistparsers
${SOURCES}
${MOC}
)
target_link_libraries(clementine_playlistparsers
)
add_translation_source(playlistparsers ${SOURCES} ${MOC})

View File

@ -18,54 +18,57 @@
#include <QtDebug>
M3UParser::M3UParser(QIODevice* device, const QDir& directory, QObject* parent)
: QObject(parent),
device_(device),
type_(STANDARD),
directory_(directory) {
M3UParser::M3UParser(QObject* parent)
: ParserBase(parent)
{
}
const QList<Song>& M3UParser::Parse() {
QString line = QString::fromLatin1(device_->readLine()).trimmed();
SongList M3UParser::Load(QIODevice* device, const QDir& dir) const {
SongList ret;
M3UType type = STANDARD;
Metadata current_metadata;
QString line = QString::fromLatin1(device->readLine()).trimmed();
if (line.startsWith("#EXTM3U")) {
// This is in extended M3U format.
type_ = EXTENDED;
line = QString::fromLatin1(device_->readLine()).trimmed();
type = EXTENDED;
line = QString::fromLatin1(device->readLine()).trimmed();
}
forever {
if (line.startsWith('#')) {
// Extended info or comment.
if (type_ == EXTENDED && line.startsWith("#EXT")) {
if (!ParseMetadata(line, &current_metadata_)) {
if (type == EXTENDED && line.startsWith("#EXT")) {
if (!ParseMetadata(line, &current_metadata)) {
qWarning() << "Failed to parse metadata: " << line;
continue;
}
}
} else {
Song song;
song.Init(current_metadata_.title,
current_metadata_.artist,
song.Init(current_metadata.title,
current_metadata.artist,
QString(), // Unknown album.
current_metadata_.length);
current_metadata.length);
// Track location.
QString location;
if (!ParseTrackLocation(line, &song)) {
if (!ParseTrackLocation(line, dir, &song)) {
qWarning() << "Failed to parse location: " << line;
} else {
songs_ << song;
current_metadata_.artist.clear();
current_metadata_.title.clear();
current_metadata_.length = -1;
ret << song;
current_metadata.artist.clear();
current_metadata.title.clear();
current_metadata.length = -1;
}
}
if (device_->atEnd()) {
if (device->atEnd()) {
break;
}
line = QString::fromLatin1(device_->readLine()).trimmed();
line = QString::fromLatin1(device->readLine()).trimmed();
}
return songs_;
return ret;
}
bool M3UParser::ParseMetadata(const QString& line, M3UParser::Metadata* metadata) const {
@ -89,7 +92,7 @@ bool M3UParser::ParseMetadata(const QString& line, M3UParser::Metadata* metadata
return true;
}
bool M3UParser::ParseTrackLocation(const QString& line, Song* song) const {
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);
@ -113,7 +116,7 @@ bool M3UParser::ParseTrackLocation(const QString& line, Song* song) const {
} else {
// Relative path.
QString proper_path = QDir::fromNativeSeparators(line);
QString absolute_path = directory_.absoluteFilePath(proper_path);
QString absolute_path = dir.absoluteFilePath(proper_path);
if (!QFile::exists(absolute_path)) {
return false;
}
@ -122,3 +125,7 @@ bool M3UParser::ParseTrackLocation(const QString& line, Song* song) const {
song->InitFromFile(song->filename(), -1);
return true;
}
void M3UParser::Save(const SongList &songs, QIODevice *device, const QDir &dir) const {
// TODO
}

View File

@ -17,31 +17,22 @@
#ifndef M3UPARSER_H
#define M3UPARSER_H
#include <QDir>
#include <QObject>
#include <QUrl>
#include "gtest/gtest_prod.h"
#include "song.h"
#include "parserbase.h"
class QIODevice;
class M3UParser : public QObject {
class M3UParser : public ParserBase {
Q_OBJECT
public:
M3UParser(QIODevice* device, const QDir& directory = QDir(), QObject* parent = 0);
virtual ~M3UParser() {}
M3UParser(QObject* parent = 0);
// Reference valid as long as the M3UParser instance lives.
const SongList& Parse();
QStringList file_extensions() const { return QStringList() << "m3u"; }
struct Metadata {
Metadata() : length(-1) {}
QString artist;
QString title;
int length;
};
SongList Load(QIODevice* device, const QDir& dir = QDir()) const;
void Save(const SongList &songs, QIODevice* device, const QDir& dir = QDir()) const;
private:
enum M3UType {
@ -50,8 +41,15 @@ class M3UParser : public QObject {
LINK, // Points to a directory.
};
struct Metadata {
Metadata() : length(-1) {}
QString artist;
QString title;
int length;
};
bool ParseMetadata(const QString& line, Metadata* metadata) const;
bool ParseTrackLocation(const QString& line, Song* song) const;
bool ParseTrackLocation(const QString& line, const QDir& dir, Song* song) const;
FRIEND_TEST(M3UParserTest, ParsesMetadata);
FRIEND_TEST(M3UParserTest, ParsesTrackLocation);
@ -60,13 +58,6 @@ class M3UParser : public QObject {
#ifdef Q_OS_WIN32
FRIEND_TEST(M3UParserTest, ParsesTrackLocationAbsoluteWindows);
#endif // Q_OS_WIN32
QIODevice* device_;
M3UType type_;
QDir directory_;
Metadata current_metadata_;
SongList songs_;
};
#endif // M3UPARSER_H

View File

@ -0,0 +1,22 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#include "parserbase.h"
ParserBase::ParserBase(QObject *parent)
: QObject(parent)
{
}

View File

@ -0,0 +1,37 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef PARSERBASE_H
#define PARSERBASE_H
#include <QObject>
#include <QDir>
#include "core/song.h"
class ParserBase : public QObject {
Q_OBJECT
public:
ParserBase(QObject *parent = 0);
virtual QStringList file_extensions() 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;
};
#endif // PARSERBASE_H

View File

@ -0,0 +1,85 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#include "playlistparser.h"
#include "xspfparser.h"
#include "m3uparser.h"
#include <QtDebug>
PlaylistParser::PlaylistParser(QObject *parent)
: QObject(parent)
{
parsers_ << new M3UParser(this);
parsers_ << new XSPFParser(this);
}
QStringList PlaylistParser::file_extensions() const {
QStringList ret;
foreach (ParserBase* parser, parsers_) {
ret << parser->file_extensions();
}
qStableSort(ret);
return ret;
}
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))
return p;
}
return NULL;
}
SongList PlaylistParser::Load(const QString &filename) const {
QFileInfo info(filename);
// Find a parser that supports this file extension
ParserBase* parser = ParserForExtension(info.suffix());
if (!parser) {
qWarning() << "Unknown filetype:" << filename;
return SongList();
}
// Open the file
QFile file(filename);
file.open(QIODevice::ReadOnly);
return parser->Load(&file, info.absolutePath());
}
void PlaylistParser::Save(const SongList &songs, const QString &filename) const {
QFileInfo info(filename);
// Find a parser that supports this file extension
ParserBase* parser = ParserForExtension(info.suffix());
if (!parser) {
qWarning() << "Unknown filetype:" << filename;
return;
}
// Open the file
QFile file(filename);
file.open(QIODevice::WriteOnly);
return parser->Save(songs, &file, info.absolutePath());
}

View File

@ -0,0 +1,44 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef PLAYLISTPARSER_H
#define PLAYLISTPARSER_H
#include <QObject>
#include "core/song.h"
class ParserBase;
class PlaylistParser : public QObject {
Q_OBJECT
public:
PlaylistParser(QObject *parent = 0);
QStringList file_extensions() const;
bool can_load(const QString& filename) const;
SongList Load(const QString& filename) const;
void Save(const SongList& songs, const QString& filename) const;
private:
ParserBase* ParserForExtension(const QString& suffix) const;
QList<ParserBase*> parsers_;
};
#endif // PLAYLISTPARSER_H

View File

@ -21,25 +21,27 @@
#include <QUrl>
#include <QXmlStreamReader>
XSPFParser::XSPFParser(QIODevice* device, QObject* parent)
: QObject(parent),
device_(device) {
XSPFParser::XSPFParser(QObject* parent)
: ParserBase(parent)
{
}
const SongList& XSPFParser::Parse() {
QXmlStreamReader reader(device_);
SongList XSPFParser::Load(QIODevice *device, const QDir&) const {
SongList ret;
QXmlStreamReader reader(device);
if (!ParseUntilElement(&reader, "playlist") ||
!ParseUntilElement(&reader, "trackList")) {
return songs_;
return ret;
}
while (!reader.atEnd() && ParseUntilElement(&reader, "track")) {
Song song = ParseTrack(&reader);
if (song.is_valid()) {
songs_ << song;
ret << song;
}
}
return songs_;
return ret;
}
bool XSPFParser::ParseUntilElement(QXmlStreamReader* reader, const QString& name) const {
@ -133,3 +135,7 @@ Song XSPFParser::ParseTrack(QXmlStreamReader* reader) const {
song.Init(title, artist, album, length);
return song;
}
void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir) const {
}

View File

@ -17,28 +17,25 @@
#ifndef XSPFPARSER_H
#define XSPFPARSER_H
#include "song.h"
#include "parserbase.h"
#include <QObject>
#include <QXmlStreamReader>
class QIODevice;
class XSPFParser : public QObject {
class XSPFParser : public ParserBase {
Q_OBJECT
public:
XSPFParser(QIODevice* device, QObject* parent = 0);
virtual ~XSPFParser() {}
const SongList& Parse();
public:
XSPFParser(QObject* parent = 0);
QStringList file_extensions() const { return QStringList() << "xspf"; }
SongList Load(QIODevice *device, const QDir &dir = QDir()) const;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir()) const;
private:
bool ParseUntilElement(QXmlStreamReader* reader, const QString& element) const;
void IgnoreElement(QXmlStreamReader* reader) const;
Song ParseTrack(QXmlStreamReader* reader) const;
QIODevice* device_;
SongList songs_;
};
#endif

View File

@ -56,6 +56,7 @@ target_link_libraries(clementine_ui
clementine_engines
clementine_library
clementine_playlist
clementine_playlistparsers
clementine_radio
clementine_transcoder
)

View File

@ -20,12 +20,10 @@
#include "core/commandlineoptions.h"
#include "core/database.h"
#include "core/globalshortcuts.h"
#include "core/m3uparser.h"
#include "core/mac_startup.h"
#include "core/mergedproxymodel.h"
#include "core/player.h"
#include "core/stylesheetloader.h"
#include "core/xspfparser.h"
#include "engines/enginebase.h"
#include "library/groupbydialog.h"
#include "library/libraryconfigdialog.h"
@ -37,6 +35,7 @@
#include "playlist/playlistsequence.h"
#include "playlist/playlistview.h"
#include "playlist/songplaylistitem.h"
#include "playlistparsers/playlistparser.h"
#include "radio/lastfmservice.h"
#include "radio/radiomodel.h"
#include "radio/radioview.h"
@ -106,6 +105,7 @@ MainWindow::MainWindow(NetworkAccessManager* network, Engine::Type engine, QWidg
radio_model_(new RadioModel(database_, network, this)),
playlist_backend_(new PlaylistBackend(database_, this)),
playlists_(new PlaylistManager(this)),
playlist_parser_(new PlaylistParser(this)),
player_(new Player(playlists_, radio_model_->GetLastFMService(), engine, this)),
library_(new Library(database_, this)),
global_shortcuts_(new GlobalShortcuts(this)),
@ -930,19 +930,8 @@ void MainWindow::AddFile() {
// Add media
QList<QUrl> urls;
foreach (const QString& path, file_names) {
if (path.endsWith(".m3u")) {
QFile file(path);
QFileInfo info(file);
file.open(QIODevice::ReadOnly);
M3UParser parser(&file, info.dir());
const SongList& songs = parser.Parse();
playlists_->current()->InsertSongs(songs);
} else if (path.endsWith(".xspf") || path.endsWith(".xml")) {
QFile file(path);
file.open(QIODevice::ReadOnly);
XSPFParser parser(&file);
const SongList& songs = parser.Parse();
playlists_->current()->InsertSongs(songs);
if (playlist_parser_->can_load(path)) {
playlists_->current()->InsertSongs(playlist_parser_->Load(path));
} else {
QUrl url(QUrl::fromLocalFile(path));
if (url.scheme().isEmpty())

View File

@ -27,34 +27,35 @@
#include "library/librarymodel.h"
#include "playlist/playlistitem.h"
class PlaylistManager;
class Player;
class Library;
class PlaylistBackend;
class RadioModel;
class Song;
class RadioItem;
class OSD;
class TrackSlider;
class EditTagDialog;
class MultiLoadingIndicator;
class SettingsDialog;
class About;
class AddStreamDialog;
class AlbumCoverManager;
class PlaylistSequence;
class GlobalShortcuts;
class GroupByDialog;
class Equalizer;
class CommandlineOptions;
class TranscodeDialog;
class Database;
class NetworkAccessManager;
class Ui_MainWindow;
class EditTagDialog;
class Equalizer;
class GlobalShortcuts;
class GlobalShortcutsDialog;
class GroupByDialog;
class Library;
class MultiLoadingIndicator;
class NetworkAccessManager;
class OSD;
class Player;
class PlaylistBackend;
class PlaylistManager;
class PlaylistParser;
class PlaylistSequence;
class RadioItem;
class RadioModel;
class SettingsDialog;
class Song;
class SystemTrayIcon;
class TrackSlider;
class TranscodeDialog;
class Ui_MainWindow;
class QSortFilterProxyModel;
class SystemTrayIcon;
class MainWindow : public QMainWindow {
Q_OBJECT
@ -162,6 +163,7 @@ class MainWindow : public QMainWindow {
RadioModel* radio_model_;
PlaylistBackend* playlist_backend_;
PlaylistManager* playlists_;
PlaylistParser* playlist_parser_;
Player* player_;
Library* library_;
GlobalShortcuts* global_shortcuts_;

View File

@ -18,7 +18,7 @@
#include "test_utils.h"
#include "mock_taglib.h"
#include "core/m3uparser.h"
#include "playlistparsers/m3uparser.h"
#include <QBuffer>
#include <QTemporaryFile>
@ -52,7 +52,7 @@ TEST_F(M3UParserTest, ParsesTrackLocation) {
taglib_.ExpectCall(temp.fileName(), "foo", "bar", "baz");
Song song(&taglib_);
QString line(temp.fileName());
ASSERT_TRUE(parser_.ParseTrackLocation(line, &song));
ASSERT_TRUE(parser_.ParseTrackLocation(line, QDir(), &song));
EXPECT_EQ(temp.fileName(), song.filename());
EXPECT_EQ("foo", song.title());
EXPECT_EQ("bar", song.artist());
@ -64,10 +64,10 @@ TEST_F(M3UParserTest, ParsesTrackLocationRelative) {
temp.open();
QFileInfo info(temp);
taglib_.ExpectCall(temp.fileName(), "foo", "bar", "baz");
M3UParser parser(NULL, info.dir());
M3UParser parser;
QString line(info.fileName());
Song song(&taglib_);
ASSERT_TRUE(parser.ParseTrackLocation(line, &song));
ASSERT_TRUE(parser.ParseTrackLocation(line, info.dir(), &song));
EXPECT_EQ(temp.fileName(), song.filename());
EXPECT_EQ("foo", song.title());
}
@ -75,7 +75,7 @@ TEST_F(M3UParserTest, ParsesTrackLocationRelative) {
TEST_F(M3UParserTest, ParsesTrackLocationHttp) {
QString line("http://example.com/foo/bar.mp3");
Song song;
ASSERT_TRUE(parser_.ParseTrackLocation(line, &song));
ASSERT_TRUE(parser_.ParseTrackLocation(line, QDir(), &song));
EXPECT_EQ("http://example.com/foo/bar.mp3", song.filename());
}
@ -85,8 +85,8 @@ TEST_F(M3UParserTest, ParsesSongsFromDevice) {
"http://foo.com/bar/somefile.mp3\n";
QBuffer buffer(&data);
buffer.open(QIODevice::ReadOnly);
M3UParser parser(&buffer);
const QList<Song>& songs = parser.Parse();
M3UParser parser;
SongList songs = parser.Load(&buffer);
ASSERT_EQ(1, songs.size());
Song s = songs[0];
EXPECT_EQ("Some Artist", s.artist());
@ -101,8 +101,8 @@ TEST_F(M3UParserTest, ParsesNonExtendedM3U) {
"http://baz.com/thing.mp3\n";
QBuffer buffer(&data);
buffer.open(QIODevice::ReadOnly);
M3UParser parser(&buffer, QDir("somedir"));
const QList<Song>& songs = parser.Parse();
M3UParser parser;
SongList songs = parser.Load(&buffer, QDir("somedir"));
ASSERT_EQ(2, songs.size());
EXPECT_PRED_FORMAT2(::testing::IsSubstring,
"http://foo.com/bar/somefile.mp3", songs[0].filename().toStdString());

View File

@ -17,7 +17,7 @@
#include "test_utils.h"
#include "gtest/gtest.h"
#include "core/xspfparser.h"
#include "playlistparsers/xspfparser.h"
#include <QBuffer>
@ -38,8 +38,8 @@ TEST_F(XSPFParserTest, ParsesOneTrackFromXML) {
"</track></trackList></playlist>";
QBuffer buffer(&data);
buffer.open(QIODevice::ReadOnly);
XSPFParser parser(&buffer);
const SongList& songs = parser.Parse();
XSPFParser parser;
SongList songs = parser.Load(&buffer);
ASSERT_EQ(1, songs.length());
const Song& song = songs[0];
EXPECT_EQ("Foo", song.title());
@ -62,8 +62,8 @@ TEST_F(XSPFParserTest, ParsesMoreThanOneTrackFromXML) {
"</trackList></playlist>";
QBuffer buffer(&data);
buffer.open(QIODevice::ReadOnly);
XSPFParser parser(&buffer);
const SongList& songs = parser.Parse();
XSPFParser parser;
SongList songs = parser.Load(&buffer);
ASSERT_EQ(2, songs.length());
EXPECT_EQ("http://example.com/foo.mp3", songs[0].filename());
EXPECT_EQ("http://example.com/bar.mp3", songs[1].filename());
@ -84,8 +84,8 @@ TEST_F(XSPFParserTest, IgnoresInvalidLength) {
"</track></trackList></playlist>";
QBuffer buffer(&data);
buffer.open(QIODevice::ReadOnly);
XSPFParser parser(&buffer);
const SongList& songs = parser.Parse();
XSPFParser parser;
SongList songs = parser.Load(&buffer);
ASSERT_EQ(1, songs.length());
EXPECT_EQ(-1, songs[0].length());
}