1 //
2 // C++ Implementation: parserpls
3 //
4 // Description: module to parse pls formatted playlists
5 //
6 //
7 // Author: Ingo Kossyk <kossyki@cs.tu-berlin.de>, (C) 2004
8 // Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011
9 //
10 // Copyright: See COPYING file that comes with this distribution
11 //
12 //
13 #include "library/parserpls.h"
14 
15 #include <QDir>
16 #include <QFile>
17 #include <QMessageBox>
18 #include <QUrl>
19 #include <QtDebug>
20 
21 #include "moc_parserpls.cpp"
22 
23 /**
24    ToDo:
25     - parse ALL information from the pls file if available ,
26           not only the filepath;
27  **/
28 
ParserPls()29 ParserPls::ParserPls() : Parser() {
30 }
31 
~ParserPls()32 ParserPls::~ParserPls() {
33 }
34 
parse(const QString & sFilename)35 QList<QString> ParserPls::parse(const QString& sFilename) {
36     //long numEntries =0;
37     QFile file(sFilename);
38     const auto basePath = sFilename.section('/', 0, -2);
39 
40     clearLocations();
41 
42     if (file.open(QIODevice::ReadOnly)) {
43         /* Unfortunately, QT 4.7 does not handle <CR> (=\r or asci value 13) line breaks.
44          * This is important on OS X where iTunes, e.g., exports M3U playlists using <CR>
45          * rather that <LF>
46          *
47          * Using QFile::readAll() we obtain the complete content of the playlist as a ByteArray.
48          * We replace any '\r' with '\n' if applicaple
49          * This ensures that playlists from iTunes on OS X can be parsed
50          */
51         QByteArray ba = file.readAll();
52         //detect encoding
53         bool isCRLF_encoded = ba.contains("\r\n");
54         bool isCR_encoded = ba.contains("\r");
55         if (isCR_encoded && !isCRLF_encoded) {
56             ba.replace('\r','\n');
57         }
58         QTextStream textstream(ba.constData());
59 
60         while(!textstream.atEnd()) {
61             QString psLine = getFilePath(&textstream, basePath);
62             if(psLine.isNull()) {
63                 break;
64             } else {
65                 m_sLocations.append(psLine);
66             }
67 
68         }
69 
70         file.close();
71 
72         if (m_sLocations.count() != 0) {
73             return m_sLocations;
74         } else {
75             return QList<QString>(); // NULL pointer returned when no locations were found
76         }
77     }
78 
79     file.close();
80     return QList<QString>(); //if we get here something went wrong :D
81 }
82 
getNumEntries(QTextStream * stream)83 long ParserPls::getNumEntries(QTextStream *stream) {
84     QString textline;
85     textline = stream->readLine();
86 
87     if (textline.contains("[playlist]")) {
88         while (!textline.contains("NumberOfEntries")) {
89             textline = stream->readLine();
90         }
91 
92         QString temp = textline.section("=",-1,-1);
93 
94         return temp.toLong();
95 
96     } else{
97         qDebug() << "ParserPls: pls file is not a playlist! \n";
98         return 0;
99     }
100 
101 }
102 
103 
getFilePath(QTextStream * stream,const QString & basePath)104 QString ParserPls::getFilePath(QTextStream *stream, const QString& basePath) {
105     QString textline = stream->readLine();
106     while (!textline.isEmpty()) {
107         if (textline.isNull()) {
108             break;
109         }
110 
111         if(textline.contains("File")) {
112             int iPos = textline.indexOf("=", 0);
113             ++iPos;
114 
115             QString filename = textline.right(textline.length() - iPos);
116             auto trackFile = playlistEntryToTrackFile(filename, basePath);
117             if (trackFile.checkFileExists()) {
118                 return trackFile.location();
119             }
120             // We couldn't match this to a real file so ignore it
121             qWarning() << trackFile << "not found";
122         }
123         textline = stream->readLine();
124     }
125 
126     // Signal we reached the end
127     return QString();
128 }
129 
writePLSFile(const QString & file_str,const QList<QString> & items,bool useRelativePath)130 bool ParserPls::writePLSFile(const QString &file_str, const QList<QString> &items, bool useRelativePath)
131 {
132     QFile file(file_str);
133     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
134         QMessageBox::warning(nullptr,
135                 tr("Playlist Export Failed"),
136                 tr("Could not create file") + " " + file_str);
137         return false;
138     }
139     //Base folder of file
140     QString base = file_str.section('/', 0, -2);
141     QDir base_dir(base);
142 
143     QTextStream out(&file);
144     out << "[playlist]\n";
145     out << "NumberOfEntries=" << items.size() << "\n";
146     for (int i = 0; i < items.size(); ++i) {
147         //Write relative path if possible
148         if (useRelativePath) {
149             //QDir::relativePath() will return the absolutePath if it cannot compute the
150             //relative Path
151             out << "File" << i << "=" << base_dir.relativeFilePath(items.at(i)) << "\n";
152         } else {
153             out << "File" << i << "=" << items.at(i) << "\n";
154         }
155     }
156 
157     return true;
158 }
159