diff --git a/src/library/parser.cpp b/src/library/parser.cpp index 4e7034038bf..762ec68272c 100644 --- a/src/library/parser.cpp +++ b/src/library/parser.cpp @@ -1,36 +1,57 @@ -// -// C++ Implementation: parser -// -// Description: superclass for external formats parsers -// -// -// Author: Ingo Kossyk , (C) 2004 -// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011 -// -// Copyright: See COPYING file that comes with this distribution -// -// +#include "library/parser.h" -#include #include #include #include #include +#include -#include "library/parser.h" +#include "library/parsercsv.h" +#include "library/parserm3u.h" +#include "library/parserpls.h" -Parser::Parser() { +// static +bool Parser::isPlaylistFilenameSupported(const QString& playlistFile) { + return ParserM3u::isPlaylistFilenameSupported(playlistFile) || + ParserPls::isPlaylistFilenameSupported(playlistFile) || + ParserCsv::isPlaylistFilenameSupported(playlistFile); } -Parser::~Parser() { -} +// static +QList Parser::parseAllLocations(const QString& playlistFile) { + if (ParserM3u::isPlaylistFilenameSupported(playlistFile)) { + return ParserM3u::parseAllLocations(playlistFile); + } + + if (ParserPls::isPlaylistFilenameSupported(playlistFile)) { + return ParserPls::parseAllLocations(playlistFile); + } + + if (ParserCsv::isPlaylistFilenameSupported(playlistFile)) { + return ParserCsv::parseAllLocations(playlistFile); + } -void Parser::clearLocations() { - m_sLocations.clear(); + return QList(); } -long Parser::countParsed() { - return (long)m_sLocations.count(); +// static +QList Parser::parse(const QString& playlistFile) { + const QList allLocations = parseAllLocations(playlistFile); + + QFileInfo fileInfo(playlistFile); + + QList existingLocations; + for (const auto& location : allLocations) { + mixxx::FileInfo trackFile = Parser::playlistEntryToFileInfo( + location, fileInfo.canonicalPath()); + if (trackFile.checkFileExists()) { + existingLocations.append(trackFile.location()); + } else { + qInfo() << "File" << trackFile.location() << "from playlist" + << playlistFile << "does not exist."; + } + } + return existingLocations; } // The following public domain code is taken from @@ -115,6 +136,7 @@ bool Parser::isUtf8(const char* string) { return true; } +// static mixxx::FileInfo Parser::playlistEntryToFileInfo( const QString& playlistEntry, const QString& basePath) { diff --git a/src/library/parser.h b/src/library/parser.h index ccad1acddb8..eeb5878462e 100644 --- a/src/library/parser.h +++ b/src/library/parser.h @@ -26,34 +26,17 @@ it afterwards for proper functioning #include "util/fileinfo.h" -class Parser : public QObject { +class Parser { public: - static bool isPlaylistFilenameSupported(const QString& fileName) { - return fileName.endsWith(".m3u", Qt::CaseInsensitive) || - fileName.endsWith(".m3u8", Qt::CaseInsensitive) || - fileName.endsWith(".pls", Qt::CaseInsensitive) || - fileName.endsWith(".csv", Qt::CaseInsensitive); - } - - Parser(); - ~Parser() override; - /**Can be called to parse a pls file - Note for developers: - This function should return an empty PtrList - or 0 in order for the trackimporter to function**/ - virtual QList parse(const QString&) = 0; + static bool isPlaylistFilenameSupported(const QString& playlistFile); + static QList parseAllLocations(const QString& playlistFile); + static QList parse(const QString& playlistFile); protected: - // Pointer to the parsed Filelocations - QList m_sLocations; - // Returns the number of parsed locations - long countParsed(); - // Clears m_psLocations - void clearLocations(); // check for Utf8 encoding static bool isUtf8(const char* string); // Resolve an absolute or relative file path - mixxx::FileInfo playlistEntryToFileInfo( + static mixxx::FileInfo playlistEntryToFileInfo( const QString& playlistEntry, const QString& basePath = QString()); }; diff --git a/src/library/parsercsv.cpp b/src/library/parsercsv.cpp index 86c58f014dc..303df763e8f 100644 --- a/src/library/parsercsv.cpp +++ b/src/library/parsercsv.cpp @@ -1,17 +1,3 @@ -// -// C++ Implementation: parsercsv -// -// Description: module to parse Comma-Separated Values (CSV) formatted playlists (rfc4180) -// -// -// Author: Ingo Kossyk , (C) 2004 -// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011 -// Author: Daniel Schürmann daschuer@gmx.de, (C) 2011 -// -// Copyright: See COPYING file that comes with this distribution -// -// - #include "library/parsercsv.h" #include @@ -19,19 +5,18 @@ #include #include -#include "moc_parsercsv.cpp" - -ParserCsv::ParserCsv() : Parser() { -} +#include "library/parser.h" -ParserCsv::~ParserCsv() { +// static +bool ParserCsv::isPlaylistFilenameSupported(const QString& playlistFile) { + return playlistFile.endsWith(".csv", Qt::CaseInsensitive); } -QList ParserCsv::parse(const QString& sFilename) { - QFile file(sFilename); - QString basepath = sFilename.section('/', 0, -2); +// static +QList ParserCsv::parseAllLocations(const QString& playlistFile) { + QFile file(playlistFile); - clearLocations(); + QList locations; //qDebug() << "ParserCsv: Starting to parse."; if (file.open(QIODevice::ReadOnly)) { QByteArray bytes = file.readAll(); @@ -39,39 +24,38 @@ QList ParserCsv::parse(const QString& sFilename) { QList> tokens = tokenize(bytes, ','); // detect Location column - int loc_coll = 0x7fffffff; + int locationColumnIndex = -1; if (tokens.size()) { for (int i = 0; i < tokens[0].size(); ++i) { - if (tokens[0][i] == tr("Location")) { - loc_coll = i; + if (tokens[0][i] == QObject::tr("Location")) { + locationColumnIndex = i; break; } } - for (int i = 1; i < tokens.size(); ++i) { - if (loc_coll < tokens[i].size()) { - // Todo: check if path is relative - QFileInfo fi(tokens[i][loc_coll]); - if (fi.isRelative()) { - // add base path - qDebug() << "is relative" << basepath << fi.filePath(); - fi.setFile(basepath,fi.filePath()); + if (locationColumnIndex < 0 && tokens.size() > 1) { + // Last resort, find column with path separators + // This happens in case of csv files in a different language + for (int i = 0; i < tokens[1].size(); ++i) { + if (tokens[1][i].contains(QDir::separator())) { + locationColumnIndex = i; + break; + } + } + } + if (locationColumnIndex >= 0) { + for (int row = 1; row < tokens.size(); ++row) { + if (locationColumnIndex < tokens[row].size()) { + locations.append(tokens[row][locationColumnIndex]); } - m_sLocations.append(fi.filePath()); } + } else { + qInfo() << "No location column found in" + << playlistFile; } } - file.close(); - - if (m_sLocations.count() != 0) { - return m_sLocations; - } else { - return QList(); // NULL pointer returned when no locations were found - } } - - file.close(); - return QList(); //if we get here something went wrong + return locations; } // Code was posted at http://www.qtcentre.org/threads/35511-Parsing-CSV-data @@ -90,21 +74,23 @@ QList> ParserCsv::tokenize(const QByteArray& str, char delimiter) if (!quotes && c == '"') { quotes = true; } else if (quotes && c== '"' ) { - if (pos + 1 < str.length() && str[pos+1]== '"') { + if (pos + 1 < str.length() && str[pos + 1] == '"') { field.append(c); pos++; } else { quotes = false; } } else if (!quotes && c == delimiter) { - if (isUtf8(field.constData())) { + if (Parser::isUtf8(field.constData())) { tokens[row].append(QString::fromUtf8(field)); } else { tokens[row].append(QString::fromLatin1(field)); } field.clear(); + } else if (!quotes && c == '\r' && str[pos + 1] == '\n') { + // skip \r in \r\n } else if (!quotes && (c == '\r' || c == '\n')) { - if (isUtf8(field.constData())) { + if (Parser::isUtf8(field.constData())) { tokens[row].append(QString::fromUtf8(field)); } else { tokens[row].append(QString::fromLatin1(field)); @@ -130,8 +116,8 @@ bool ParserCsv::writeCSVFile(const QString &file_str, BaseSqlTableModel* pPlayli QFile file(file_str); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(nullptr, - tr("Playlist Export Failed"), - tr("Could not create file") + " " + file_str); + QObject::tr("Playlist Export Failed"), + QObject::tr("Could not create file") + " " + file_str); return false; } //Base folder of file @@ -221,8 +207,8 @@ bool ParserCsv::writeReadableTextFile(const QString &file_str, BaseSqlTableModel QFile file(file_str); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(nullptr, - tr("Readable text Export Failed"), - tr("Could not create file") + " " + file_str); + QObject::tr("Readable text Export Failed"), + QObject::tr("Could not create file") + " " + file_str); return false; } diff --git a/src/library/parsercsv.h b/src/library/parsercsv.h index 360938e134f..28b2d53d210 100644 --- a/src/library/parsercsv.h +++ b/src/library/parsercsv.h @@ -20,19 +20,17 @@ #include "library/parser.h" #include "library/basesqltablemodel.h" -class ParserCsv : public Parser -{ - Q_OBJECT -public: - ParserCsv(); - virtual ~ParserCsv(); - /**Overwriting function parse in class Parser**/ - QList parse(const QString&); +class ParserCsv : public Parser { + public: + // static + static bool isPlaylistFilenameSupported(const QString& playlistFile); + static QList parseAllLocations(const QString&); // Playlist Export static bool writeCSVFile(const QString &file, BaseSqlTableModel* pPlaylistTableModel, bool useRelativePath); // Readable Text export static bool writeReadableTextFile(const QString &file, BaseSqlTableModel* pPlaylistTableModel, bool writeTimestamp); -private: - /**Reads a line from the file and returns filepath if a valid file**/ - QList> tokenize(const QByteArray& str, char delimiter); + + private: + // Reads a line from the file and returns filepath if a valid file + static QList> tokenize(const QByteArray& str, char delimiter); }; diff --git a/src/library/parserm3u.cpp b/src/library/parserm3u.cpp index d07c2a326e1..336b9a12550 100644 --- a/src/library/parserm3u.cpp +++ b/src/library/parserm3u.cpp @@ -15,16 +15,20 @@ #include #include +#include #include #include #include -#include "moc_parserm3u.cpp" namespace { // according to http://en.wikipedia.org/wiki/M3U the default encoding of m3u is Windows-1252 // see also http://tools.ietf.org/html/draft-pantos-http-live-streaming-07 -const char* kStandardM3uTextEncoding = "Windows-1250"; +const char kStandardM3uTextEncoding[] = "Windows-1250"; +const char kM3uHeader[] = "#EXTM3U"; +const char kM3uCommentPrefix[] = "#"; +// Note: The RegEx pattern is compiled, when first used the first time +const auto kUniveralEndOfLineRegEx = QRegularExpression(QStringLiteral("\r\n|\r|\n")); } // anonymous namespace /** @@ -42,43 +46,26 @@ const char* kStandardM3uTextEncoding = "Windows-1250"; or on a mounted harddrive. **/ -ParserM3u::ParserM3u() : Parser() -{ -} - -ParserM3u::~ParserM3u() -{ - +// static +bool ParserM3u::isPlaylistFilenameSupported(const QString& fileName) { + return fileName.endsWith(".m3u", Qt::CaseInsensitive) || + fileName.endsWith(".m3u8", Qt::CaseInsensitive); } -QList ParserM3u::parse(const QString& filename) { +QList ParserM3u::parseAllLocations(const QString& playlistFile) { QList paths; - QFile file(filename); + QFile file(playlistFile); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Failed to open playlist file" - << filename; + << playlistFile; return paths; } - // Unfortunately QTextStream does not handle (=\r or asci value 13) line breaks. - // This is important on OS X where iTunes, e.g., exports M3U playlists using - // rather that . - // - // Using QFile::readAll() we obtain the complete content of the playlist as a ByteArray. - // We replace any '\r' with '\n' if applicaple - // This ensures that playlists from iTunes on OS X can be parsed QByteArray byteArray = file.readAll(); - //detect encoding - bool isCRLF_encoded = byteArray.contains("\r\n"); - bool isCR_encoded = byteArray.contains("\r"); - if (isCR_encoded && !isCRLF_encoded) { - byteArray.replace('\r', '\n'); - } - QString fileContents; - if (isUtf8(byteArray.constData())) { + if (Parser::isUtf8(byteArray.constData())) { fileContents = QString::fromUtf8(byteArray); } else { // FIXME: replace deprecated QTextCodec with direct usage of libicu @@ -86,18 +73,18 @@ QList ParserM3u::parse(const QString& filename) { ->toUnicode(byteArray); } - QFileInfo fileInfo(filename); - const QStringList fileLines = fileContents.split('\n'); + if (!fileContents.startsWith(kM3uHeader)) { + qWarning() << "M3U playlist file" << playlistFile << "does not start with" << kM3uHeader; + } + + const QStringList fileLines = fileContents.split(kUniveralEndOfLineRegEx); for (const QString& line : fileLines) { - auto trackFile = playlistEntryToFileInfo(line, fileInfo.canonicalPath()); - if (trackFile.checkFileExists()) { - paths.append(trackFile.location()); - } else { - qInfo() << "File" << trackFile.location() << "from M3U playlist" - << filename << "does not exist."; + if (line.startsWith(kM3uCommentPrefix)) { + // Skip lines with comments + continue; } + paths.append(line); } - return paths; } @@ -139,8 +126,8 @@ bool ParserM3u::writeM3UFile(const QString &file_str, const QList &item QFile file(file_str); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(nullptr, - tr("Playlist Export Failed"), - tr("Could not create file") + " " + file_str); + QObject::tr("Playlist Export Failed"), + QObject::tr("Could not create file") + " " + file_str); return false; } file.write(outputByteArray); diff --git a/src/library/parserm3u.h b/src/library/parserm3u.h index ea1d497a489..3836ede7f77 100644 --- a/src/library/parserm3u.h +++ b/src/library/parserm3u.h @@ -19,15 +19,11 @@ #include "library/parser.h" -class ParserM3u : public Parser -{ - Q_OBJECT -public: - ParserM3u(); - ~ParserM3u(); - /**Overwriting function parse in class Parser**/ - QList parse(const QString&); - //Playlist Export +class ParserM3u : public Parser { + public: + static bool isPlaylistFilenameSupported(const QString& fileName); + static QList parseAllLocations(const QString& playlistFile); + /// Playlist Export static bool writeM3UFile(const QString &file_str, const QList &items, bool useRelativePath, bool useUtf8); static bool writeM3UFile(const QString &file, const QList &items, bool useRelativePath); static bool writeM3U8File(const QString &file_str, const QList &items, bool useRelativePath); diff --git a/src/library/parserpls.cpp b/src/library/parserpls.cpp index 5691e96a630..aca876841dd 100644 --- a/src/library/parserpls.cpp +++ b/src/library/parserpls.cpp @@ -1,15 +1,3 @@ -// -// C++ Implementation: parserpls -// -// Description: module to parse pls formatted playlists -// -// -// Author: Ingo Kossyk , (C) 2004 -// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011 -// -// Copyright: See COPYING file that comes with this distribution -// -// #include "library/parserpls.h" #include @@ -18,27 +6,40 @@ #include #include -#include "moc_parserpls.cpp" +#include "library/parser.h" -/** - ToDo: - - parse ALL information from the pls file if available , - not only the filepath; - **/ +namespace { -ParserPls::ParserPls() : Parser() { -} +QString getFilePath(QTextStream* pStream) { + QString textline = pStream->readLine(); + while (!textline.isEmpty()) { + if (textline.isNull()) { + break; + } -ParserPls::~ParserPls() { + if (textline.contains("File")) { + int iPos = textline.indexOf("=", 0); + ++iPos; + return textline.right(textline.length() - iPos); + } + textline = pStream->readLine(); + } + // Signal we reached the end + return QString(); } -QList ParserPls::parse(const QString& sFilename) { - //long numEntries =0; - QFile file(sFilename); - const auto basePath = sFilename.section('/', 0, -2); +} // namespace - clearLocations(); +// static +bool ParserPls::isPlaylistFilenameSupported(const QString& playlistFile) { + return playlistFile.endsWith(".pls", Qt::CaseInsensitive); +} + +// static +QList ParserPls::parseAllLocations(const QString& playlistFile) { + QFile file(playlistFile); + QList locations; if (file.open(QIODevice::ReadOnly)) { /* Unfortunately, QT 4.7 does not handle (=\r or asci value 13) line breaks. * This is important on OS X where iTunes, e.g., exports M3U playlists using @@ -58,73 +59,18 @@ QList ParserPls::parse(const QString& sFilename) { QTextStream textstream(ba.constData()); while(!textstream.atEnd()) { - QString psLine = getFilePath(&textstream, basePath); + QString psLine = getFilePath(&textstream); if(psLine.isNull()) { break; } else { - m_sLocations.append(psLine); + locations.append(psLine); } } file.close(); - - if (m_sLocations.count() != 0) { - return m_sLocations; - } else { - return QList(); // NULL pointer returned when no locations were found - } } - - file.close(); - return QList(); //if we get here something went wrong :D -} - -long ParserPls::getNumEntries(QTextStream *stream) { - QString textline; - textline = stream->readLine(); - - if (textline.contains("[playlist]")) { - while (!textline.contains("NumberOfEntries")) { - textline = stream->readLine(); - } - - QString temp = textline.section("=",-1,-1); - - return temp.toLong(); - - } else{ - qDebug() << "ParserPls: pls file is not a playlist! \n"; - return 0; - } - -} - - -QString ParserPls::getFilePath(QTextStream *stream, const QString& basePath) { - QString textline = stream->readLine(); - while (!textline.isEmpty()) { - if (textline.isNull()) { - break; - } - - if(textline.contains("File")) { - int iPos = textline.indexOf("=", 0); - ++iPos; - - QString filename = textline.right(textline.length() - iPos); - auto trackFile = playlistEntryToFileInfo(filename, basePath); - if (trackFile.checkFileExists()) { - return trackFile.location(); - } - // We couldn't match this to a real file so ignore it - qWarning() << trackFile << "not found"; - } - textline = stream->readLine(); - } - - // Signal we reached the end - return QString(); + return locations; } bool ParserPls::writePLSFile(const QString &file_str, const QList &items, bool useRelativePath) @@ -132,8 +78,8 @@ bool ParserPls::writePLSFile(const QString &file_str, const QList &item QFile file(file_str); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(nullptr, - tr("Playlist Export Failed"), - tr("Could not create file") + " " + file_str); + QObject::tr("Playlist Export Failed"), + QObject::tr("Could not create file") + " " + file_str); return false; } //Base folder of file diff --git a/src/library/parserpls.h b/src/library/parserpls.h index a7ca089da3a..b19c9b65c15 100644 --- a/src/library/parserpls.h +++ b/src/library/parserpls.h @@ -11,26 +11,16 @@ // #pragma once -#include "library/parser.h" - -#include #include #include +#include + +#include "library/parser.h" class ParserPls : public Parser { - Q_OBJECT public: - ParserPls(); - virtual ~ParserPls(); - /**Can be called to parse a pls file**/ - QList parse(const QString&); - //Playlist Export + static bool isPlaylistFilenameSupported(const QString& fileName); + static QList parseAllLocations(const QString& playlistFile); + /// Playlist Export static bool writePLSFile(const QString &file, const QList &items, bool useRelativePath); - - private: - /**Returns the Number of entries in the pls file**/ - long getNumEntries(QTextStream*); - /**Reads a line from the file and returns filepath**/ - QString getFilePath(QTextStream*, const QString& basePath); - }; diff --git a/src/library/trackset/baseplaylistfeature.cpp b/src/library/trackset/baseplaylistfeature.cpp index db913369915..64a8f4e704f 100644 --- a/src/library/trackset/baseplaylistfeature.cpp +++ b/src/library/trackset/baseplaylistfeature.cpp @@ -439,28 +439,9 @@ void BasePlaylistFeature::slotImportPlaylistFile(const QString& playlist_file) { // folder. We don't need access to this file on a regular basis so we do not // register a security bookmark. - Parser* playlist_parser = nullptr; - - if (playlist_file.endsWith(".m3u", Qt::CaseInsensitive) || - playlist_file.endsWith(".m3u8", Qt::CaseInsensitive)) { - playlist_parser = new ParserM3u(); - } else if (playlist_file.endsWith(".pls", Qt::CaseInsensitive)) { - playlist_parser = new ParserPls(); - } else if (playlist_file.endsWith(".csv", Qt::CaseInsensitive)) { - playlist_parser = new ParserCsv(); - } else { - return; - } - - if (playlist_parser) { - QStringList entries = playlist_parser->parse(playlist_file); - - // Iterate over the List that holds URLs of playlist entries - m_pPlaylistTableModel->addTracks(QModelIndex(), entries); - - // delete the parser object - delete playlist_parser; - } + QList locations = Parser::parse(playlist_file); + // Iterate over the List that holds locations of playlist entries + m_pPlaylistTableModel->addTracks(QModelIndex(), locations); } void BasePlaylistFeature::slotCreateImportPlaylist() { diff --git a/src/library/trackset/crate/cratefeature.cpp b/src/library/trackset/crate/cratefeature.cpp index 37c5cbcef3c..c99f4ba5973 100644 --- a/src/library/trackset/crate/cratefeature.cpp +++ b/src/library/trackset/crate/cratefeature.cpp @@ -605,19 +605,11 @@ void CrateFeature::slotImportPlaylistFile(const QString& playlist_file) { // register a security bookmark. // TODO(XXX): Parsing a list of track locations from a playlist file // is a general task and should be implemented separately. - QList entries; - if (playlist_file.endsWith(".m3u", Qt::CaseInsensitive) || - playlist_file.endsWith(".m3u8", Qt::CaseInsensitive)) { - // .m3u8 is Utf8 representation of an m3u playlist - entries = ParserM3u().parse(playlist_file); - } else if (playlist_file.endsWith(".pls", Qt::CaseInsensitive)) { - entries = ParserPls().parse(playlist_file); - } else if (playlist_file.endsWith(".csv", Qt::CaseInsensitive)) { - entries = ParserCsv().parse(playlist_file); - } else { + QList locations = Parser().parse(playlist_file); + if (locations.empty()) { return; } - m_crateTableModel.addTracks(QModelIndex(), entries); + m_crateTableModel.addTracks(QModelIndex(), locations); } void CrateFeature::slotCreateImportCrate() { diff --git a/src/test/playlisttest.cpp b/src/test/playlisttest.cpp index 3470403b49b..9fb6a585d0b 100644 --- a/src/test/playlisttest.cpp +++ b/src/test/playlisttest.cpp @@ -1,21 +1,22 @@ #include +#include #include +#include #include #include #include "library/parser.h" +#include "library/parsercsv.h" +#include "library/parserm3u.h" +#include "library/parserpls.h" class DummyParser : public Parser { public: - QList parse(const QString&) override { - return QList(); - } - QString playlistEntryToFilePath( const QString& playlistEntry, const QString& basePath = QString()) { - const auto fileInfo = playlistEntryToFileInfo(playlistEntry, basePath); + const auto fileInfo = Parser::playlistEntryToFileInfo(playlistEntry, basePath); // Return the plain, literal file path, because the location // is undefined if relative paths. return fileInfo.asQFileInfo().filePath(); @@ -56,3 +57,52 @@ TEST_F(PlaylistTest, Relative) { EXPECT_EQ(QString("base/folder/../../bar.mp3"), parser.playlistEntryToFilePath("../../bar.mp3", "base/folder")); } + +TEST_F(PlaylistTest, M3uEndOfLine) { + QTemporaryFile m3uFile; + ASSERT_TRUE(m3uFile.open()); + m3uFile.write("crlf.mp3\r\n"); + m3uFile.write("cr.mp3\r"); + m3uFile.write("lf.mp3\n"); + // Check for Windows-1250 Euro Sign + m3uFile.write("EuroSign\x80.mp3\n"); + m3uFile.write("end.mp3"); + m3uFile.close(); + + const QList entries = ParserM3u().parseAllLocations(m3uFile.fileName()); + ASSERT_EQ(entries.size(), 5); + EXPECT_TRUE(entries.at(0).endsWith(QStringLiteral("crlf.mp3"))); + EXPECT_TRUE(entries.at(1).endsWith(QStringLiteral("cr.mp3"))); + EXPECT_TRUE(entries.at(2).endsWith(QStringLiteral("lf.mp3"))); + EXPECT_TRUE(entries.at(3).endsWith(QStringLiteral("EuroSign\u20AC.mp3"))); + EXPECT_TRUE(entries.at(4).endsWith(QStringLiteral("end.mp3"))); +} + +TEST_F(PlaylistTest, CsvEndOfLine) { + QTemporaryFile csvFile; + ASSERT_TRUE(csvFile.open()); + csvFile.write("#,Location\r\n"); + csvFile.write("1,cr.mp3\r"); + csvFile.write("2,lf.mp3\n"); + csvFile.close(); + + const QList entries = ParserCsv().parseAllLocations(csvFile.fileName()); + ASSERT_EQ(entries.size(), 2); + EXPECT_TRUE(entries.at(0).endsWith(QStringLiteral("cr.mp3"))); + EXPECT_TRUE(entries.at(1).endsWith(QStringLiteral("lf.mp3"))); +} + +TEST_F(PlaylistTest, PlsEndOfLine) { + QTemporaryFile plsFile; + ASSERT_TRUE(plsFile.open()); + plsFile.write("[playlist]\n"); + plsFile.write("NumberOfEntries=2\r"); + plsFile.write("File0=cr.mp3\r"); + plsFile.write("File1=lf.mp3\n"); + plsFile.close(); + + const QList entries = ParserPls().parseAllLocations(plsFile.fileName()); + ASSERT_EQ(entries.size(), 2); + EXPECT_TRUE(entries.at(0).endsWith(QStringLiteral("cr.mp3"))); + EXPECT_TRUE(entries.at(1).endsWith(QStringLiteral("lf.mp3"))); +} diff --git a/src/util/dnd.cpp b/src/util/dnd.cpp index e201d0e677b..10dbef0e30e 100644 --- a/src/util/dnd.cpp +++ b/src/util/dnd.cpp @@ -115,16 +115,9 @@ QList DragAndDropHelper::supportedTracksFromUrls( continue; } - if (acceptPlaylists && (file.endsWith(".m3u") || file.endsWith(".m3u8"))) { - QScopedPointer playlist_parser(new ParserM3u()); - QList track_list = playlist_parser->parse(file); - foreach (const QString& playlistFile, track_list) { - addFileToList(mixxx::FileInfo(playlistFile), &fileInfos); - } - } else if (acceptPlaylists && url.toString().endsWith(".pls")) { - QScopedPointer playlist_parser(new ParserPls()); - QList track_list = playlist_parser->parse(file); - foreach (const QString& playlistFile, track_list) { + if (acceptPlaylists && Parser::isPlaylistFilenameSupported(file)) { + const QList track_list = Parser::parse(file); + for (auto& playlistFile : track_list) { addFileToList(mixxx::FileInfo(playlistFile), &fileInfos); } } else {