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"