1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  *
6  * Strawberry is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Strawberry is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include <algorithm>
22 
23 #include <QObject>
24 #include <QIODevice>
25 #include <QDir>
26 #include <QFile>
27 #include <QFileInfo>
28 #include <QByteArray>
29 #include <QString>
30 #include <QStringBuilder>
31 #include <QStringList>
32 #include <QtDebug>
33 
34 #include "asxiniparser.h"
35 #include "asxparser.h"
36 #include "core/logging.h"
37 #include "cueparser.h"
38 #include "m3uparser.h"
39 #include "playlistparser.h"
40 #include "playlistparsers/parserbase.h"
41 #include "plsparser.h"
42 #include "wplparser.h"
43 #include "xspfparser.h"
44 
45 const int PlaylistParser::kMagicSize = 512;
46 
PlaylistParser(CollectionBackendInterface * collection,QObject * parent)47 PlaylistParser::PlaylistParser(CollectionBackendInterface *collection, QObject *parent) : QObject(parent) {
48 
49   default_parser_ = new XSPFParser(collection, this);
50   parsers_ << new M3UParser(collection, this);
51   parsers_ << default_parser_;
52   parsers_ << new PLSParser(collection, this);
53   parsers_ << new ASXParser(collection, this);
54   parsers_ << new AsxIniParser(collection, this);
55   parsers_ << new CueParser(collection, this);
56   parsers_ << new WplParser(collection, this);
57 
58 }
59 
file_extensions() const60 QStringList PlaylistParser::file_extensions() const {
61 
62   QStringList ret;
63 
64   for (ParserBase *parser : parsers_) {
65     ret << parser->file_extensions();
66   }
67 
68   std::stable_sort(ret.begin(), ret.end());
69   return ret;
70 
71 }
72 
mime_types() const73 QStringList PlaylistParser::mime_types() const {
74 
75   QStringList ret;
76 
77   for (ParserBase *parser : parsers_) {
78     if (!parser->mime_type().isEmpty()) ret << parser->mime_type();
79   }
80 
81   std::stable_sort(ret.begin(), ret.end());
82   return ret;
83 
84 }
85 
filters() const86 QString PlaylistParser::filters() const {
87 
88   QStringList filters;
89   filters.reserve(parsers_.count() + 1);
90   QStringList all_extensions;
91   for (ParserBase *parser : parsers_) {
92     filters << FilterForParser(parser, &all_extensions);
93   }
94 
95   filters.prepend(tr("All playlists (%1)").arg(all_extensions.join(" ")));
96 
97   return filters.join(";;");
98 
99 }
100 
FilterForParser(const ParserBase * parser,QStringList * all_extensions)101 QString PlaylistParser::FilterForParser(const ParserBase *parser, QStringList *all_extensions) {
102 
103   const QStringList file_extensions = parser->file_extensions();
104   QStringList extensions;
105   extensions.reserve(file_extensions.count());
106   for (const QString &extension : file_extensions) {
107     extensions << "*." + extension;
108   }
109 
110   if (all_extensions) *all_extensions << extensions;
111 
112   return tr("%1 playlists (%2)").arg(parser->name(), extensions.join(" "));
113 
114 }
115 
default_extension() const116 QString PlaylistParser::default_extension() const {
117   QStringList file_extensions = default_parser_->file_extensions();
118   return file_extensions[0];
119 }
120 
default_filter() const121 QString PlaylistParser::default_filter() const {
122   return FilterForParser(default_parser_);
123 }
124 
ParserForExtension(const QString & suffix) const125 ParserBase *PlaylistParser::ParserForExtension(const QString &suffix) const {
126 
127   for (ParserBase *p : parsers_) {
128     if (p->file_extensions().contains(suffix)) return p;
129   }
130   return nullptr;
131 
132 }
133 
ParserForMimeType(const QString & mime_type) const134 ParserBase *PlaylistParser::ParserForMimeType(const QString &mime_type) const {
135 
136   for (ParserBase *p : parsers_) {
137     if (!p->mime_type().isEmpty() && (QString::compare(p->mime_type(), mime_type, Qt::CaseInsensitive) == 0)) {
138       return p;
139     }
140   }
141   return nullptr;
142 
143 }
144 
ParserForMagic(const QByteArray & data,const QString & mime_type) const145 ParserBase *PlaylistParser::ParserForMagic(const QByteArray &data, const QString &mime_type) const {
146 
147   for (ParserBase *p : parsers_) {
148     if ((!mime_type.isEmpty() && mime_type == p->mime_type()) || p->TryMagic(data)) {
149       return p;
150     }
151   }
152   return nullptr;
153 
154 }
155 
LoadFromFile(const QString & filename) const156 SongList PlaylistParser::LoadFromFile(const QString &filename) const {
157 
158   QFileInfo info(filename);
159 
160   // Find a parser that supports this file extension
161   ParserBase *parser = ParserForExtension(info.suffix());
162   if (!parser) {
163     qLog(Warning) << "Unknown filetype:" << filename;
164     return SongList();
165   }
166 
167   // Open the file
168   QFile file(filename);
169   if (!file.open(QIODevice::ReadOnly)) return SongList();
170 
171   SongList ret = parser->Load(&file, filename, info.absolutePath());
172   file.close();
173 
174   return ret;
175 
176 }
177 
LoadFromDevice(QIODevice * device,const QString & path_hint,const QDir & dir_hint) const178 SongList PlaylistParser::LoadFromDevice(QIODevice *device, const QString &path_hint, const QDir &dir_hint) const {
179 
180   // Find a parser that supports this data
181   ParserBase *parser = ParserForMagic(device->peek(kMagicSize));
182   if (!parser) {
183     return SongList();
184   }
185 
186   return parser->Load(device, path_hint, dir_hint);
187 
188 }
189 
Save(const SongList & songs,const QString & filename,const Playlist::Path path_type) const190 void PlaylistParser::Save(const SongList &songs, const QString &filename, const Playlist::Path path_type) const {
191 
192   QFileInfo info(filename);
193 
194   // Find a parser that supports this file extension
195   ParserBase *parser = ParserForExtension(info.suffix());
196   if (!parser) {
197     qLog(Warning) << "Unknown filetype:" << filename;
198     return;
199   }
200 
201   // Open the file
202   QFile file(filename);
203   if (!file.open(QIODevice::WriteOnly)) return;
204 
205   parser->Save(songs, &file, info.absolutePath(), path_type);
206 
207   file.close();
208 
209 }
210