KCDDB

cdinfo.cpp
1 /*
2  SPDX-FileCopyrightText: 2002 Rik Hemsley (rikkus) <[email protected]>
3  SPDX-FileCopyrightText: 2002-2005 Benjamin Meyer <[email protected]>
4  SPDX-FileCopyrightText: 2002-2004 Nadeem Hasan <[email protected]>
5  SPDX-FileCopyrightText: 2006 Richard Lärkäng <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "cdinfo.h"
11 
12 #include "client.h"
13 #include "cddb.h"
14 #include "logging.h"
15 
16 #include <KStringHandler>
17 #include <QDebug>
18 
19 #include <QMap>
20 #include <QRegExp>
21 #include <QRegularExpression>
22 
23 namespace KCDDB
24 {
25  class InfoBasePrivate {
26  public:
27  /**
28  * Creates a line in the form NAME=VALUE, and splits it into several
29  * lines if the line gets longer than 256 chars
30  */
31  static QString
32  createLine(const QString& name, const QString& value)
33  {
34  Q_ASSERT(name.length() < 254);
35 
36  int maxLength = 256 - name.length() - 2;
37 
38  QString tmpValue = escape(value);
39 
40  QString lines;
41 
42  while (tmpValue.length() > maxLength)
43  {
44  lines += QString::fromLatin1("%1=%2\n").arg(name,tmpValue.left(maxLength));
45  tmpValue = tmpValue.mid(maxLength);
46  }
47 
48  lines += QString::fromLatin1("%1=%2\n").arg(name,tmpValue);
49 
50  return lines;
51  }
52 
53  /**
54  * escape's string for CDDB processing
55  */
56  static QString
57  escape( const QString &value )
58  {
59  QString s = value;
60  s.replace( QLatin1String( "\\" ), QLatin1String( "\\\\" ) );
61  s.replace( QLatin1String( "\n" ), QLatin1String( "\\n" ) );
62  s.replace( QLatin1String( "\t" ), QLatin1String( "\\t" ) );
63 
64  return s;
65  }
66 
67  /**
68  * fixes an escaped string that has been CDDB processed
69  */
70  static QString
71  unescape( const QString &value )
72  {
73  QString s = value;
74 
75  s.replace( QLatin1String( "\\n" ), QLatin1String( "\n" ) );
76  s.replace( QLatin1String( "\\t" ), QLatin1String( "\t" ) );
77  s.replace( QLatin1String( "\\\\" ), QLatin1String( "\\" ) );
78 
79  return s;
80  }
81 
82  QVariant
83  get(const QString& type)
84  {
85  return data[type.toUpper()];
86  }
87  QVariant
88  get(Type type)
89  {
90  switch(type){
91  case(Title):
92  return get(QLatin1String( "title" ));
93  case(Comment):
94  return get(QLatin1String( "comment" ));
95  case(Artist):
96  return get(QLatin1String( "artist" ));
97  case(Genre):
98  return get(QLatin1String( "genre" ));
99  case(Year):
100  return get(QLatin1String( "year" ));
101  case(Length):
102  return get(QLatin1String( "length" ));
103  case(Category):
104  return get(QLatin1String( "category" ));
105  }
106  return QVariant();
107  }
108 
109  void
110  set(const QString& type, const QVariant &d)
111  {
112  //qDebug() << "set: " << type << ", " << d.toString();
113  if(type.contains(QRegularExpression( QLatin1String( "^T.*_.*$" )) )){
114  qCDebug(LIBKCDDB) << "Error: custom cdinfo::set data can not start with T and contain a _";
115  return;
116  }
117  if(type.toUpper() == QLatin1String( "DTITLE" )){
118  qCDebug(LIBKCDDB) << "Error: type: DTITLE is reserved and can not be set.";
119  return;
120  }
121 
122  data[type.toUpper()] = d;
123  }
124  void
125  set(Type type, const QVariant &d)
126  {
127  switch(type)
128  {
129  case(Title):
130  set(QLatin1String( "title" ), d);
131  return;
132  case(Comment):
133  set(QLatin1String( "comment" ), d);
134  return;
135  case(Artist):
136  set(QLatin1String( "artist" ), d);
137  return;
138  case(Genre):
139  set(QLatin1String( "genre" ), d);
140  return;
141  case(Year):
142  set(QLatin1String( "year" ), d);
143  return;
144  case(Length):
145  set(QLatin1String( "length" ), d);
146  return;
147  case(Category):
148  set(QLatin1String( "category" ), d);
149  return;
150  }
151 
152  Q_ASSERT(false);
153  }
154 
155  // Appends text to data instead of overwriting it
156  void
157  append(const QString& type, const QString& text)
158  {
159  set(type, get(type).toString().append(text));
160  }
161  void
162  append(Type type, const QString& text)
163  {
164  set(type, get(type).toString().append(text));
165  }
166 
168  } ;
169 
170  class TrackInfoPrivate : public InfoBasePrivate {
171  };
172 
173  TrackInfo::TrackInfo()
174  {
175  d = new TrackInfoPrivate();
176  }
177 
178  TrackInfo::TrackInfo(const TrackInfo& clone)
179  : d(new TrackInfoPrivate)
180  {
181  d->data = clone.d->data;
182  }
183 
184  TrackInfo::~TrackInfo()
185  {
186  delete d;
187  }
188 
189  TrackInfo& TrackInfo::operator=(const TrackInfo& clone)
190  {
191  d->data = clone.d->data;
192  return *this;
193  }
194 
195  QVariant TrackInfo::get(Type type) const {
196  return d->get(type);
197  }
198 
199  QVariant TrackInfo::get(const QString &type) const {
200  return d->get(type);
201  }
202 
203  void TrackInfo::set(const QString &type, const QVariant &data){
204  d->set(type, data);
205  }
206 
207  void TrackInfo::set(Type type, const QVariant &data) {
208  d->set(type, data);
209  }
210 
212  d->data.clear();
213  }
214 
216  QString out;
217  bool ok;
218  int track = get(QLatin1String( "tracknumber" )).toInt(&ok);
219  if(!ok)
220  qCDebug(LIBKCDDB) << "Warning toString() on a track that doesn't have track number assigned.";
221  QMap<QString, QVariant>::const_iterator i = d->data.constBegin();
222  while (i != d->data.constEnd()) {
223  if(i.key() != QLatin1String( "COMMENT" ) && i.key() != QLatin1String( "TITLE" ) && i.key() != QLatin1String( "ARTIST" ) && i.key() != QLatin1String( "TRACKNUMBER" )) {
224  out += d->createLine(QString::fromLatin1("T%1_%2").arg(i.key()).arg(track),i.value().toString());
225  }
226  ++i;
227  }
228  return out;
229  }
230 
231  bool TrackInfo::operator==( const TrackInfo& other ) const
232  {
233  return d->data == other.d->data;
234  }
235 
236  bool TrackInfo::operator!=( const TrackInfo& other ) const
237  {
238  return d->data != other.d->data;
239  }
240 
241  class CDInfoPrivate : public InfoBasePrivate {
242  public:
243  TrackInfoList trackInfoList;
244  };
245 
246  CDInfo::CDInfo()
247  : d(new CDInfoPrivate())
248  {
249  set(QLatin1String( "revision" ), 0);
250  }
251 
252  CDInfo::CDInfo(const CDInfo& clone)
253  : d(new CDInfoPrivate())
254  {
255  d->data = clone.d->data;
256  d->trackInfoList = clone.d->trackInfoList;
257  }
258 
259  CDInfo::~CDInfo()
260  {
261  delete d;
262  }
263 
264  CDInfo& CDInfo::operator=(const CDInfo& clone)
265  {
266  d->trackInfoList = clone.d->trackInfoList;
267  d->data = clone.d->data;
268  return *this;
269  }
270 
271  bool
272  CDInfo::load(const QString & string)
273  {
274 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
275  return load(string.split(QLatin1Char( '\n' ),QString::SkipEmptyParts));
276 #else
277  return load(string.split(QLatin1Char( '\n' ),Qt::SkipEmptyParts));
278 #endif
279  }
280 
281  bool
282  CDInfo::load(const QStringList & lineList)
283  {
284  clear();
285 
286  // We'll append to this until we've seen all the lines, then parse it after.
287  QString dtitle;
288 
289  QStringList::ConstIterator it = lineList.begin();
290 
291  QRegExp rev(QLatin1String( "# Revision: (\\d+)" ));
292  const static QRegularExpression eol(QLatin1String( "[\r\n]" ));
293 
294  while ( it != lineList.end() )
295  {
296  QString line(*it);
297  line.remove(eol);
298  ++it;
299 
300  if (rev.indexIn(line) != -1)
301  {
302  set(QLatin1String( "revision" ), rev.cap(1).toUInt());
303  continue;
304  }
305 
306  QStringList tokenList = KStringHandler::perlSplit(QLatin1Char( '=' ), line, 2);
307 
308  if (2 != tokenList.count())
309  {
310  continue;
311  }
312 
313  QString key = tokenList[0].trimmed();
314  QString value = d->unescape ( tokenList[1] );
315 
316  if ( QLatin1String( "DTITLE" ) == key )
317  {
318  dtitle += value;
319  }
320  else if ( key.startsWith(QLatin1String( "TTITLE" )) )
321  {
322  uint trackNumber = key.mid(6).toUInt();
323 
324  TrackInfo& ti = track(trackNumber);
325  ti.set(Title, ti.get(Title).toString().append(value));
326  }
327 
328  else if ( QLatin1String( "EXTD" ) == key )
329  {
330  d->append(Comment, value);
331  }
332  else if ( QLatin1String( "DGENRE" ) == key )
333  {
334  d->append(Genre, value);
335  }
336  else if ( QLatin1String( "DYEAR" ) == key )
337  {
338  set(Year, value);
339  }
340  else if ( key.startsWith(QLatin1String( "EXTT" )) )
341  {
342  uint trackNumber = key.mid( 4 ).toUInt();
343 
344  checkTrack( trackNumber );
345 
346  QString extt = track(trackNumber).get(Comment).toString();
347  track(trackNumber).set(Comment, QVariant(extt + value));
348  }
349  else if ( key.startsWith(QLatin1String( "T" )) )
350  {
351  // Custom Track data
352  uint trackNumber = key.mid( key.indexOf(QLatin1Char( '_' ))+1 ).toUInt();
353  checkTrack( trackNumber );
354 
355  QRegularExpression data(QString::fromLatin1("^T.*_%1$").arg(trackNumber));
356  if ( key.contains( data ) )
357  {
358  QString k = key.mid(1, key.indexOf(QLatin1Char( '_' ))-1);
359  TrackInfo& ti = track(trackNumber);
360  ti.set( k, ti.get(k).toString().append(value) );
361  }
362  }
363  else
364  {
365  // Custom Disk data
366  d->append( key, value );
367  }
368  }
369 
370  int slashPos = dtitle.indexOf(QLatin1String( " / " ));
371 
372  if (-1 == slashPos)
373  {
374  // Use string for title _and_ artist.
375  set(Artist, dtitle);
376  set(Title, dtitle);
377  }
378  else
379  {
380  set(Artist, dtitle.left(slashPos).trimmed());
381  set(Title, dtitle.mid(slashPos + 3).trimmed());
382  }
383 
384  bool isSampler = true;
385  for (TrackInfoList::Iterator it = d->trackInfoList.begin(); it != d->trackInfoList.end(); ++it)
386  {
387  if (!(*it).get(Title).toString().contains(QLatin1String( " / " )))
388  {
389  isSampler = false;
390  }
391  }
392  for (TrackInfoList::Iterator it = d->trackInfoList.begin(); it != d->trackInfoList.end(); ++it)
393  {
394  if (isSampler)
395  {
396  int delimiter = (*it).get(Title).toString().indexOf(QLatin1String( " / " ));
397  (*it).set(Artist, (*it).get(Title).toString().left(delimiter));
398  (*it).set(Title, (*it).get(Title).toString().mid(delimiter + 3));
399  }
400  else
401  {
402  (*it).set(Artist, get(Artist));
403  }
404  }
405 
406  if ( get(Genre).toString().isEmpty() )
407  set(Genre, QLatin1String( "Unknown" ));
408 
409  qCDebug(LIBKCDDB) << "Loaded CDInfo for " << get(QLatin1String( "discid" )).toString();
410 
411  return true;
412  }
413 
414  QString
415  CDInfo::toString(bool submit) const
416  {
417  QString s;
418 
419  if (get(QLatin1String( "revision" )) != 0)
420  s += QLatin1String( "# Revision: " ) + get(QLatin1String( "revision" )).toString() + QLatin1Char( '\n' );
421 
422  // If we are submiting make it a fully compliant CDDB entry
423  if (submit)
424  {
425  s += QLatin1String( "#\n" );
426  s += QString::fromLatin1("# Submitted via: %1 %2\n").arg(CDDB::clientName(),
427  CDDB::clientVersion());
428  }
429 
430  s += d->createLine(QLatin1String( "DISCID" ), get(QLatin1String( "discid" )).toString() );
431  QString artist = get(Artist).toString();
432  s += d->createLine(QLatin1String( "DTITLE" ), artist + QLatin1String( " / " ) + get(Title).toString() );
433  int year = get(Year).toInt();
434  s += QLatin1String( "DYEAR=" ) + (0 == year ? QString() : QString::number(year)) + QLatin1Char( '\n' ); //krazy:exclude=nullstrassign for old broken gcc
435  if (get(Genre) == QLatin1String( "Unknown" ))
436  s += d->createLine(QLatin1String( "DGENRE" ), QString());
437  else
438  s += d->createLine(QLatin1String( "DGENRE" ),get(Genre).toString());
439 
440  bool isSampler = false;
441  for (int i = 0; i < d->trackInfoList.count(); ++i){
442  QString trackArtist = d->trackInfoList[i].get(Artist).toString();
443  if (!trackArtist.isEmpty() && trackArtist != artist)
444  {
445  isSampler = true;
446  break;
447  }
448  }
449 
450  for (int i = 0; i < d->trackInfoList.count(); ++i){
451  QString trackTitle = d->trackInfoList[i].get(Title).toString();
452  QString trackArtist = d->trackInfoList[i].get(Artist).toString();
453  if (isSampler)
454  {
455  if (trackArtist.isEmpty())
456  s += d->createLine(QString::fromLatin1("TTITLE%1").arg(i), QString::fromLatin1("%1 / %2").arg(artist).arg(trackTitle));
457  else
458  s += d->createLine(QString::fromLatin1("TTITLE%1").arg(i), QString::fromLatin1("%1 / %2").arg(trackArtist).arg(trackTitle));
459  }
460  else
461  {
462  s += d->createLine(QString::fromLatin1("TTITLE%1").arg(i), trackTitle);
463  }
464  }
465 
466  s += d->createLine(QLatin1String("EXTD"), get(Comment).toString());
467 
468  for (int i = 0; i < d->trackInfoList.count(); ++i)
469  s += d->createLine(QString::fromLatin1("EXTT%1").arg(i), d->trackInfoList[i].get(Comment).toString());
470 
471  if (submit)
472  {
473  s += d->createLine(QLatin1String( "PLAYORDER" ), QString());
474  return s;
475  }
476 
477  s += d->createLine(QLatin1String( "PLAYORDER" ), get(QLatin1String( "playorder" )).toString() );
478 
479  // Custom track data
480  for (int i = 0; i < d->trackInfoList.count(); ++i)
481  s += d->trackInfoList[i].toString();
482 
483  QStringList cddbKeywords;
484  cddbKeywords
485  << QLatin1String( "DISCID" )
486  << QLatin1String( "ARTIST" )
487  << QLatin1String( "TITLE" )
488  << QLatin1String( "COMMENT" )
489  << QLatin1String( "YEAR" )
490  << QLatin1String( "GENRE" )
491  << QLatin1String( "PLAYORDER" )
492  << QLatin1String( "CATEGORY" )
493  << QLatin1String( "REVISION" );
494 
495  // Custom disc data
496  QMap<QString, QVariant>::const_iterator i = d->data.constBegin();
497  while (i != d->data.constEnd()){
498  if (!cddbKeywords.contains(i.key()) && i.key() != QLatin1String( "SOURCE" ))
499  {
500  s+= d->createLine(i.key(), i.value().toString());
501  }
502  ++i;
503  }
504 
505  return s;
506  }
507 
508  QVariant CDInfo::get(Type type) const {
509  return d->get(type);
510  }
511 
512  QVariant CDInfo::get(const QString &type) const {
513  return d->get(type);
514  }
515 
516  void CDInfo::set(const QString &type, const QVariant &data){
517  d->set(type, data);
518  }
519 
520  void CDInfo::set(Type type, const QVariant &data) {
521  d->set(type, data);
522  }
523 
524 
525  void
526  CDInfo::checkTrack( int trackNumber )
527  {
528  while ( d->trackInfoList.count() <= trackNumber ){
529  int count = d->trackInfoList.count();
530  d->trackInfoList.append(TrackInfo());
531  d->trackInfoList[count].set(QLatin1String( "tracknumber" ), count);
532  }
533  }
534 
535  void
537  {
538  d->data.clear();
539  d->trackInfoList.clear();
540  }
541 
542  bool
544  {
545  QString discid = get(QLatin1String( "DISCID" )).toString();
546  if (discid.isEmpty())
547  return false;
548 
549  if (discid == QLatin1String( "0" ))
550  return false;
551 
552  return true;
553  }
554 
555  TrackInfo &
556  CDInfo::track( int trackNumber )
557  {
558  checkTrack( trackNumber );
559  return d->trackInfoList[trackNumber];
560  }
561 
562  TrackInfo
563  CDInfo::track( int trackNumber ) const
564  {
565  if (trackNumber < d->trackInfoList.count())
566  return d->trackInfoList[trackNumber];
567  else
568  {
569  qWarning() << "Couldn't find track " << trackNumber;
570  return TrackInfo();
571  }
572  }
573 
574  int
576  {
577  return d->trackInfoList.count();
578  }
579 
580  bool CDInfo::operator==( const CDInfo& other ) const
581  {
582  return( d->data == other.d->data &&
583  d->trackInfoList == other.d->trackInfoList );
584  }
585 
586  bool CDInfo::operator!=( const CDInfo& other ) const
587  {
588  return( d->data != other.d->data ||
589  d->trackInfoList != other.d->trackInfoList );
590  }
591 }
592 
593 // vim:tabstop=2:shiftwidth=2:expandtab:cinoptions=(s,U1,m1
Information about a sepecific track in a cd.
Definition: cdinfo.h:42
void clear()
Clear all information, setting this to invalid internal.
Definition: cdinfo.cpp:536
Artist
void clear()
internal
Definition: cdinfo.cpp:211
QString number(int n, int base)
TrackInfo & track(int trackNumber)
Returns track with nr trackNumber and adds it to the track list if it doesn't exist (first track is 0...
Definition: cdinfo.cpp:556
QString escape(const QString &plain)
int numberOfTracks() const
Returns number of tracks on CD.
Definition: cdinfo.cpp:575
Type type(const QSqlDatabase &db)
const T value(const Key &key, const T &defaultValue) const const
int count(const T &value) const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QVariant get(const QString &type) const
Get data for type that has been assigned to this track.
Definition: cdinfo.cpp:199
QString trimmed() const const
QVariant get(const QString &type) const
Get data for type that has been assigned to this disc.
Definition: cdinfo.cpp:512
void set(const QString &type, const QVariant &data)
Set any data from this track.
Definition: cdinfo.cpp:203
Comment
bool isValid() const
Definition: cdinfo.cpp:543
char * toString(const T &value)
SkipEmptyParts
int indexIn(const QString &str, int offset, QRegExp::CaretMode caretMode) const const
bool isEmpty() const const
QString toString(bool submit=false) const
Definition: cdinfo.cpp:415
int length() const const
Genre
int toInt(bool *ok) const const
void clear()
void set(const QString &type, const QVariant &data)
Set any data from this disc.
Definition: cdinfo.cpp:516
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
void checkTrack(int trackNumber)
Checks to make sure that trackNumber exists.
Definition: cdinfo.cpp:526
Information about a CD.
Definition: cdinfo.h:104
uint toUInt(bool *ok, int base) const const
const Key key(const T &value, const Key &defaultKey) const const
QString & replace(int position, int n, QChar after)
QString & remove(int position, int n)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
typedef ConstIterator
QString cap(int nth) const const
QString toString() const
Definition: cdinfo.cpp:215
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString left(int n) const const
QString fromLatin1(const char *str, int size)
QString name(StandardShortcut id)
KCOREADDONS_EXPORT QStringList perlSplit(const QChar &sep, const QString &s, int max=0)
QList::iterator begin()
bool load(const QString &string)
Load CDInfo from a string that is CDDB compatible.
Definition: cdinfo.cpp:272
Title
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QList::iterator end()
QString mid(int position, int n) const const
QString & append(QChar ch)
virtual QVariant get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName)
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Tue May 17 2022 03:46:21 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.