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

KDE's Doxygen guidelines are available online.