1 /*
2  * Copyright (C) 2000 Rik Hemsley (rikkus) <rik@kde.org>
3  * Copyright (C) 2000-2002 Michael Matz <matz@kde.org>
4  * Copyright (C) 2001 Carsten Duvenhorst <duvenhorst@m2.uni-hannover.de>
5  * Copyright (C) 2001 Adrian Schroeter <adrian@suse.de>
6  * Copyright (C) 2003 Richard Lärkäng <richard@goteborg.utfors.se>
7  * Copyright (C) 2003 Scott Wheeler <wheeler@kde.org>
8  * Copyright (C) 2004-2005 Benjamin Meyer <ben at meyerhome dot net>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
23  * USA.
24  */
25 
26 #include "audiocd.h"
27 #include <config-audiocd.h>
28 
29 extern "C" {
30 // cdda_interface.h in cdparanoia 10.2 has a member called 'private' which the
31 // C++ compiler doesn't like
32 // we will thus use a generated local copy which renames that member.
33 #include "cdda_interface.hpp"
34 #include <cdda_paranoia.h>
35 void paranoiaCallback(long, int);
36 
37 int Q_DECL_EXPORT kdemain(int argc, char **argv);
38 }
39 
40 #include "plugins/audiocdencoder.h"
41 
42 #include <sys/resource.h>
43 #include <sys/stat.h>
44 #include <unistd.h>
45 
46 #include <cstdlib>
47 #include <ctime>
48 
49 #include <QApplication>
50 #include <QFile>
51 #include <QFileInfo>
52 #include <QGlobalStatic>
53 #include <QHash>
54 #include <QRegExp>
55 #include <QUrlQuery>
56 
57 #include "audiocd_kio_debug.h"
58 
59 #include <KLocalizedString>
60 #include <KMacroExpander>
61 
62 // CDDB
63 #include <KCddb/Client>
64 #include <KCompactDisc>
65 
66 // Pseudo plugin class to embed metadata
67 class KIOPluginForMetaData : public QObject
68 {
69     Q_OBJECT
70     Q_PLUGIN_METADATA(IID "org.kde.kio.slave.audiocd" FILE "audiocd.json")
71 };
72 
73 using namespace KIO;
74 
75 #define CDDB_INFORMATION I18N_NOOP("CDDB Information")
76 
77 using namespace AudioCD;
78 
kdemain(int argc,char ** argv)79 extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv)
80 {
81     // QApplication uses libkcddb which needs a valid kapp pointer
82     // GUIenabled must be true as libkcddb sometimes wants to communicate
83     // with the user
84     qunsetenv("SESSION_MANAGER");
85     QApplication app(argc, argv);
86     app.setApplicationName(QStringLiteral("kio_audiocd"));
87     KLocalizedString::setApplicationDomain("kio_audiocd");
88 
89     if (argc != 4) {
90         fprintf(stderr, "Usage: kio_audiocd protocol pool app\n");
91         exit(-1);
92     }
93 
94     qCDebug(AUDIOCD_KIO_LOG) << "Starting " << getpid();
95 
96     AudioCDProtocol slave(argv[1], argv[2], argv[3]);
97     slave.dispatchLoop();
98 
99     qCDebug(AUDIOCD_KIO_LOG) << "Done";
100 
101     return 0;
102 }
103 
104 enum Which_dir {
105     Unknown = 0, // Error
106     Info, // CDDB info
107     Base, // The ioslave base directory showing all drives
108     Root, // The root directory, shows all these :)
109     FullCD, // Show a single file containing all of the data
110     EncoderDir, // The root directory created by an encoder
111     SubDir // A directory created from the Album name configuration
112 };
113 
114 class AudioCDProtocol::Private {
115 public:
Private()116     Private()
117         : s_info(i18n("Information"))
118         , s_fullCD(i18n("Full CD"))
119     {
120         clearURLargs();
121     }
122 
clearURLargs()123     void clearURLargs()
124     {
125         req_allTracks = false;
126         which_dir = Unknown;
127         req_track = -1;
128         cddbUserChoice = -1;
129     }
130 
tocsAreDifferent(struct cdrom_drive * drive)131     bool tocsAreDifferent(struct cdrom_drive *drive)
132     {
133         if (tracks != (uint)drive->tracks)
134             return true;
135         for (int i = 0; i < drive->tracks; ++i) {
136             if (disc_toc[i].dwStartSector != drive->disc_toc[i].dwStartSector || disc_toc[i].bFlags != drive->disc_toc[i].bFlags
137                 || disc_toc[i].bTrack != drive->disc_toc[i].bTrack)
138                 return true;
139         }
140         return false;
141     }
142 
setToc(struct cdrom_drive * drive)143     void setToc(struct cdrom_drive *drive)
144     {
145         for (int i = 0; i < drive->tracks; ++i) {
146             disc_toc[i].dwStartSector = drive->disc_toc[i].dwStartSector;
147             disc_toc[i].bFlags = drive->disc_toc[i].bFlags;
148             disc_toc[i].bTrack = drive->disc_toc[i].bTrack;
149         }
150     }
151 
152     // The type/which of request
153     bool req_allTracks;
154     Which_dir which_dir;
155     int req_track;
156     QString fname;
157     QString child_dir;
158     AudioCDEncoder *encoder_dir_type;
159 
160     // Misc settings
161     QString device; // URL settable
162     int paranoiaLevel; // URL settable
163     bool reportErrors;
164 
165     // Directory strings, never change after init
166     const QString s_info;
167     const QString s_fullCD;
168 
169     // Current CD
170     TOC disc_toc[MAXTRK];
171     unsigned tracks;
172     bool trackIsAudio[100];
173 
174     // CDDB items
175     KCDDB::Result cddbResult;
176     CDInfoList cddbList;
177     int cddbUserChoice; // URL settable
178     KCDDB::CDInfo cddbBestChoice;
179 
180     // Template for ..
181     QString fileNameTemplate; // URL settable
182     QString albumNameTemplate; // URL settable
183     QString fileLocationTemplate; // URL settable
184     QString rsearch;
185     QString rreplace;
186 
187     // Current strings for this CD and or cddb selection
188     QStringList templateTitles;
189     QString templateAlbumName;
190     QString templateFileLocation;
191 };
192 
193 int paranoia_read_limited_error;
194 
AudioCDProtocol(const QByteArray & protocol,const QByteArray & pool,const QByteArray & app)195 AudioCDProtocol::AudioCDProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app)
196     : SlaveBase(protocol, pool, app)
197 {
198     d = new Private;
199     // Add encoders
200     AudioCDEncoder::findAllPlugins(this, encoders);
201     if (encoders.isEmpty()) {
202         qCCritical(AUDIOCD_KIO_LOG) << "No encoders available";
203         return;
204     }
205 
206     encoderTypeCDA = encoderFromExtension(QStringLiteral(".cda"));
207     encoderTypeWAV = encoderFromExtension(QStringLiteral(".wav"));
208 }
209 
~AudioCDProtocol()210 AudioCDProtocol::~AudioCDProtocol()
211 {
212     qDeleteAll(encoders);
213     delete d;
214 }
215 
encoderFromExtension(const QString & extension)216 AudioCDEncoder *AudioCDProtocol::encoderFromExtension(const QString &extension)
217 {
218     AudioCDEncoder *encoder;
219     for (int i = encoders.size() - 1; i >= 0; --i) {
220         encoder = encoders.at(i);
221         if (QLatin1String(".") + QLatin1String(encoder->fileType()) == extension)
222             return encoder;
223     }
224 
225     qCWarning(AUDIOCD_KIO_LOG) << "No encoder available for format" << extension;
226     return nullptr;
227 }
228 
determineEncoder(const QString & filename)229 AudioCDEncoder *AudioCDProtocol::determineEncoder(const QString &filename)
230 {
231     int pos = filename.lastIndexOf(QLatin1Char('.'));
232     return encoderFromExtension(filename.mid(pos));
233 }
234 
setDeviceToCd(KCompactDisc * cd,struct cdrom_drive * drive)235 static void setDeviceToCd(KCompactDisc *cd, struct cdrom_drive *drive)
236 {
237 #if defined(HAVE_CDDA_IOCTL_DEVICE)
238     cd->setDevice(QLatin1String(drive->ioctl_device_name), 50, false);
239 #elif defined(__FreeBSD__) || defined(__DragonFly__)
240     // FreeBSD's cdparanoia as of January 5th 2006 has rather broken
241     // support for non-SCSI devices. Although it finds ATA cdroms just
242     // fine, there is no straightforward way to discover the device
243     // name associated with the device, which throws the rest of audiocd
244     // for a loop.
245     //
246     if (!(drive->dev) || (COOKED_IOCTL == drive->interface)) {
247         // For ATAPI devices, we have no real choice. Use the
248         // user selected value, even if there is none.
249         //
250         qCWarning(AUDIOCD_KIO_LOG) << "Found an ATAPI device, assuming it is the "
251                                       "one specified by the user.";
252         cd->setDevice(QString::fromLatin1(drive->cdda_device_name));
253     } else {
254         qCDebug(AUDIOCD_KIO_LOG) << "Found a SCSI or ATAPICAM device.";
255         if (strlen(drive->dev->device_path) > 0) {
256             cd->setDevice(QString::fromLatin1(drive->dev->device_path));
257         } else {
258             // But the device_path can be empty under some
259             // circumstances, so build a representation from
260             // the unit number and SCSI device name.
261             //
262             QString devname = QStringLiteral("/dev/%1%2").arg(QString::fromLatin1(drive->dev->given_dev_name)).arg(drive->dev->given_unit_number);
263             qCDebug(AUDIOCD_KIO_LOG) << "  Using derived name " << devname;
264             cd->setDevice(devname);
265         }
266     }
267 #else
268 #ifdef __GNUC__
269 #warning audiocd ioslave is not going to work for you
270 #endif
271 #endif
272 }
273 
274 // Initiate a request to access the CD drive.  If there is no valid drive
275 // specified or there is a problem, then error() must be (or have been)
276 // called before returning a null pointer.
initRequest(const QUrl & url)277 struct cdrom_drive *AudioCDProtocol::initRequest(const QUrl &url)
278 {
279     // Load our Settings.
280     loadSettings();
281     // Then URL parameters can overrule our settings.
282     parseURLArgs(url);
283 
284     struct cdrom_drive *drive = getDrive();
285     if (drive == nullptr) {
286         return nullptr;
287     }
288 
289     if (d->tocsAreDifferent(drive)) {
290         // Update our knowledge of the disc
291         KCompactDisc cd(KCompactDisc::Asynchronous);
292         setDeviceToCd(&cd, drive);
293         d->setToc(drive);
294         d->tracks = cd.tracks();
295         for (uint i = 0; i < cd.tracks(); i++)
296             d->trackIsAudio[i] = cd.isAudio(i + 1);
297 
298         KCDDB::Client c;
299         d->cddbResult = c.lookup(cd.discSignature());
300         if (d->cddbResult == Success) {
301             d->cddbList = c.lookupResponse();
302             // FIXME: not always the best choice, see bug 279485
303             // for a similar problem with Amarok
304             d->cddbBestChoice = d->cddbList.first();
305         }
306         generateTemplateTitles();
307     }
308 
309     // Determine what file or folder that is wanted.
310     QString path = url.path();
311     if (!path.isEmpty() && path[0] == QLatin1Char('/'))
312         path = path.mid(1);
313 
314     d->req_allTracks = false;
315 
316     // See which file and directory they want
317     QString remainingDirPath;
318     d->which_dir = Unknown;
319     if (path.isEmpty()) {
320         d->which_dir = Root;
321         d->encoder_dir_type = encoderTypeWAV;
322         remainingDirPath = d->templateFileLocation;
323         d->fname = QString();
324     } else {
325         for (int i = encoders.size() - 1; i >= 0; --i) {
326             AudioCDEncoder *encoder = encoders.at(i);
327             QString encoderFileLocation = encoder->type();
328             if (!d->templateFileLocation.isEmpty())
329                 encoderFileLocation = encoderFileLocation + QLatin1String("/") + d->templateFileLocation;
330             if (path == encoder->type()) {
331                 d->which_dir = EncoderDir;
332                 d->encoder_dir_type = encoder;
333                 remainingDirPath = encoderFileLocation.mid(path.length());
334                 d->fname = QString();
335                 break;
336             } else if (encoderFileLocation.startsWith(path)) {
337                 d->which_dir = SubDir;
338                 d->encoder_dir_type = encoder;
339                 remainingDirPath = encoderFileLocation.mid(path.length());
340                 d->fname = QString();
341                 break;
342             } else if (path.startsWith(encoderFileLocation)) {
343                 d->which_dir = SubDir;
344                 d->encoder_dir_type = encoder;
345                 remainingDirPath = QString();
346                 d->fname = path.mid(encoderFileLocation.length() + 1);
347                 break;
348             } else if (path.startsWith(encoder->type())) {
349                 d->which_dir = EncoderDir;
350                 d->encoder_dir_type = encoder;
351                 remainingDirPath = QString();
352                 d->fname = path.mid(encoder->type().length() + 1);
353             }
354         }
355         if (Unknown == d->which_dir) {
356             if (path.startsWith(d->s_info)) {
357                 d->which_dir = Info;
358                 d->fname = path.mid(d->s_info.length() + 1);
359             } else if (path.startsWith(d->s_fullCD)) {
360                 d->which_dir = FullCD;
361                 d->fname = path.mid(d->s_fullCD.length() + 1);
362                 d->req_allTracks = true;
363             } else if (d->templateFileLocation.startsWith(path)) {
364                 d->which_dir = SubDir;
365                 d->encoder_dir_type = encoderTypeWAV;
366                 remainingDirPath = d->templateFileLocation.mid(path.length());
367                 d->fname = QString();
368             } else if (path.startsWith(d->templateFileLocation)) {
369                 d->encoder_dir_type = encoderTypeWAV;
370                 remainingDirPath = QString();
371                 d->fname = path.mid(d->templateFileLocation.length() + 1);
372             } else {
373                 d->encoder_dir_type = encoderTypeWAV;
374                 remainingDirPath = QString();
375                 d->fname = path;
376             }
377         }
378     }
379     if (!remainingDirPath.isEmpty() && remainingDirPath[0] == QLatin1Char('/'))
380         remainingDirPath = remainingDirPath.mid(1);
381     d->child_dir = remainingDirPath.split(QStringLiteral("/")).first();
382 
383     // See if the url is a track
384     d->req_track = -1;
385     if (!d->fname.isEmpty()) {
386         QString name(d->fname);
387 
388         // Remove extension
389         int dot = name.lastIndexOf(QLatin1Char('.'));
390         if (dot >= 0)
391             name.truncate(dot);
392 
393         // See if it matches a cddb title
394         uint trackNumber;
395         for (trackNumber = 0; trackNumber < d->tracks; trackNumber++) {
396             if (d->templateTitles[trackNumber] == name)
397                 break;
398         }
399         if (trackNumber < d->tracks)
400             d->req_track = trackNumber;
401         else {
402             /* Not found in title list. Try hard to find a number in the
403                      string. */
404             int start = 0;
405             int end = 0;
406             // Find where the numbers start
407             while (start < name.length()) {
408                 if (name[start++].isDigit())
409                     break;
410             }
411             // Find where the numbers end
412             for (end = start; end < name.length(); ++end)
413                 if (!name[end].isDigit())
414                     break;
415             if (start < name.length()) {
416                 bool ok;
417                 // The external representation counts from 1 so subtract 1.
418                 d->req_track = name.mid(start - 1, end - start + 2).toInt(&ok) - 1;
419                 if (!ok)
420                     d->req_track = -1;
421             }
422         }
423     }
424     if (d->req_track >= (int)d->tracks)
425         d->req_track = -1;
426 
427     qCDebug(AUDIOCD_KIO_LOG) << "path=" << path << " file=" << d->fname << " req_track=" << d->req_track << " which_dir=" << d->which_dir
428                              << " full CD?=" << d->req_allTracks;
429     return drive;
430 }
431 
getSectorsForRequest(struct cdrom_drive * drive,long & firstSector,long & lastSector) const432 bool AudioCDProtocol::getSectorsForRequest(struct cdrom_drive *drive, long &firstSector, long &lastSector) const
433 {
434     if (d->req_allTracks) { // we rip all the tracks of the CD
435         firstSector = cdda_track_firstsector(drive, 1);
436         lastSector = cdda_track_lastsector(drive, cdda_tracks(drive));
437     } else { // we only rip the selected track
438         int trackNumber = d->req_track + 1;
439 
440         if (trackNumber <= 0 || trackNumber > cdda_tracks(drive))
441             return false;
442         firstSector = cdda_track_firstsector(drive, trackNumber);
443         lastSector = cdda_track_lastsector(drive, trackNumber);
444     }
445     return true;
446 }
447 
findInformationFileNumber(const QString & filename,uint max)448 static uint findInformationFileNumber(const QString &filename, uint max) {
449     if (filename == QStringLiteral("%1.txt").arg(i18n(CDDB_INFORMATION)))
450         return 1;
451 
452     for (uint i = 2; i <= max; ++i) {
453         if (filename == QStringLiteral("%1_%2.txt").arg(i18n(CDDB_INFORMATION)).arg(i)) {
454             return i;
455         }
456     }
457 
458     return max + 1;
459 }
460 
461 // See whether this is the root of the ioslave (listing of all
462 // available CD drives) or the root of a drive (listing of encoder
463 // subdirectories and track WAV files).
464 //
465 // This needs to be deduced from the URL without calling initRequest()
466 // or getDrive(), either of which may call error().  It is not an
467 // error if there is no drive to be accessed and none needs to be, but
468 // subsequently calling finished() will assert.
469 //
470 // So parse the URL only.  Either Base, Root or Unknown (meaning "anywhere
471 // else") will be returned.
whichFromUrl(const QUrl & url)472 static Which_dir whichFromUrl(const QUrl &url)
473 {
474     QUrlQuery query(url);
475     if (!query.hasQueryItem(QStringLiteral("device"))) { // see if "device" query present
476         return Base; // if not, must be slave base
477     }
478 
479     if (url.path() == QLatin1String("/")) { // see if the device root
480         return Root;
481     }
482 
483     return Unknown; // elsewhere within device
484 }
485 
486 // Check that the URL does not have a host specified, and return an error
487 // if it does.  Moved here because not all operations need to or should
488 // call initRequest().
checkNoHost(const QUrl & url)489 bool AudioCDProtocol::checkNoHost(const QUrl &url)
490 {
491     if (!url.host().isEmpty()) {
492         error(KIO::ERR_UNSUPPORTED_ACTION,
493               i18n("You cannot specify a host with this protocol. "
494                    "Please use the audiocd:/ format instead."));
495         return false;
496     }
497 
498     return true;
499 }
500 
501 // Escape any '/'es in what should be a file name.
escapePath(const QString & in)502 static QString escapePath(const QString &in)
503 {
504     QString result = in;
505     // FIXME: maybe could use QUrl::toPercentEncoding()
506     result.replace(QLatin1Char('/'), QLatin1String("%2F"));
507     return result;
508 }
509 
get(const QUrl & url)510 void AudioCDProtocol::get(const QUrl &url)
511 {
512     if (!checkNoHost(url)) {
513         return;
514     }
515 
516     struct cdrom_drive *drive = initRequest(url);
517     if (drive == nullptr) {
518         // Do not call error(), getDrive() will already have done that.
519         return;
520     }
521 
522     if (d->fname.contains(i18n(CDDB_INFORMATION))) {
523         const uint choice = findInformationFileNumber(d->fname, d->cddbList.count());
524         uint count = 1;
525         CDInfoList::iterator it;
526         bool found = false;
527         for (it = d->cddbList.begin(); it != d->cddbList.end(); ++it) {
528             if (count == choice) {
529                 // FIXME: should be "text/plain"?
530                 mimeType(QStringLiteral("text/html"));
531                 data((*it).toString().toUtf8());
532                 // send an empty QByteArray to signal end of data.
533                 data(QByteArray());
534                 finished();
535                 found = true;
536                 break;
537             }
538             count++;
539         }
540         if (!found && d->fname.contains(i18n(CDDB_INFORMATION) + QLatin1Char(':'))) {
541             // FIXME: should be "text/plain"?
542             mimeType(QStringLiteral("text/html"));
543             // data(QCString( d->fname.latin1() ));
544             // send an empty QByteArray to signal end of data.
545             data(QByteArray());
546             finished();
547             found = true;
548         }
549         if (!found)
550             error(KIO::ERR_DOES_NOT_EXIST, url.path());
551         cdda_close(drive);
552         return;
553     }
554 
555     long firstSector, lastSector;
556     if (!getSectorsForRequest(drive, firstSector, lastSector)) {
557         error(KIO::ERR_DOES_NOT_EXIST, url.path());
558         cdda_close(drive);
559         return;
560     }
561 
562     AudioCDEncoder *encoder = determineEncoder(d->fname);
563     if (!encoder) {
564         error(KIO::ERR_DOES_NOT_EXIST, url.path());
565         cdda_close(drive);
566         return;
567     }
568 
569     KCDDB::CDInfo info;
570     if (d->cddbResult == KCDDB::Success) {
571         info = d->cddbBestChoice;
572 
573         int track = d->req_track + 1;
574 
575         // hack
576         // do we rip the whole CD?
577         if (d->req_allTracks) {
578             track = 1;
579             // YES => the title of the file is the title of the CD
580             info.track(track - 1).set(Title, info.get(Title));
581         }
582         encoder->fillSongInfo(info, track, QString());
583     }
584     long totalByteCount = CD_FRAMESIZE_RAW * (lastSector - firstSector + 1);
585     long time_secs = (8 * totalByteCount) / (44100 * 2 * 16);
586 
587     unsigned long size = encoder->size(time_secs);
588     totalSize(size);
589     mimeType(QLatin1String(encoder->mimeType()));
590 
591     // Read data (track/disk) from the cd
592     paranoiaRead(drive, firstSector, lastSector, encoder, url.fileName(), size);
593 
594     // send an empty QByteArray to signal end of data.
595     data(QByteArray());
596 
597     cdda_close(drive);
598 
599     finished();
600 }
601 
app_dir(UDSEntry & e,const QString & n,size_t s)602 static void app_dir(UDSEntry &e, const QString &n, size_t s)
603 {
604     const mode_t _umask = ::umask(0);
605     ::umask(_umask);
606 
607     e.clear();
608     e.reserve(5);
609     e.fastInsert(KIO::UDSEntry::UDS_NAME, QFile::decodeName(n.toLocal8Bit()));
610     e.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
611     e.fastInsert(KIO::UDSEntry::UDS_ACCESS, (0555 & ~_umask));
612     e.fastInsert(KIO::UDSEntry::UDS_SIZE, s);
613     e.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory"));
614 }
615 
app_file(UDSEntry & e,const QString & n,size_t s,const QString & mimetype=QString ())616 static void app_file(UDSEntry &e, const QString &n, size_t s, const QString &mimetype = QString())
617 {
618     const mode_t _umask = ::umask(0);
619     ::umask(_umask);
620 
621     e.clear();
622     e.reserve(6);
623     e.fastInsert(KIO::UDSEntry::UDS_NAME, QFile::decodeName(n.toLocal8Bit()));
624     e.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
625     // Use current date and time to avoid confusions. See
626     // https://bugs.kde.org/show_bug.cgi?id=400114
627     e.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, QDateTime::currentDateTime().toSecsSinceEpoch());
628     e.fastInsert(KIO::UDSEntry::UDS_ACCESS, (0444 & ~_umask));
629     e.fastInsert(KIO::UDSEntry::UDS_SIZE, s);
630     if (!mimetype.isEmpty())
631         e.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, mimetype);
632 }
633 
stat(const QUrl & url)634 void AudioCDProtocol::stat(const QUrl &url)
635 {
636     if (!checkNoHost(url)) {
637         return;
638     }
639 
640     if (whichFromUrl(url) == Base) {
641         // This is the top level directory with CDROM devices only.
642         // No drive access is required.  url.fileName() is empty here.
643 
644         UDSEntry entry;
645         // One subdirectory for each drive
646         const QStringList &deviceNames = KCompactDisc::cdromDeviceNames();
647         app_dir(entry, escapePath(QStringLiteral("/")), deviceNames.count());
648         statEntry(entry);
649         finished();
650         return;
651     }
652 
653     struct cdrom_drive *drive = initRequest(url);
654     if (drive == nullptr) {
655         // Do not call error(), getDrive() will already have done that.
656         return;
657     }
658 
659     if (d->which_dir == Info) {
660         // This is the info dir or one of the info files
661         if (d->fname.isEmpty()) {
662             // The info dir
663             UDSEntry entry;
664             app_dir(entry, escapePath(url.fileName()), d->cddbList.count());
665             statEntry(entry);
666             finished();
667             return;
668         } else if (d->fname.contains(i18n(CDDB_INFORMATION))) {
669             // choice is 1-indexed so we need <= and -1 when accessing d->cddbList
670             const uint choice = findInformationFileNumber(d->fname, d->cddbList.count());
671             if (choice <= (uint)d->cddbList.count()) {
672                 // It's a valid info file
673                 UDSEntry entry;
674                 app_file(entry, escapePath(url.fileName()), d->cddbList.at(choice - 1).toString().toLatin1().size(), QStringLiteral("text/plain"));
675                 statEntry(entry);
676                 finished();
677                 return;
678             }
679         }
680     }
681 
682     const bool isFile = !d->fname.isEmpty();
683 
684     // the track number. 0 if ripping
685     // the whole CD.
686     const uint trackNumber = d->req_track + 1;
687 
688     if (!d->req_allTracks) { // we only want to rip one track.
689         // does this track exist?
690         if (isFile && (trackNumber < 1 || trackNumber > d->tracks)) {
691             error(KIO::ERR_DOES_NOT_EXIST, url.path());
692             cdda_close(drive);
693             return;
694         }
695     }
696 
697     UDSEntry entry;
698     if (!isFile) {
699         app_dir(entry, escapePath(url.fileName()), cdda_tracks(drive));
700     } else {
701         AudioCDEncoder *encoder = determineEncoder(d->fname);
702         long firstSector = 0, lastSector = 0;
703         getSectorsForRequest(drive, firstSector, lastSector);
704         app_file(entry, escapePath(url.fileName()), fileSize(firstSector, lastSector, encoder));
705     }
706 
707     statEntry(entry);
708     cdda_close(drive);
709     finished();
710 }
711 
listDir(const QUrl & url)712 void AudioCDProtocol::listDir(const QUrl &url)
713 {
714     if (!checkNoHost(url)) {
715         return;
716     }
717 
718     UDSEntry entry;
719     app_dir(entry, QStringLiteral("."), 0);
720     listEntry(entry);
721 
722     if (whichFromUrl(url) == Base) {
723         // The top level directory with CDROM devices only.
724 
725         const QStringList deviceNames = KCompactDisc::cdromDeviceNames();
726         for (const QString &deviceName : deviceNames) {
727             const QString &device = KCompactDisc::urlToDevice(KCompactDisc::cdromDeviceUrl(deviceName));
728             QUrl targetUrl = url;
729             QUrlQuery targetQuery;
730             targetQuery.addQueryItem(QStringLiteral("device"), device);
731             targetUrl.setQuery(targetQuery);
732 
733             app_dir(entry, escapePath(device), 2 + encoders.count());
734             entry.fastInsert(KIO::UDSEntry::UDS_TARGET_URL, targetUrl.url());
735             entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, deviceName);
736             listEntry(entry);
737         }
738         totalSize(entry.count());
739         finished();
740         return;
741     }
742 
743     struct cdrom_drive *drive = initRequest(url);
744 
745     // Some error checking before proceeding
746     if (drive == nullptr) {
747         // Do not call error(), getDrive() will already have done that.
748         return;
749     }
750 
751     if (d->which_dir == Unknown) {
752         error(KIO::ERR_DOES_NOT_EXIST, url.path());
753         cdda_close(drive);
754         return;
755     }
756 
757     if (!d->fname.isEmpty()) {
758         error(KIO::ERR_IS_FILE, url.path());
759         cdda_close(drive);
760         return;
761     }
762 
763     // Generate templated names every time
764     // because the template might have changed.
765     generateTemplateTitles();
766 
767     if (d->which_dir == Info) {
768         CDInfoList::iterator it;
769         uint count = 1;
770         for (it = d->cddbList.begin(); it != d->cddbList.end(); ++it) {
771             (*it).toString();
772             if (count == 1)
773                 app_file(entry, QString::fromLatin1("%1.txt").arg(i18n(CDDB_INFORMATION)), ((*it).toString().length()) + 1);
774             else
775                 app_file(entry, QString::fromLatin1("%1_%2.txt").arg(i18n(CDDB_INFORMATION)).arg(count), ((*it).toString().length()) + 1);
776             count++;
777             listEntry(entry);
778         }
779         // Error
780         if (count == 1) {
781             app_file(entry, QString::fromLatin1("%1: %2.txt").arg(i18n(CDDB_INFORMATION)).arg(KCDDB::resultToString(d->cddbResult)), 0);
782             count++;
783             listEntry(entry);
784         }
785     }
786 
787     if (d->which_dir == Root) {
788         // List virtual directories.
789         app_dir(entry, d->s_fullCD, encoders.count());
790         listEntry(entry);
791 
792         // Either >0 cddb results or cddb error file
793         app_dir(entry, d->s_info, d->cddbList.count());
794         listEntry(entry);
795 
796         // List the encoders
797         AudioCDEncoder *encoder;
798         for (int i = encoders.size() - 1; i >= 0; --i) {
799             encoder = encoders.at(i);
800             // Skip the directory that is in the root (you can still go in it, just
801             // don't show it)
802             if (encoder == encoderTypeWAV)
803                 continue;
804             app_dir(entry, encoder->type(), d->tracks);
805             listEntry(entry);
806         }
807     }
808 
809     // Now fill in the tracks for the current directory
810     if (d->which_dir == FullCD) {
811         AudioCDEncoder *encoder;
812         for (int i = encoders.size() - 1; i >= 0; --i) {
813             encoder = encoders.at(i);
814             if (d->cddbResult != KCDDB::Success)
815                 addEntry(d->s_fullCD, encoder, drive, -1);
816             else
817                 addEntry(d->templateAlbumName, encoder, drive, -1);
818         }
819     }
820 
821     if (d->which_dir == SubDir || d->which_dir == Root || d->which_dir == EncoderDir) {
822         if (d->child_dir.isEmpty()) {
823             // we are at the end of the hierarchy, list the tracks
824             for (uint trackNumber = 1; trackNumber <= d->tracks; trackNumber++) {
825                 // Skip data tracks
826                 if (!d->trackIsAudio[trackNumber - 1])
827                     continue;
828 
829                 switch (d->which_dir) {
830                 case EncoderDir:
831                 case SubDir:
832                 case Root:
833                     addEntry(d->templateTitles[trackNumber - 1], d->encoder_dir_type, drive, trackNumber);
834                     break;
835                 case Info:
836                 case Unknown:
837                 default:
838                     error(KIO::ERR_INTERNAL, url.path());
839                     cdda_close(drive);
840                     return;
841                 }
842             }
843         } else {
844             app_dir(entry, d->child_dir, 1);
845             listEntry(entry);
846         }
847     }
848 
849     totalSize(entry.count());
850     cdda_close(drive);
851     finished();
852 }
853 
addEntry(const QString & trackTitle,AudioCDEncoder * encoder,struct cdrom_drive * drive,int trackNo)854 void AudioCDProtocol::addEntry(const QString &trackTitle, AudioCDEncoder *encoder, struct cdrom_drive *drive, int trackNo)
855 {
856     if (!encoder || !drive)
857         return;
858 
859     long theFileSize = 0;
860     if (trackNo == -1) { // adding entry for the full CD
861         theFileSize = fileSize(cdda_track_firstsector(drive, 1), cdda_track_lastsector(drive, cdda_tracks(drive)), encoder);
862     } else { // adding one regular track
863         long firstSector = cdda_track_firstsector(drive, trackNo);
864         long lastSector = cdda_track_lastsector(drive, trackNo);
865         theFileSize = fileSize(firstSector, lastSector, encoder);
866     }
867     UDSEntry entry;
868     app_file(entry, trackTitle + QLatin1String(".") + QLatin1String(encoder->fileType()), theFileSize, QLatin1String(encoder->mimeType()));
869     listEntry(entry);
870 }
871 
fileSize(long firstSector,long lastSector,AudioCDEncoder * encoder)872 long AudioCDProtocol::fileSize(long firstSector, long lastSector, AudioCDEncoder *encoder)
873 {
874     if (!encoder)
875         return 0;
876 
877     long filesize = CD_FRAMESIZE_RAW * (lastSector - firstSector + 1);
878     long length_seconds = (filesize) / 176400;
879 
880     return encoder->size(length_seconds);
881 }
882 
883 // If a null pointer (meaning no or an invalid drive) is to be returned,
884 // this must call error() first and the caller must not subsequently
885 // call error() or finished().
getDrive()886 struct cdrom_drive *AudioCDProtocol::getDrive()
887 {
888     const QByteArray device(QFile::encodeName(d->device));
889     if (device.isEmpty()) {
890         error(KIO::ERR_MALFORMED_URL, i18nc("The URL does not include a device name", "Missing device specification"));
891         return nullptr;
892     }
893 
894     struct cdrom_drive *drive = cdda_identify(device.data(), CDDA_MESSAGE_FORGETIT, nullptr);
895     if (drive == nullptr) {
896         qCDebug(AUDIOCD_KIO_LOG) << "Can't find an audio CD on: \"" << d->device << "\"";
897 
898         const QFileInfo fi(d->device);
899         if (!fi.isReadable())
900             error(KIO::ERR_SLAVE_DEFINED,
901                   i18n("Device does not have read permissions for this account.  "
902                        "Check the read permissions on the device."));
903         else if (!fi.isWritable())
904             error(KIO::ERR_SLAVE_DEFINED,
905                   i18n("Device does not have write permissions for this account.  "
906                        "Check the write permissions on the device."));
907         else if (!fi.exists())
908             error(KIO::ERR_DOES_NOT_EXIST, d->device);
909         else
910             error(KIO::ERR_SLAVE_DEFINED,
911                   i18n("Unknown error.  If you have a cd in the drive try running "
912                        "cdparanoia -vsQ as yourself (not root). Do you see a track "
913                        "list? If not, make sure you have permission to access the CD "
914                        "device. If you are using SCSI emulation (possible if you "
915                        "have an IDE CD writer) then make sure you check that you "
916                        "have read and write permissions on the generic SCSI device, "
917                        "which is probably /dev/sg0, /dev/sg1, etc.. If it still does "
918                        "not work, try typing audiocd:/?device=/dev/sg0 (or similar) "
919                        "to tell kio_audiocd which device your CD-ROM is."));
920         return nullptr;
921     }
922 
923     if (cdda_open(drive) != 0) {
924         qCDebug(AUDIOCD_KIO_LOG) << "cdda_open failed";
925         error(KIO::ERR_CANNOT_OPEN_FOR_READING, d->device);
926         cdda_close(drive);
927         return nullptr;
928     }
929 
930     return drive;
931 }
932 
paranoiaRead(struct cdrom_drive * drive,long firstSector,long lastSector,AudioCDEncoder * encoder,const QString & fileName,unsigned long size)933 void AudioCDProtocol::paranoiaRead(struct cdrom_drive *drive,
934                                    long firstSector,
935                                    long lastSector,
936                                    AudioCDEncoder *encoder,
937                                    const QString &fileName,
938                                    unsigned long size)
939 {
940     if (!encoder || !drive)
941         return;
942 
943     cdrom_paranoia *paranoia = paranoia_init(drive);
944     if (nullptr == paranoia) {
945         qCDebug(AUDIOCD_KIO_LOG) << "paranoia_init failed";
946         return;
947     }
948 
949     int paranoiaLevel = PARANOIA_MODE_FULL ^ PARANOIA_MODE_NEVERSKIP;
950     switch (d->paranoiaLevel) {
951     case 0:
952         paranoiaLevel = PARANOIA_MODE_DISABLE;
953         break;
954 
955     case 1:
956         paranoiaLevel |= PARANOIA_MODE_OVERLAP;
957         paranoiaLevel &= ~PARANOIA_MODE_VERIFY;
958         break;
959 
960     case 2:
961         paranoiaLevel |= PARANOIA_MODE_NEVERSKIP;
962     default:
963         break;
964     }
965 
966     paranoia_modeset(paranoia, paranoiaLevel);
967 
968     cdda_verbose_set(drive, CDDA_MESSAGE_PRINTIT, CDDA_MESSAGE_FORGETIT);
969 
970     paranoia_seek(paranoia, firstSector, SEEK_SET);
971 
972     long currentSector(firstSector);
973 
974     unsigned long processed = encoder->readInit(CD_FRAMESIZE_RAW * (lastSector - firstSector + 1));
975     // TODO test for errors (processed<0)?
976     processedSize(processed);
977     bool ok = true;
978 
979     unsigned long lastSize = size;
980     unsigned long diff = 0;
981 
982     paranoia_read_limited_error = 0;
983     int warned = 0;
984     while (currentSector <= lastSector) {
985         // TODO make the 5 configurable? The default in the lib is 20 fyi
986         qint16 *buf = paranoia_read_limited(paranoia, paranoiaCallback, 5);
987         if (warned == 0 && paranoia_read_limited_error >= 5 && d->reportErrors) {
988             warning(
989                 i18n("AudioCD: Disk damage detected on this track, risk of data "
990                      "corruption."));
991             warned = 1;
992         }
993         if (nullptr == buf) {
994             qCDebug(AUDIOCD_KIO_LOG) << "Unrecoverable error in paranoia_read";
995             ok = false;
996             error(ERR_SLAVE_DEFINED, i18n("Error reading audio data for %1 from the CD", fileName));
997             break;
998         }
999 
1000         ++currentSector;
1001 
1002         int encoderProcessed = encoder->read(buf, CD_FRAMESAMPLES);
1003         if (encoderProcessed == -1) {
1004             qCDebug(AUDIOCD_KIO_LOG) << "Encoder processing error, stopping.";
1005             ok = false;
1006             QString errMsg = i18n("Could not read %1: encoding failed", fileName);
1007             const QString details = encoder->lastErrorMessage();
1008             if (!details.isEmpty())
1009                 errMsg += QLatin1Char('\n') + details;
1010             error(ERR_SLAVE_DEFINED, errMsg);
1011             break;
1012         }
1013         processed += encoderProcessed;
1014 
1015         /**
1016          * Because compression size is so 'unknown' use some guesswork
1017          *
1018          * 1) First assume that the reported size is correct and
1019          * only change the totalSize if the guess it outside a range of %5.
1020          * 2) Only increase in size unless the decrease is %5 of last estimate.
1021          * This prevents continues small changes which is just annoying.
1022          */
1023         unsigned long end = lastSector - firstSector;
1024         unsigned long cur = currentSector - firstSector;
1025         unsigned long estSize = (processed / cur) * end;
1026 
1027         // If our guess is within 5% of reported
1028         // size then use the reported size.
1029         unsigned long guess = (long)((100 / (float)size) * estSize);
1030         if ((guess > 97 && guess < 103) || estSize == 0) {
1031             if (processed > lastSize) {
1032                 totalSize(processed + 1);
1033                 lastSize = processed;
1034             }
1035         } else {
1036             float percentDone = ((float)cur / (float)end);
1037             // Calculate estimated amount that will be wrong
1038             diff = estSize - lastSize;
1039             diff = (diff * (unsigned long)((100 / (float)end) * (end - cur))) / 2;
1040             // Need 1% of data calculated as initial buffer, use %2 to be safe
1041             if (percentDone < .02) {
1042                 // qCDebug(AUDIOCD_KIO_LOG) << "val: " << (float)cur/(float)end << "
1043                 // diff: " << diff;
1044                 diff = 0;
1045             }
1046 
1047             // We are growing larger, increase total.
1048             if (lastSize < estSize) {
1049                 // qCDebug(AUDIOCD_KIO_LOG) << "lastGuess: " << lastSize << ", guess: "
1050                 // << estSize << ", diff: " << diff;
1051                 totalSize(estSize + diff);
1052                 lastSize = estSize + diff;
1053             } else {
1054                 int margin = (int)((percentDone)*75);
1055                 // Don't bother really trying until almost half way done.
1056                 if (percentDone <= .40)
1057                     margin = 7;
1058                 unsigned long low = lastSize - lastSize / margin;
1059                 if (estSize < low) {
1060                     // qCDebug(AUDIOCD_KIO_LOG) << "low: " << low << ", estSize: " <<
1061                     // estSize << ", num: " << margin;
1062                     totalSize(estSize);
1063                     lastSize = estSize;
1064                 }
1065             }
1066         }
1067         /**
1068          * End estimation.
1069          */
1070         // qCDebug(AUDIOCD_KIO_LOG) << "processed: " << processed << ", totalSize: "
1071         // << estSize;
1072         processedSize(processed);
1073     }
1074 
1075     if (processed > size)
1076         totalSize(processed);
1077 
1078     long encoderProcessed = encoder->readCleanup();
1079     if (encoderProcessed >= 0) {
1080         processed += encoderProcessed;
1081         if (processed > size)
1082             totalSize(processed);
1083         processedSize(processed);
1084     } else if (ok) // i.e. no error message already emitted
1085         error(ERR_SLAVE_DEFINED, i18n("Could not read %1: encoding failed", fileName));
1086 
1087     paranoia_free(paranoia);
1088     paranoia = nullptr;
1089 }
1090 
1091 /**
1092  * Read the settings from the URL
1093  * @see loadSettings()
1094  */
parseURLArgs(const QUrl & url)1095 void AudioCDProtocol::parseURLArgs(const QUrl &url)
1096 {
1097     d->clearURLargs();
1098 
1099     // FIXME: Use QUrlQuery for parsing
1100     QString query(QUrl::fromPercentEncoding(url.query().toLatin1()));
1101 
1102     if (query.isEmpty())
1103         return;
1104 
1105     const QStringList tokens(query.split(QLatin1Char('&'), Qt::SkipEmptyParts));
1106 
1107     for (QStringList::ConstIterator it(tokens.constBegin()); it != tokens.constEnd(); ++it) {
1108         const QString token(*it);
1109 
1110         int equalsPos = token.indexOf(QLatin1Char('='));
1111         if (-1 == equalsPos)
1112             continue;
1113 
1114         const QString attribute(token.left(equalsPos));
1115         const QString value(token.mid(equalsPos + 1));
1116 
1117         if (attribute == QLatin1String("device"))
1118             d->device = value;
1119         else if (attribute == QLatin1String("paranoia_level"))
1120             d->paranoiaLevel = value.toInt();
1121         else if (attribute == QLatin1String("fileNameTemplate"))
1122             d->fileNameTemplate = value;
1123         else if (attribute == QLatin1String("albumNameTemplate"))
1124             d->albumNameTemplate = value;
1125         else if (attribute == QLatin1String("fileLocationTemplate"))
1126             d->fileLocationTemplate = value;
1127         else if (attribute == QLatin1String("cddbChoice"))
1128             d->cddbUserChoice = value.toInt();
1129         else if (attribute == QLatin1String("niceLevel")) {
1130             int niceLevel = value.toInt();
1131             if (setpriority(PRIO_PROCESS, getpid(), niceLevel) != 0)
1132                 qCDebug(AUDIOCD_KIO_LOG) << "Setting nice level to (" << niceLevel << ") failed.";
1133         }
1134     }
1135 }
1136 
1137 /**
1138  * Read the settings set by the kcm modules
1139  * @see parseURLArgs()
1140  */
loadSettings()1141 void AudioCDProtocol::loadSettings()
1142 {
1143     const KConfig *config = new KConfig(QStringLiteral("kcmaudiocdrc"), KConfig::NoGlobals);
1144     const KConfigGroup groupCDDA(config, "CDDA");
1145 
1146     d->device = QString(); // clear device
1147     d->paranoiaLevel = 1; // enable paranoia error correction, but allow skipping
1148 
1149     if (groupCDDA.readEntry("disable_paranoia", false)) {
1150         d->paranoiaLevel = 0; // disable all paranoia error correction
1151     }
1152 
1153     if (groupCDDA.readEntry("never_skip", true)) {
1154         d->paranoiaLevel = 2;
1155         // never skip on errors of the medium, should be default for high quality
1156     }
1157 
1158     d->reportErrors = groupCDDA.readEntry("report_errors", false);
1159 
1160     if (groupCDDA.hasKey("niceLevel")) {
1161         int niceLevel = groupCDDA.readEntry("niceLevel", 0);
1162         if (setpriority(PRIO_PROCESS, getpid(), niceLevel) != 0)
1163             qCDebug(AUDIOCD_KIO_LOG) << "Setting nice level to (" << niceLevel << ") failed.";
1164     }
1165 
1166     // The default track filename template
1167     const KConfigGroup groupFileName(config, "FileName");
1168     d->fileNameTemplate = groupFileName.readEntry("file_name_template", "%{trackartist} - %{number} - %{title}");
1169     d->albumNameTemplate = groupFileName.readEntry("album_name_template", "%{albumartist} - %{albumtitle}");
1170     if (groupFileName.readEntry("show_file_location", false))
1171         d->fileLocationTemplate = groupFileName.readEntry("file_location_template", QString());
1172     else
1173         d->fileLocationTemplate = QString();
1174     d->rsearch = groupFileName.readEntry("regexp_search");
1175     d->rreplace = groupFileName.readEntry("regexp_replace");
1176     // if the regular expressions are enclosed in quotes. Remove them
1177     // otherwise it is not possible to search for a space " ", since an empty
1178     // (only spaces) value is not supported by KConfig, so the space has to be
1179     // quoted, but then here the regexp searches really for " " instead of just
1180     // the space. Alex
1181     QRegExp qoutedString(QLatin1String("^\".*\"$"));
1182     if (qoutedString.exactMatch(d->rsearch)) {
1183         d->rsearch = d->rsearch.mid(1, d->rsearch.length() - 2);
1184     }
1185     if (qoutedString.exactMatch(d->rreplace)) {
1186         d->rreplace = d->rreplace.mid(1, d->rreplace.length() - 2);
1187     }
1188 
1189     // Tell the encoders to load their settings
1190     AudioCDEncoder *encoder;
1191     for (int i = encoders.size() - 1; i >= 0; --i) {
1192         encoder = encoders.at(i);
1193         if (encoder->init())
1194             encoder->loadSettings();
1195         else
1196             encoders.removeAt(i);
1197     }
1198 
1199     delete config;
1200 }
1201 
1202 /**
1203  * Generates the track titles from the template using the cddb information.
1204  */
generateTemplateTitles()1205 void AudioCDProtocol::generateTemplateTitles()
1206 {
1207     d->templateTitles.clear();
1208     if (d->cddbResult != KCDDB::Success) {
1209         for (unsigned int i = 0; i < d->tracks; i++) {
1210             d->templateTitles.append(i18n("Track %1", QString::asprintf("%02d", i + 1)));
1211         }
1212         return;
1213     }
1214 
1215     KCDDB::CDInfo info = d->cddbBestChoice;
1216     if (d->cddbUserChoice >= 0 && ((d->cddbUserChoice) < d->cddbList.count()))
1217         info = d->cddbList[d->cddbUserChoice];
1218 
1219     // Then generate the templates
1220     d->templateTitles.clear();
1221     for (uint i = 0; i < d->tracks; i++) {
1222         QHash<QString, QString> macros;
1223         macros[QStringLiteral("albumartist")] = info.get(Artist).toString();
1224         macros[QStringLiteral("albumtitle")] = info.get(Title).toString();
1225         macros[QStringLiteral("title")] = info.track(i).get(Title).toString();
1226         macros[QStringLiteral("trackartist")] = info.track(i).get(Artist).toString();
1227         macros[QStringLiteral("number")] = QString::asprintf("%02d", i + 1);
1228         // macros["number"] = QString("%1").arg(i+1, 2, 10);
1229         macros[QStringLiteral("genre")] = info.get(Genre).toString();
1230         macros[QStringLiteral("year")] = info.get(Year).toString();
1231 
1232         QString title = escapePath(KMacroExpander::expandMacros(d->fileNameTemplate, macros, QLatin1Char('%')));
1233         title.replace(QRegExp(d->rsearch), d->rreplace);
1234         d->templateTitles.append(title);
1235     }
1236 
1237     QHash<QString, QString> macros;
1238     macros[QStringLiteral("albumartist")] = info.get(Artist).toString();
1239     macros[QStringLiteral("albumtitle")] = info.get(Title).toString();
1240     macros[QStringLiteral("genre")] = info.get(Genre).toString();
1241     macros[QStringLiteral("year")] = info.get(Year).toString();
1242     d->templateAlbumName = escapePath(KMacroExpander::expandMacros(d->albumNameTemplate, macros, QLatin1Char('%')));
1243     d->templateAlbumName.replace(QRegExp(d->rsearch), d->rreplace);
1244 
1245     d->templateFileLocation = KMacroExpander::expandMacros(d->fileLocationTemplate, macros, QLatin1Char('%'));
1246 }
1247 
1248 /**
1249  * Based upon the cdparanoia ripping application
1250  * Only output BAD stuff
1251  * The higher the paranoia_read_limited_error the worse the problem is
1252  * FYI: PARANOIA_CB_READ & PARANOIA_CB_VERIFY happen continuously when ripping
1253  */
paranoiaCallback(long,int function)1254 void paranoiaCallback(long, int function)
1255 {
1256     switch (function) {
1257     case PARANOIA_CB_VERIFY:
1258         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_VERIFY";
1259         break;
1260 
1261     case PARANOIA_CB_READ:
1262         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_READ";
1263         break;
1264 
1265     case PARANOIA_CB_FIXUP_EDGE:
1266         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_EDGE";
1267         paranoia_read_limited_error = 2;
1268         break;
1269 
1270     case PARANOIA_CB_FIXUP_ATOM:
1271         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_ATOM";
1272         paranoia_read_limited_error = 6;
1273         break;
1274 
1275     case PARANOIA_CB_READERR:
1276         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_READERR";
1277         paranoia_read_limited_error = 6;
1278         break;
1279 
1280     case PARANOIA_CB_SKIP:
1281         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_SKIP";
1282         paranoia_read_limited_error = 8;
1283         break;
1284 
1285     case PARANOIA_CB_OVERLAP:
1286         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_OVERLAP";
1287         break;
1288 
1289     case PARANOIA_CB_SCRATCH:
1290         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_SCRATCH";
1291         paranoia_read_limited_error = 7;
1292         break;
1293 
1294     case PARANOIA_CB_DRIFT:
1295         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_DRIFT";
1296         paranoia_read_limited_error = 4;
1297         break;
1298 
1299     case PARANOIA_CB_FIXUP_DROPPED:
1300         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_DROPPED";
1301         paranoia_read_limited_error = 5;
1302         break;
1303 
1304     case PARANOIA_CB_FIXUP_DUPED:
1305         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_DUPED";
1306         paranoia_read_limited_error = 5;
1307         break;
1308     }
1309 }
1310 
1311 // needed for JSON file embedding
1312 #include "audiocd.moc"
1313