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