File indexing completed on 2024-04-14 04:43:21

0001 /*
0002  * Copyright (C) 2000 Rik Hemsley (rikkus) <rik@kde.org>
0003  * Copyright (C) 2000-2002 Michael Matz <matz@kde.org>
0004  * Copyright (C) 2001 Carsten Duvenhorst <duvenhorst@m2.uni-hannover.de>
0005  * Copyright (C) 2001 Adrian Schroeter <adrian@suse.de>
0006  * Copyright (C) 2003 Richard Lärkäng <richard@goteborg.utfors.se>
0007  * Copyright (C) 2003 Scott Wheeler <wheeler@kde.org>
0008  * Copyright (C) 2004-2005 Benjamin Meyer <ben at meyerhome dot net>
0009  *
0010  * This program is free software; you can redistribute it and/or modify
0011  * it under the terms of the GNU General Public License as published by
0012  * the Free Software Foundation; either version 2 of the License, or
0013  * (at your option) any later version.
0014  *
0015  * This program is distributed in the hope that it will be useful,
0016  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0018  * GNU General Public License for more details.
0019  *
0020  * You should have received a copy of the GNU General Public License
0021  * along with this program; if not, write to the Free Software
0022  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
0023  * USA.
0024  */
0025 
0026 #include "audiocd.h"
0027 #include <config-audiocd.h>
0028 
0029 extern "C" {
0030 // cdda_interface.h in cdparanoia 10.2 has a member called 'private' which the
0031 // C++ compiler doesn't like
0032 // we will thus use a generated local copy which renames that member.
0033 #include "cdda_interface.hpp"
0034 #include <cdda_paranoia.h>
0035 void paranoiaCallback(long, int);
0036 
0037 int Q_DECL_EXPORT kdemain(int argc, char **argv);
0038 }
0039 
0040 #include "plugins/audiocdencoder.h"
0041 
0042 #include <sys/resource.h>
0043 #include <sys/stat.h>
0044 #include <unistd.h>
0045 
0046 #include <cstdlib>
0047 #include <ctime>
0048 
0049 #include <QApplication>
0050 #include <QFile>
0051 #include <QFileInfo>
0052 #include <QHash>
0053 #include <QRegExp>
0054 #include <QUrlQuery>
0055 
0056 #include "audiocd_kio_debug.h"
0057 
0058 #include <KLocalizedString>
0059 #include <KMacroExpander>
0060 #include <QRegularExpression>
0061 
0062 // CDDB
0063 #include <KCDDB/Client>
0064 #include <KCompactDisc>
0065 
0066 // Pseudo plugin class to embed metadata
0067 class KIOPluginForMetaData : public QObject
0068 {
0069     Q_OBJECT
0070     Q_PLUGIN_METADATA(IID "org.kde.kio.worker.audiocd" FILE "audiocd.json")
0071 };
0072 
0073 using namespace KIO;
0074 
0075 using namespace AudioCD;
0076 
0077 extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv)
0078 {
0079     // QApplication uses libkcddb which needs a valid kapp pointer
0080     // GUIenabled must be true as libkcddb sometimes wants to communicate
0081     // with the user
0082     qunsetenv("SESSION_MANAGER");
0083     QApplication app(argc, argv);
0084     app.setApplicationName(QStringLiteral("kio_audiocd"));
0085     KLocalizedString::setApplicationDomain(QByteArrayLiteral("kio_audiocd"));
0086 
0087     if (argc != 4) {
0088         fprintf(stderr, "Usage: kio_audiocd protocol pool app\n");
0089         exit(-1);
0090     }
0091 
0092     qCDebug(AUDIOCD_KIO_LOG) << "Starting " << getpid();
0093 
0094     AudioCDProtocol worker(argv[1], argv[2], argv[3]);
0095     worker.dispatchLoop();
0096 
0097     qCDebug(AUDIOCD_KIO_LOG) << "Done";
0098 
0099     return 0;
0100 }
0101 
0102 enum Which_dir {
0103     Unknown = 0, // Error
0104     Info, // CDDB info
0105     Base, // The KIO worker base directory showing all drives
0106     Root, // The root directory, shows all these :)
0107     FullCD, // Show a single file containing all of the data
0108     EncoderDir, // The root directory created by an encoder
0109     SubDir // A directory created from the Album name configuration
0110 };
0111 
0112 class AudioCDProtocol::Private {
0113 public:
0114     Private()
0115         : s_info(i18n("Information"))
0116         , s_fullCD(i18n("Full CD"))
0117     {
0118         clearURLargs();
0119     }
0120 
0121     void clearURLargs()
0122     {
0123         req_allTracks = false;
0124         which_dir = Unknown;
0125         req_track = -1;
0126         cddbUserChoice = -1;
0127     }
0128 
0129     bool tocsAreDifferent(struct cdrom_drive *drive)
0130     {
0131         if (tracks != (uint)drive->tracks)
0132             return true;
0133         for (int i = 0; i < drive->tracks; ++i) {
0134             if (disc_toc[i].dwStartSector != drive->disc_toc[i].dwStartSector || disc_toc[i].bFlags != drive->disc_toc[i].bFlags
0135                 || disc_toc[i].bTrack != drive->disc_toc[i].bTrack)
0136                 return true;
0137         }
0138         return false;
0139     }
0140 
0141     void setToc(struct cdrom_drive *drive)
0142     {
0143         for (int i = 0; i < drive->tracks; ++i) {
0144             disc_toc[i].dwStartSector = drive->disc_toc[i].dwStartSector;
0145             disc_toc[i].bFlags = drive->disc_toc[i].bFlags;
0146             disc_toc[i].bTrack = drive->disc_toc[i].bTrack;
0147         }
0148     }
0149 
0150     // The type/which of request
0151     bool req_allTracks;
0152     Which_dir which_dir;
0153     int req_track;
0154     QString fname;
0155     QString child_dir;
0156     AudioCDEncoder *encoder_dir_type;
0157 
0158     // Misc settings
0159     QString device; // URL settable
0160     int paranoiaLevel; // URL settable
0161     bool reportErrors;
0162 
0163     // Directory strings, never change after init
0164     const QString s_info;
0165     const QString s_fullCD;
0166 
0167     // Current CD
0168     TOC disc_toc[MAXTRK];
0169     unsigned tracks;
0170     bool trackIsAudio[100];
0171 
0172     // CDDB items
0173     KCDDB::Result cddbResult;
0174     CDInfoList cddbList;
0175     int cddbUserChoice; // URL settable
0176     KCDDB::CDInfo cddbBestChoice;
0177 
0178     // Template for ..
0179     QString fileNameTemplate; // URL settable
0180     QString albumNameTemplate; // URL settable
0181     QString fileLocationTemplate; // URL settable
0182     QString rsearch;
0183     QString rreplace;
0184 
0185     // Current strings for this CD and or cddb selection
0186     QStringList templateTitles;
0187     QString templateAlbumName;
0188     QString templateFileLocation;
0189 };
0190 
0191 int paranoia_read_limited_error;
0192 
0193 AudioCDProtocol::AudioCDProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app)
0194     : WorkerBase(protocol, pool, app)
0195 {
0196     d = new Private;
0197     // Add encoders
0198     AudioCDEncoder::findAllPlugins(this, encoders);
0199     if (encoders.isEmpty()) {
0200         qCCritical(AUDIOCD_KIO_LOG) << "No encoders available";
0201         return;
0202     }
0203 
0204     encoderTypeCDA = encoderFromExtension(QStringLiteral(".cda"));
0205     encoderTypeWAV = encoderFromExtension(QStringLiteral(".wav"));
0206 }
0207 
0208 AudioCDProtocol::~AudioCDProtocol()
0209 {
0210     qDeleteAll(encoders);
0211     delete d;
0212 }
0213 
0214 AudioCDEncoder *AudioCDProtocol::encoderFromExtension(const QString &extension)
0215 {
0216     AudioCDEncoder *encoder;
0217     for (int i = encoders.size() - 1; i >= 0; --i) {
0218         encoder = encoders.at(i);
0219         if (QLatin1String(".") + QLatin1String(encoder->fileType()) == extension)
0220             return encoder;
0221     }
0222 
0223     qCWarning(AUDIOCD_KIO_LOG) << "No encoder available for format" << extension;
0224     return nullptr;
0225 }
0226 
0227 AudioCDEncoder *AudioCDProtocol::determineEncoder(const QString &filename)
0228 {
0229     int pos = filename.lastIndexOf(QLatin1Char('.'));
0230     return encoderFromExtension(filename.mid(pos));
0231 }
0232 
0233 static void setDeviceToCd(KCompactDisc *cd, struct cdrom_drive *drive)
0234 {
0235 #if defined(HAVE_CDDA_IOCTL_DEVICE)
0236     cd->setDevice(QLatin1String(drive->ioctl_device_name), 50, false);
0237 #elif defined(__FreeBSD__) || defined(__DragonFly__)
0238     // FreeBSD's cdparanoia as of January 5th 2006 has rather broken
0239     // support for non-SCSI devices. Although it finds ATA cdroms just
0240     // fine, there is no straightforward way to discover the device
0241     // name associated with the device, which throws the rest of audiocd
0242     // for a loop.
0243     //
0244     if (!(drive->dev) || (COOKED_IOCTL == drive->interface)) {
0245         // For ATAPI devices, we have no real choice. Use the
0246         // user selected value, even if there is none.
0247         //
0248         qCWarning(AUDIOCD_KIO_LOG) << "Found an ATAPI device, assuming it is the "
0249                                       "one specified by the user.";
0250         cd->setDevice(QString::fromLatin1(drive->cdda_device_name));
0251     } else {
0252         qCDebug(AUDIOCD_KIO_LOG) << "Found a SCSI or ATAPICAM device.";
0253         if (strlen(drive->dev->device_path) > 0) {
0254             cd->setDevice(QString::fromLatin1(drive->dev->device_path));
0255         } else {
0256             // But the device_path can be empty under some
0257             // circumstances, so build a representation from
0258             // the unit number and SCSI device name.
0259             //
0260             QString devname = QStringLiteral("/dev/%1%2").arg(QString::fromLatin1(drive->dev->given_dev_name)).arg(drive->dev->given_unit_number);
0261             qCDebug(AUDIOCD_KIO_LOG) << "  Using derived name " << devname;
0262             cd->setDevice(devname);
0263         }
0264     }
0265 #else
0266 #ifdef __GNUC__
0267 #warning audiocd KIO worker is not going to work for you
0268 #endif
0269 #endif
0270 }
0271 
0272 // Initiate a request to access the CD drive.  If there is no valid drive
0273 // specified or there is a problem, then error() must be (or have been)
0274 // called before returning a null pointer.
0275 KIO::WorkerResult AudioCDProtocol::initRequest(const QUrl &url, struct cdrom_drive **drive)
0276 {
0277     // Load our Settings.
0278     loadSettings();
0279     // Then URL parameters can overrule our settings.
0280     parseURLArgs(url);
0281 
0282     const KIO::WorkerResult result = getDrive(drive);
0283     if (!result.success()) {
0284         return result;
0285     }
0286 
0287     if (d->tocsAreDifferent(*drive)) {
0288         // Update our knowledge of the disc
0289         KCompactDisc cd(KCompactDisc::Asynchronous);
0290         setDeviceToCd(&cd, *drive);
0291         d->setToc(*drive);
0292         d->tracks = cd.tracks();
0293         for (uint i = 0; i < cd.tracks(); i++)
0294             d->trackIsAudio[i] = cd.isAudio(i + 1);
0295 
0296         KCDDB::Client c;
0297         d->cddbResult = c.lookup(cd.discSignature());
0298         if (d->cddbResult == Success) {
0299             d->cddbList = c.lookupResponse();
0300             // FIXME: not always the best choice, see bug 279485
0301             // for a similar problem with Amarok
0302             d->cddbBestChoice = d->cddbList.first();
0303         }
0304         generateTemplateTitles();
0305     }
0306 
0307     // Determine what file or folder that is wanted.
0308     QString path = url.path();
0309     if (!path.isEmpty() && path[0] == QLatin1Char('/'))
0310         path = path.mid(1);
0311 
0312     d->req_allTracks = false;
0313 
0314     // See which file and directory they want
0315     QString remainingDirPath;
0316     d->which_dir = Unknown;
0317     if (path.isEmpty()) {
0318         d->which_dir = Root;
0319         d->encoder_dir_type = encoderTypeWAV;
0320         remainingDirPath = d->templateFileLocation;
0321         d->fname = QString();
0322     } else {
0323         for (int i = encoders.size() - 1; i >= 0; --i) {
0324             AudioCDEncoder *encoder = encoders.at(i);
0325             QString encoderFileLocation = encoder->type();
0326             if (!d->templateFileLocation.isEmpty())
0327                 encoderFileLocation = encoderFileLocation + QLatin1String("/") + d->templateFileLocation;
0328             if (path == encoder->type()) {
0329                 d->which_dir = EncoderDir;
0330                 d->encoder_dir_type = encoder;
0331                 remainingDirPath = encoderFileLocation.mid(path.length());
0332                 d->fname = QString();
0333                 break;
0334             } else if (encoderFileLocation.startsWith(path)) {
0335                 d->which_dir = SubDir;
0336                 d->encoder_dir_type = encoder;
0337                 remainingDirPath = encoderFileLocation.mid(path.length());
0338                 d->fname = QString();
0339                 break;
0340             } else if (path.startsWith(encoderFileLocation)) {
0341                 d->which_dir = SubDir;
0342                 d->encoder_dir_type = encoder;
0343                 remainingDirPath = QString();
0344                 d->fname = path.mid(encoderFileLocation.length() + 1);
0345                 break;
0346             } else if (path.startsWith(encoder->type())) {
0347                 d->which_dir = EncoderDir;
0348                 d->encoder_dir_type = encoder;
0349                 remainingDirPath = QString();
0350                 d->fname = path.mid(encoder->type().length() + 1);
0351             }
0352         }
0353         if (Unknown == d->which_dir) {
0354             if (path.startsWith(d->s_info)) {
0355                 d->which_dir = Info;
0356                 d->fname = path.mid(d->s_info.length() + 1);
0357             } else if (path.startsWith(d->s_fullCD)) {
0358                 d->which_dir = FullCD;
0359                 d->fname = path.mid(d->s_fullCD.length() + 1);
0360                 d->req_allTracks = true;
0361             } else if (d->templateFileLocation.startsWith(path)) {
0362                 d->which_dir = SubDir;
0363                 d->encoder_dir_type = encoderTypeWAV;
0364                 remainingDirPath = d->templateFileLocation.mid(path.length());
0365                 d->fname = QString();
0366             } else if (path.startsWith(d->templateFileLocation)) {
0367                 d->encoder_dir_type = encoderTypeWAV;
0368                 remainingDirPath = QString();
0369                 d->fname = path.mid(d->templateFileLocation.length() + 1);
0370             } else {
0371                 d->encoder_dir_type = encoderTypeWAV;
0372                 remainingDirPath = QString();
0373                 d->fname = path;
0374             }
0375         }
0376     }
0377     if (!remainingDirPath.isEmpty() && remainingDirPath[0] == QLatin1Char('/'))
0378         remainingDirPath = remainingDirPath.mid(1);
0379     d->child_dir = remainingDirPath.split(QStringLiteral("/")).first();
0380 
0381     // See if the url is a track
0382     d->req_track = -1;
0383     if (!d->fname.isEmpty()) {
0384         QString name(d->fname);
0385 
0386         // Remove extension
0387         int dot = name.lastIndexOf(QLatin1Char('.'));
0388         if (dot >= 0)
0389             name.truncate(dot);
0390 
0391         // See if it matches a cddb title
0392         uint trackNumber;
0393         for (trackNumber = 0; trackNumber < d->tracks; trackNumber++) {
0394             if (d->templateTitles[trackNumber] == name)
0395                 break;
0396         }
0397         if (trackNumber < d->tracks)
0398             d->req_track = trackNumber;
0399         else {
0400             /* Not found in title list. Try hard to find a number in the
0401                      string. */
0402             int start = 0;
0403             int end = 0;
0404             // Find where the numbers start
0405             while (start < name.length()) {
0406                 if (name[start++].isDigit())
0407                     break;
0408             }
0409             // Find where the numbers end
0410             for (end = start; end < name.length(); ++end)
0411                 if (!name[end].isDigit())
0412                     break;
0413             if (start < name.length()) {
0414                 bool ok;
0415                 // The external representation counts from 1 so subtract 1.
0416                 d->req_track = name.mid(start - 1, end - start + 2).toInt(&ok) - 1;
0417                 if (!ok)
0418                     d->req_track = -1;
0419             }
0420         }
0421     }
0422     if (d->req_track >= (int)d->tracks)
0423         d->req_track = -1;
0424 
0425     qCDebug(AUDIOCD_KIO_LOG) << "path=" << path << " file=" << d->fname << " req_track=" << d->req_track << " which_dir=" << d->which_dir
0426                              << " full CD?=" << d->req_allTracks;
0427     return KIO::WorkerResult::pass();
0428 }
0429 
0430 bool AudioCDProtocol::getSectorsForRequest(struct cdrom_drive *drive, long &firstSector, long &lastSector) const
0431 {
0432     if (d->req_allTracks) { // we rip all the tracks of the CD
0433         firstSector = cdda_track_firstsector(drive, 1);
0434         lastSector = cdda_track_lastsector(drive, cdda_tracks(drive));
0435     } else { // we only rip the selected track
0436         int trackNumber = d->req_track + 1;
0437 
0438         if (trackNumber <= 0 || trackNumber > cdda_tracks(drive))
0439             return false;
0440         firstSector = cdda_track_firstsector(drive, trackNumber);
0441         lastSector = cdda_track_lastsector(drive, trackNumber);
0442     }
0443     return true;
0444 }
0445 
0446 static uint findInformationFileNumber(const QString &filename, uint max) {
0447     if (filename == QStringLiteral("%1.txt").arg(i18n("CDDB Information")))
0448         return 1;
0449 
0450     for (uint i = 2; i <= max; ++i) {
0451         if (filename == QStringLiteral("%1_%2.txt").arg(i18n("CDDB Information")).arg(i)) {
0452             return i;
0453         }
0454     }
0455 
0456     return max + 1;
0457 }
0458 
0459 // See whether this is the root of the io worker (listing of all
0460 // available CD drives) or the root of a drive (listing of encoder
0461 // subdirectories and track WAV files).
0462 //
0463 // This needs to be deduced from the URL without calling initRequest()
0464 // or getDrive(), either of which may return KIO::WorkerResult::fail().  It is not an
0465 // error if there is no drive to be accessed and none needs to be.
0466 //
0467 // So parse the URL only.  Either Base, Root or Unknown (meaning "anywhere
0468 // else") will be returned.
0469 static Which_dir whichFromUrl(const QUrl &url)
0470 {
0471     QUrlQuery query(url);
0472     if (!query.hasQueryItem(QStringLiteral("device"))) { // see if "device" query present
0473         return Base; // if not, must be KIO worker base
0474     }
0475 
0476     if (url.path() == QLatin1String("/")) { // see if the device root
0477         return Root;
0478     }
0479 
0480     return Unknown; // elsewhere within device
0481 }
0482 
0483 // Check that the URL does not have a host specified, and return an error
0484 // if it does.  Moved here because not all operations need to or should
0485 // call initRequest().
0486 KIO::WorkerResult AudioCDProtocol::checkNoHost(const QUrl &url)
0487 {
0488     if (!url.host().isEmpty()) {
0489         return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION,
0490                                        i18n("You cannot specify a host with this protocol. "
0491                                             "Please use the audiocd:/ format instead."));
0492     }
0493 
0494     return KIO::WorkerResult::pass();
0495 }
0496 
0497 // Escape any '/'es in what should be a file name.
0498 static QString escapePath(const QString &in)
0499 {
0500     QString result = in;
0501     // FIXME: maybe could use QUrl::toPercentEncoding()
0502     result.replace(QLatin1Char('/'), QLatin1String("%2F"));
0503     return result;
0504 }
0505 
0506 KIO::WorkerResult AudioCDProtocol::get(const QUrl &url)
0507 {
0508     const KIO::WorkerResult noHostResult = checkNoHost(url);
0509     if (!noHostResult.success()) {
0510         return noHostResult;
0511     }
0512 
0513     struct cdrom_drive *drive;
0514     const KIO::WorkerResult initResult = initRequest(url, &drive);
0515     if (!initResult.success()) {
0516         return initResult;
0517     }
0518 
0519     if (d->fname.contains(i18n("CDDB Information"))) {
0520         const uint choice = findInformationFileNumber(d->fname, d->cddbList.count());
0521         uint count = 1;
0522         CDInfoList::iterator it;
0523         bool found = false;
0524         for (it = d->cddbList.begin(); it != d->cddbList.end(); ++it) {
0525             if (count == choice) {
0526                 // FIXME: should be "text/plain"?
0527                 mimeType(QStringLiteral("text/html"));
0528                 data((*it).toString().toUtf8());
0529                 // send an empty QByteArray to signal end of data.
0530                 data(QByteArray());
0531                 found = true;
0532                 break;
0533             }
0534             count++;
0535         }
0536         if (!found && d->fname.contains(i18n("CDDB Information") + QLatin1Char(':'))) {
0537             // FIXME: should be "text/plain"?
0538             mimeType(QStringLiteral("text/html"));
0539             // data(QCString( d->fname.latin1() ));
0540             // send an empty QByteArray to signal end of data.
0541             data(QByteArray());
0542             found = true;
0543         }
0544         cdda_close(drive);
0545         if (!found)
0546             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.path());
0547         return KIO::WorkerResult::pass();
0548     }
0549 
0550     long firstSector, lastSector;
0551     if (!getSectorsForRequest(drive, firstSector, lastSector)) {
0552         cdda_close(drive);
0553         return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.path());
0554     }
0555 
0556     AudioCDEncoder *encoder = determineEncoder(d->fname);
0557     if (!encoder) {
0558         cdda_close(drive);
0559         return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.path());
0560     }
0561 
0562     KCDDB::CDInfo info;
0563     if (d->cddbResult == KCDDB::Success) {
0564         info = d->cddbBestChoice;
0565 
0566         int track = d->req_track + 1;
0567 
0568         // hack
0569         // do we rip the whole CD?
0570         if (d->req_allTracks) {
0571             track = 1;
0572             // YES => the title of the file is the title of the CD
0573             info.track(track - 1).set(Title, info.get(Title));
0574         }
0575         encoder->fillSongInfo(info, track, QString());
0576     }
0577     long totalByteCount = CD_FRAMESIZE_RAW * (lastSector - firstSector + 1);
0578     long time_secs = (8 * totalByteCount) / (44100 * 2 * 16);
0579 
0580     unsigned long size = encoder->size(time_secs);
0581     totalSize(size);
0582     mimeType(QLatin1String(encoder->mimeType()));
0583 
0584     // Read data (track/disk) from the cd
0585     const KIO::WorkerResult readResult = paranoiaRead(drive, firstSector, lastSector, encoder, url.fileName(), size);
0586 
0587     // send an empty QByteArray to signal end of data.
0588     data(QByteArray());
0589 
0590     cdda_close(drive);
0591 
0592     return readResult;
0593 }
0594 
0595 static void app_dir(UDSEntry &e, const QString &n, size_t s)
0596 {
0597     const mode_t _umask = ::umask(0);
0598     ::umask(_umask);
0599 
0600     e.clear();
0601     e.reserve(5);
0602     e.fastInsert(KIO::UDSEntry::UDS_NAME, QFile::decodeName(n.toLocal8Bit()));
0603     e.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0604     e.fastInsert(KIO::UDSEntry::UDS_ACCESS, (0555 & ~_umask));
0605     e.fastInsert(KIO::UDSEntry::UDS_SIZE, s);
0606     e.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory"));
0607 }
0608 
0609 static void app_file(UDSEntry &e, const QString &n, size_t s, const QString &mimetype = QString())
0610 {
0611     const mode_t _umask = ::umask(0);
0612     ::umask(_umask);
0613 
0614     e.clear();
0615     e.reserve(6);
0616     e.fastInsert(KIO::UDSEntry::UDS_NAME, QFile::decodeName(n.toLocal8Bit()));
0617     e.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
0618     // Use current date and time to avoid confusions. See
0619     // https://bugs.kde.org/show_bug.cgi?id=400114
0620     e.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, QDateTime::currentDateTime().toSecsSinceEpoch());
0621     e.fastInsert(KIO::UDSEntry::UDS_ACCESS, (0444 & ~_umask));
0622     e.fastInsert(KIO::UDSEntry::UDS_SIZE, s);
0623     if (!mimetype.isEmpty())
0624         e.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, mimetype);
0625 }
0626 
0627 KIO::WorkerResult AudioCDProtocol::stat(const QUrl &url)
0628 {
0629     const KIO::WorkerResult result = checkNoHost(url);
0630     if (!result.success()) {
0631         return result;
0632     }
0633 
0634     if (whichFromUrl(url) == Base) {
0635         // This is the top level directory with CDROM devices only.
0636         // No drive access is required.  url.fileName() is empty here.
0637 
0638         UDSEntry entry;
0639         // One subdirectory for each drive
0640         const QStringList &deviceNames = KCompactDisc::cdromDeviceNames();
0641         app_dir(entry, escapePath(QStringLiteral("/")), deviceNames.count());
0642         statEntry(entry);
0643         return KIO::WorkerResult::pass();
0644     }
0645 
0646     struct cdrom_drive *drive;
0647     const KIO::WorkerResult initResult = initRequest(url, &drive);
0648     if (!initResult.success()) {
0649         return initResult;
0650     }
0651 
0652     if (d->which_dir == Info) {
0653         // This is the info dir or one of the info files
0654         if (d->fname.isEmpty()) {
0655             // The info dir
0656             UDSEntry entry;
0657             app_dir(entry, escapePath(url.fileName()), d->cddbList.count());
0658             statEntry(entry);
0659             return KIO::WorkerResult::pass();
0660         }
0661         if (d->fname.contains(i18n("CDDB Information"))) {
0662             // choice is 1-indexed so we need <= and -1 when accessing d->cddbList
0663             const uint choice = findInformationFileNumber(d->fname, d->cddbList.count());
0664             if (choice <= (uint)d->cddbList.count()) {
0665                 // It's a valid info file
0666                 UDSEntry entry;
0667                 app_file(entry, escapePath(url.fileName()), d->cddbList.at(choice - 1).toString().toLatin1().size(), QStringLiteral("text/plain"));
0668                 statEntry(entry);
0669                 return KIO::WorkerResult::pass();
0670             }
0671         }
0672     }
0673 
0674     const bool isFile = !d->fname.isEmpty();
0675 
0676     // the track number. 0 if ripping
0677     // the whole CD.
0678     const uint trackNumber = d->req_track + 1;
0679 
0680     if (!d->req_allTracks) { // we only want to rip one track.
0681         // does this track exist?
0682         if (isFile && (trackNumber < 1 || trackNumber > d->tracks)) {
0683             cdda_close(drive);
0684             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.path());
0685         }
0686     }
0687 
0688     UDSEntry entry;
0689     if (!isFile) {
0690         app_dir(entry, escapePath(url.fileName()), cdda_tracks(drive));
0691     } else {
0692         AudioCDEncoder *encoder = determineEncoder(d->fname);
0693         long firstSector = 0, lastSector = 0;
0694         getSectorsForRequest(drive, firstSector, lastSector);
0695         app_file(entry, escapePath(url.fileName()), fileSize(firstSector, lastSector, encoder));
0696     }
0697 
0698     statEntry(entry);
0699     cdda_close(drive);
0700     return KIO::WorkerResult::pass();
0701 }
0702 
0703 KIO::WorkerResult AudioCDProtocol::listDir(const QUrl &url)
0704 {
0705     const KIO::WorkerResult result = checkNoHost(url);
0706     if (!result.success()) {
0707         return result;
0708     }
0709 
0710     UDSEntry entry;
0711     app_dir(entry, QStringLiteral("."), 0);
0712     listEntry(entry);
0713 
0714     if (whichFromUrl(url) == Base) {
0715         // The top level directory with CDROM devices only.
0716 
0717         const QStringList deviceNames = KCompactDisc::cdromDeviceNames();
0718         for (const QString &deviceName : deviceNames) {
0719             const QString &device = KCompactDisc::urlToDevice(KCompactDisc::cdromDeviceUrl(deviceName));
0720             QUrl targetUrl = url;
0721             QUrlQuery targetQuery;
0722             targetQuery.addQueryItem(QStringLiteral("device"), device);
0723             targetUrl.setQuery(targetQuery);
0724 
0725             app_dir(entry, escapePath(device), 2 + encoders.count());
0726             entry.fastInsert(KIO::UDSEntry::UDS_TARGET_URL, targetUrl.url());
0727             entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, deviceName);
0728             listEntry(entry);
0729         }
0730         totalSize(entry.count());
0731         return KIO::WorkerResult::pass();
0732     }
0733 
0734     struct cdrom_drive *drive;
0735     const KIO::WorkerResult initResult = initRequest(url, &drive);
0736     if (!initResult.success()) {
0737         return initResult;
0738     }
0739 
0740     if (d->which_dir == Unknown) {
0741         cdda_close(drive);
0742         return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.path());
0743     }
0744 
0745     if (!d->fname.isEmpty()) {
0746         cdda_close(drive);
0747         return KIO::WorkerResult::fail(KIO::ERR_IS_FILE, url.path());
0748     }
0749 
0750     // Generate templated names every time
0751     // because the template might have changed.
0752     generateTemplateTitles();
0753 
0754     if (d->which_dir == Info) {
0755         CDInfoList::iterator it;
0756         uint count = 1;
0757         for (it = d->cddbList.begin(); it != d->cddbList.end(); ++it) {
0758             (*it).toString();
0759             if (count == 1)
0760                 app_file(entry, QString::fromLatin1("%1.txt").arg(i18n("CDDB Information")), ((*it).toString().length()) + 1);
0761             else
0762                 app_file(entry, QString::fromLatin1("%1_%2.txt").arg(i18n("CDDB Information")).arg(count), ((*it).toString().length()) + 1);
0763             count++;
0764             listEntry(entry);
0765         }
0766         // Error
0767         if (count == 1) {
0768             app_file(entry, QString::fromLatin1("%1: %2.txt").arg(i18n("CDDB Information")).arg(KCDDB::resultToString(d->cddbResult)), 0);
0769             count++;
0770             listEntry(entry);
0771         }
0772     }
0773 
0774     if (d->which_dir == Root) {
0775         // List virtual directories.
0776         app_dir(entry, d->s_fullCD, encoders.count());
0777         listEntry(entry);
0778 
0779         // Either >0 cddb results or cddb error file
0780         app_dir(entry, d->s_info, d->cddbList.count());
0781         listEntry(entry);
0782 
0783         // List the encoders
0784         AudioCDEncoder *encoder;
0785         for (int i = encoders.size() - 1; i >= 0; --i) {
0786             encoder = encoders.at(i);
0787             // Skip the directory that is in the root (you can still go in it, just
0788             // don't show it)
0789             if (encoder == encoderTypeWAV)
0790                 continue;
0791             app_dir(entry, encoder->type(), d->tracks);
0792             listEntry(entry);
0793         }
0794     }
0795 
0796     // Now fill in the tracks for the current directory
0797     if (d->which_dir == FullCD) {
0798         AudioCDEncoder *encoder;
0799         for (int i = encoders.size() - 1; i >= 0; --i) {
0800             encoder = encoders.at(i);
0801             if (d->cddbResult != KCDDB::Success)
0802                 addEntry(d->s_fullCD, encoder, drive, -1);
0803             else
0804                 addEntry(d->templateAlbumName, encoder, drive, -1);
0805         }
0806     }
0807 
0808     if (d->which_dir == SubDir || d->which_dir == Root || d->which_dir == EncoderDir) {
0809         if (d->child_dir.isEmpty()) {
0810             // we are at the end of the hierarchy, list the tracks
0811             for (uint trackNumber = 1; trackNumber <= d->tracks; trackNumber++) {
0812                 // Skip data tracks
0813                 if (!d->trackIsAudio[trackNumber - 1])
0814                     continue;
0815 
0816                 switch (d->which_dir) {
0817                 case EncoderDir:
0818                 case SubDir:
0819                 case Root:
0820                     addEntry(d->templateTitles[trackNumber - 1], d->encoder_dir_type, drive, trackNumber);
0821                     break;
0822                 case Info:
0823                 case Unknown:
0824                 default:
0825                     cdda_close(drive);
0826                     return KIO::WorkerResult::fail(KIO::ERR_INTERNAL, url.path());
0827                 }
0828             }
0829         } else {
0830             app_dir(entry, d->child_dir, 1);
0831             listEntry(entry);
0832         }
0833     }
0834 
0835     totalSize(entry.count());
0836     cdda_close(drive);
0837     return KIO::WorkerResult::pass();
0838 }
0839 
0840 void AudioCDProtocol::addEntry(const QString &trackTitle, AudioCDEncoder *encoder, struct cdrom_drive *drive, int trackNo)
0841 {
0842     if (!encoder || !drive)
0843         return;
0844 
0845     long theFileSize = 0;
0846     if (trackNo == -1) { // adding entry for the full CD
0847         theFileSize = fileSize(cdda_track_firstsector(drive, 1), cdda_track_lastsector(drive, cdda_tracks(drive)), encoder);
0848     } else { // adding one regular track
0849         long firstSector = cdda_track_firstsector(drive, trackNo);
0850         long lastSector = cdda_track_lastsector(drive, trackNo);
0851         theFileSize = fileSize(firstSector, lastSector, encoder);
0852     }
0853     UDSEntry entry;
0854     app_file(entry, trackTitle + QLatin1String(".") + QLatin1String(encoder->fileType()), theFileSize, QLatin1String(encoder->mimeType()));
0855     listEntry(entry);
0856 }
0857 
0858 long AudioCDProtocol::fileSize(long firstSector, long lastSector, AudioCDEncoder *encoder)
0859 {
0860     if (!encoder)
0861         return 0;
0862 
0863     long filesize = CD_FRAMESIZE_RAW * (lastSector - firstSector + 1);
0864     long length_seconds = (filesize) / 176400;
0865 
0866     return encoder->size(length_seconds);
0867 }
0868 
0869 KIO::WorkerResult AudioCDProtocol::getDrive(struct cdrom_drive **drive)
0870 {
0871     const QByteArray device(QFile::encodeName(d->device));
0872     if (device.isEmpty()) {
0873         *drive = nullptr;
0874         return KIO::WorkerResult::fail(KIO::ERR_MALFORMED_URL, i18nc("The URL does not include a device name", "Missing device specification"));
0875     }
0876 
0877     *drive = cdda_identify(device.data(), CDDA_MESSAGE_FORGETIT, nullptr);
0878     if (*drive == nullptr) {
0879         qCDebug(AUDIOCD_KIO_LOG) << "Can't find an audio CD on: \"" << d->device << "\"";
0880 
0881         const QFileInfo fi(d->device);
0882         if (!fi.isReadable())
0883             return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED,
0884                                            i18n("Device does not have read permissions for this account.  "
0885                                                 "Check the read permissions on the device."));
0886         if (!fi.isWritable())
0887             return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED,
0888                                            i18n("Device does not have write permissions for this account.  "
0889                                                 "Check the write permissions on the device."));
0890         if (!fi.exists())
0891             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, d->device);
0892 
0893         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED,
0894                                        i18n("Unknown error.  If you have a cd in the drive try running "
0895                                             "cdparanoia -vsQ as yourself (not root). Do you see a track "
0896                                             "list? If not, make sure you have permission to access the CD "
0897                                             "device. If you are using SCSI emulation (possible if you "
0898                                             "have an IDE CD writer) then make sure you check that you "
0899                                             "have read and write permissions on the generic SCSI device, "
0900                                             "which is probably /dev/sg0, /dev/sg1, etc.. If it still does "
0901                                             "not work, try typing audiocd:/?device=/dev/sg0 (or similar) "
0902                                             "to tell kio_audiocd which device your CD-ROM is."));
0903     }
0904 
0905     if (cdda_open(*drive) != 0) {
0906         qCDebug(AUDIOCD_KIO_LOG) << "cdda_open failed";
0907         cdda_close(*drive);
0908         *drive = nullptr;
0909         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, d->device);
0910     }
0911 
0912     return KIO::WorkerResult::pass();
0913 }
0914 
0915 KIO::WorkerResult AudioCDProtocol::paranoiaRead(struct cdrom_drive *drive,
0916                                                 long firstSector,
0917                                                 long lastSector,
0918                                                 AudioCDEncoder *encoder,
0919                                                 const QString &fileName,
0920                                                 unsigned long size)
0921 {
0922     if (!encoder || !drive)
0923         return KIO::WorkerResult::fail();
0924 
0925     cdrom_paranoia *paranoia = paranoia_init(drive);
0926     if (nullptr == paranoia) {
0927         qCDebug(AUDIOCD_KIO_LOG) << "paranoia_init failed";
0928         return KIO::WorkerResult::fail();
0929     }
0930 
0931     int paranoiaLevel = PARANOIA_MODE_FULL ^ PARANOIA_MODE_NEVERSKIP;
0932     switch (d->paranoiaLevel) {
0933     case 0:
0934         paranoiaLevel = PARANOIA_MODE_DISABLE;
0935         break;
0936 
0937     case 1:
0938         paranoiaLevel |= PARANOIA_MODE_OVERLAP;
0939         paranoiaLevel &= ~PARANOIA_MODE_VERIFY;
0940         break;
0941 
0942     case 2:
0943         paranoiaLevel |= PARANOIA_MODE_NEVERSKIP;
0944     default:
0945         break;
0946     }
0947 
0948     paranoia_modeset(paranoia, paranoiaLevel);
0949 
0950     cdda_verbose_set(drive, CDDA_MESSAGE_PRINTIT, CDDA_MESSAGE_FORGETIT);
0951 
0952     paranoia_seek(paranoia, firstSector, SEEK_SET);
0953 
0954     long currentSector(firstSector);
0955 
0956     unsigned long processed = encoder->readInit(CD_FRAMESIZE_RAW * (lastSector - firstSector + 1));
0957     // TODO test for errors (processed<0)?
0958     processedSize(processed);
0959     bool ok = true;
0960     QString errorString;
0961 
0962     unsigned long lastSize = size;
0963     unsigned long diff = 0;
0964 
0965     paranoia_read_limited_error = 0;
0966     int warned = 0;
0967     while (currentSector <= lastSector) {
0968         // TODO make the 5 configurable? The default in the lib is 20 fyi
0969         qint16 *buf = paranoia_read_limited(paranoia, paranoiaCallback, 5);
0970         if (warned == 0 && paranoia_read_limited_error >= 5 && d->reportErrors) {
0971             warning(
0972                 i18n("AudioCD: Disk damage detected on this track, risk of data "
0973                      "corruption."));
0974             warned = 1;
0975         }
0976         if (nullptr == buf) {
0977             qCDebug(AUDIOCD_KIO_LOG) << "Unrecoverable error in paranoia_read";
0978             ok = false;
0979             errorString = i18n("Error reading audio data for %1 from the CD", fileName);
0980             break;
0981         }
0982 
0983         ++currentSector;
0984 
0985         int encoderProcessed = encoder->read(buf, CD_FRAMESAMPLES);
0986         if (encoderProcessed == -1) {
0987             qCDebug(AUDIOCD_KIO_LOG) << "Encoder processing error, stopping.";
0988             ok = false;
0989             errorString = i18n("Could not read %1: encoding failed", fileName);
0990             const QString details = encoder->lastErrorMessage();
0991             if (!details.isEmpty())
0992                 errorString += QLatin1Char('\n') + details;
0993             break;
0994         }
0995         processed += encoderProcessed;
0996 
0997         /**
0998          * Because compression size is so 'unknown' use some guesswork
0999          *
1000          * 1) First assume that the reported size is correct and
1001          * only change the totalSize if the guess it outside a range of %5.
1002          * 2) Only increase in size unless the decrease is %5 of last estimate.
1003          * This prevents continues small changes which is just annoying.
1004          */
1005         unsigned long end = lastSector - firstSector;
1006         unsigned long cur = currentSector - firstSector;
1007         unsigned long estSize = (processed / cur) * end;
1008 
1009         // If our guess is within 5% of reported
1010         // size then use the reported size.
1011         unsigned long guess = (long)((100 / (float)size) * estSize);
1012         if ((guess > 97 && guess < 103) || estSize == 0) {
1013             if (processed > lastSize) {
1014                 totalSize(processed + 1);
1015                 lastSize = processed;
1016             }
1017         } else {
1018             float percentDone = ((float)cur / (float)end);
1019             // Calculate estimated amount that will be wrong
1020             diff = estSize - lastSize;
1021             diff = (diff * (unsigned long)((100 / (float)end) * (end - cur))) / 2;
1022             // Need 1% of data calculated as initial buffer, use %2 to be safe
1023             if (percentDone < .02) {
1024                 // qCDebug(AUDIOCD_KIO_LOG) << "val: " << (float)cur/(float)end << "
1025                 // diff: " << diff;
1026                 diff = 0;
1027             }
1028 
1029             // We are growing larger, increase total.
1030             if (lastSize < estSize) {
1031                 // qCDebug(AUDIOCD_KIO_LOG) << "lastGuess: " << lastSize << ", guess: "
1032                 // << estSize << ", diff: " << diff;
1033                 totalSize(estSize + diff);
1034                 lastSize = estSize + diff;
1035             } else {
1036                 int margin = (int)((percentDone)*75);
1037                 // Don't bother really trying until almost half way done.
1038                 if (percentDone <= .40)
1039                     margin = 7;
1040                 unsigned long low = lastSize - lastSize / margin;
1041                 if (estSize < low) {
1042                     // qCDebug(AUDIOCD_KIO_LOG) << "low: " << low << ", estSize: " <<
1043                     // estSize << ", num: " << margin;
1044                     totalSize(estSize);
1045                     lastSize = estSize;
1046                 }
1047             }
1048         }
1049         /**
1050          * End estimation.
1051          */
1052         // qCDebug(AUDIOCD_KIO_LOG) << "processed: " << processed << ", totalSize: "
1053         // << estSize;
1054         processedSize(processed);
1055     }
1056 
1057     if (processed > size)
1058         totalSize(processed);
1059 
1060     long encoderProcessed = encoder->readCleanup();
1061     if (encoderProcessed >= 0) {
1062         processed += encoderProcessed;
1063         if (processed > size)
1064             totalSize(processed);
1065         processedSize(processed);
1066     } else if (ok) // i.e. no error message already emitted
1067         errorString = i18n("Could not read %1: encoding failed", fileName);
1068 
1069     paranoia_free(paranoia);
1070     paranoia = nullptr;
1071 
1072     if (!errorString.isEmpty()) {
1073         return KIO::WorkerResult::fail(ERR_WORKER_DEFINED, errorString);
1074     }
1075 
1076     return KIO::WorkerResult::pass();
1077 }
1078 
1079 /**
1080  * Read the settings from the URL
1081  * @see loadSettings()
1082  */
1083 void AudioCDProtocol::parseURLArgs(const QUrl &url)
1084 {
1085     d->clearURLargs();
1086 
1087     // FIXME: Use QUrlQuery for parsing
1088     QString query(QUrl::fromPercentEncoding(url.query().toLatin1()));
1089 
1090     if (query.isEmpty())
1091         return;
1092 
1093     const QStringList tokens(query.split(QLatin1Char('&'), Qt::SkipEmptyParts));
1094 
1095     for (QStringList::ConstIterator it(tokens.constBegin()); it != tokens.constEnd(); ++it) {
1096         const QString token(*it);
1097 
1098         int equalsPos = token.indexOf(QLatin1Char('='));
1099         if (-1 == equalsPos)
1100             continue;
1101 
1102         const QString attribute(token.left(equalsPos));
1103         const QString value(token.mid(equalsPos + 1));
1104 
1105         if (attribute == QLatin1String("device"))
1106             d->device = value;
1107         else if (attribute == QLatin1String("paranoia_level"))
1108             d->paranoiaLevel = value.toInt();
1109         else if (attribute == QLatin1String("fileNameTemplate"))
1110             d->fileNameTemplate = value;
1111         else if (attribute == QLatin1String("albumNameTemplate"))
1112             d->albumNameTemplate = value;
1113         else if (attribute == QLatin1String("fileLocationTemplate"))
1114             d->fileLocationTemplate = value;
1115         else if (attribute == QLatin1String("cddbChoice"))
1116             d->cddbUserChoice = value.toInt();
1117         else if (attribute == QLatin1String("niceLevel")) {
1118             int niceLevel = value.toInt();
1119             if (setpriority(PRIO_PROCESS, getpid(), niceLevel) != 0)
1120                 qCDebug(AUDIOCD_KIO_LOG) << "Setting nice level to (" << niceLevel << ") failed.";
1121         }
1122     }
1123 }
1124 
1125 /**
1126  * Read the settings set by the kcm modules
1127  * @see parseURLArgs()
1128  */
1129 void AudioCDProtocol::loadSettings()
1130 {
1131     const KConfig *config = new KConfig(QStringLiteral("kcmaudiocdrc"), KConfig::NoGlobals);
1132     const KConfigGroup groupCDDA(config, QStringLiteral("CDDA"));
1133 
1134     d->device = QString(); // clear device
1135     d->paranoiaLevel = 1; // enable paranoia error correction, but allow skipping
1136 
1137     if (groupCDDA.readEntry("disable_paranoia", false)) {
1138         d->paranoiaLevel = 0; // disable all paranoia error correction
1139     }
1140 
1141     if (groupCDDA.readEntry("never_skip", true)) {
1142         d->paranoiaLevel = 2;
1143         // never skip on errors of the medium, should be default for high quality
1144     }
1145 
1146     d->reportErrors = groupCDDA.readEntry("report_errors", false);
1147 
1148     if (groupCDDA.hasKey("niceLevel")) {
1149         int niceLevel = groupCDDA.readEntry("niceLevel", 0);
1150         if (setpriority(PRIO_PROCESS, getpid(), niceLevel) != 0)
1151             qCDebug(AUDIOCD_KIO_LOG) << "Setting nice level to (" << niceLevel << ") failed.";
1152     }
1153 
1154     // The default track filename template
1155     const KConfigGroup groupFileName(config, QStringLiteral("FileName"));
1156     d->fileNameTemplate = groupFileName.readEntry("file_name_template", "%{trackartist} - %{number} - %{title}");
1157     d->albumNameTemplate = groupFileName.readEntry("album_name_template", "%{albumartist} - %{albumtitle}");
1158     if (groupFileName.readEntry("show_file_location", false))
1159         d->fileLocationTemplate = groupFileName.readEntry("file_location_template", QString());
1160     else
1161         d->fileLocationTemplate = QString();
1162     d->rsearch = groupFileName.readEntry("regexp_search");
1163     d->rreplace = groupFileName.readEntry("regexp_replace");
1164     // if the regular expressions are enclosed in quotes. Remove them
1165     // otherwise it is not possible to search for a space " ", since an empty
1166     // (only spaces) value is not supported by KConfig, so the space has to be
1167     // quoted, but then here the regexp searches really for " " instead of just
1168     // the space. Alex
1169     QRegExp qoutedString(QLatin1String("^\".*\"$"));
1170     if (qoutedString.exactMatch(d->rsearch)) {
1171         d->rsearch = d->rsearch.mid(1, d->rsearch.length() - 2);
1172     }
1173     if (qoutedString.exactMatch(d->rreplace)) {
1174         d->rreplace = d->rreplace.mid(1, d->rreplace.length() - 2);
1175     }
1176 
1177     // Tell the encoders to load their settings
1178     AudioCDEncoder *encoder;
1179     for (int i = encoders.size() - 1; i >= 0; --i) {
1180         encoder = encoders.at(i);
1181         if (encoder->init())
1182             encoder->loadSettings();
1183         else
1184             encoders.removeAt(i);
1185     }
1186 
1187     delete config;
1188 }
1189 
1190 /**
1191  * Generates the track titles from the template using the cddb information.
1192  */
1193 void AudioCDProtocol::generateTemplateTitles()
1194 {
1195     d->templateTitles.clear();
1196     if (d->cddbResult != KCDDB::Success) {
1197         for (unsigned int i = 0; i < d->tracks; i++) {
1198             d->templateTitles.append(i18n("Track %1", QString::asprintf("%02d", i + 1)));
1199         }
1200         return;
1201     }
1202 
1203     KCDDB::CDInfo info = d->cddbBestChoice;
1204     if (d->cddbUserChoice >= 0 && ((d->cddbUserChoice) < d->cddbList.count()))
1205         info = d->cddbList[d->cddbUserChoice];
1206 
1207     // Then generate the templates
1208     d->templateTitles.clear();
1209     for (uint i = 0; i < d->tracks; i++) {
1210         QHash<QString, QString> macros;
1211         macros[QStringLiteral("albumartist")] = info.get(Artist).toString();
1212         macros[QStringLiteral("albumtitle")] = info.get(Title).toString();
1213         macros[QStringLiteral("title")] = info.track(i).get(Title).toString();
1214         macros[QStringLiteral("trackartist")] = info.track(i).get(Artist).toString();
1215         macros[QStringLiteral("number")] = QString::asprintf("%02d", i + 1);
1216         // macros["number"] = QString("%1").arg(i+1, 2, 10);
1217         macros[QStringLiteral("genre")] = info.get(Genre).toString();
1218         macros[QStringLiteral("year")] = info.get(Year).toString();
1219 
1220         QString title = escapePath(KMacroExpander::expandMacros(d->fileNameTemplate, macros, QLatin1Char('%')));
1221         title.replace(QRegularExpression(d->rsearch), d->rreplace);
1222         d->templateTitles.append(title);
1223     }
1224 
1225     QHash<QString, QString> macros;
1226     macros[QStringLiteral("albumartist")] = info.get(Artist).toString();
1227     macros[QStringLiteral("albumtitle")] = info.get(Title).toString();
1228     macros[QStringLiteral("genre")] = info.get(Genre).toString();
1229     macros[QStringLiteral("year")] = info.get(Year).toString();
1230     d->templateAlbumName = escapePath(KMacroExpander::expandMacros(d->albumNameTemplate, macros, QLatin1Char('%')));
1231     d->templateAlbumName.replace(QRegularExpression(d->rsearch), d->rreplace);
1232 
1233     d->templateFileLocation = KMacroExpander::expandMacros(d->fileLocationTemplate, macros, QLatin1Char('%'));
1234 }
1235 
1236 /**
1237  * Based upon the cdparanoia ripping application
1238  * Only output BAD stuff
1239  * The higher the paranoia_read_limited_error the worse the problem is
1240  * FYI: PARANOIA_CB_READ & PARANOIA_CB_VERIFY happen continuously when ripping
1241  */
1242 void paranoiaCallback(long, int function)
1243 {
1244     switch (function) {
1245     case PARANOIA_CB_VERIFY:
1246         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_VERIFY";
1247         break;
1248 
1249     case PARANOIA_CB_READ:
1250         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_READ";
1251         break;
1252 
1253     case PARANOIA_CB_FIXUP_EDGE:
1254         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_EDGE";
1255         paranoia_read_limited_error = 2;
1256         break;
1257 
1258     case PARANOIA_CB_FIXUP_ATOM:
1259         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_ATOM";
1260         paranoia_read_limited_error = 6;
1261         break;
1262 
1263     case PARANOIA_CB_READERR:
1264         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_READERR";
1265         paranoia_read_limited_error = 6;
1266         break;
1267 
1268     case PARANOIA_CB_SKIP:
1269         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_SKIP";
1270         paranoia_read_limited_error = 8;
1271         break;
1272 
1273     case PARANOIA_CB_OVERLAP:
1274         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_OVERLAP";
1275         break;
1276 
1277     case PARANOIA_CB_SCRATCH:
1278         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_SCRATCH";
1279         paranoia_read_limited_error = 7;
1280         break;
1281 
1282     case PARANOIA_CB_DRIFT:
1283         // qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_DRIFT";
1284         paranoia_read_limited_error = 4;
1285         break;
1286 
1287     case PARANOIA_CB_FIXUP_DROPPED:
1288         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_DROPPED";
1289         paranoia_read_limited_error = 5;
1290         break;
1291 
1292     case PARANOIA_CB_FIXUP_DUPED:
1293         qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_DUPED";
1294         paranoia_read_limited_error = 5;
1295         break;
1296     }
1297 }
1298 
1299 // needed for JSON file embedding
1300 #include "audiocd.moc"