Logo Search packages:      
Sourcecode: kdemultimedia version File versions

audiocd.cpp

/*
 * Copyright (C) 2000 Rik Hemsley (rikkus) <rik@kde.org>
 * Copyright (C) 2000, 2001, 2002 Michael Matz <matz@kde.org>
 * Copyright (C) 2001 Carsten Duvenhorst <duvenhorst@m2.uni-hannover.de>
 * Copyright (C) 2001 Adrian Schroeter <adrian@suse.de>
 * Copyright (C) 2003 Richard Lärkäng <richard@goteborg.utfors.se>
 * Copyright (C) 2003 Scott Wheeler <wheeler@kde.org>
 * Copyright (C) 2004 Benjamin Meyer <ben + audiocd at meyerhome dot net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * ERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <config.h>

extern "C"
{
#include <cdda_interface.h>
#include <cdda_paranoia.h>

/**
 * This is in support for the Mega Hack, if cdparanoia ever is fixed, or we
 * use another ripping library we can remove this.
 **/
#ifdef __linux__
#ifdef __KCC
/* KAI C++'s sys/types.h defines _I386_TYPES_H to get rid of non-ANSI things
       from asm/types.h, which also removes __u8,__u16, which are required
       by linux/cdrom.h. The only chance is to undef it again, so it gets
       included. (matz) */
#undef _I386_TYPES_H
/* And <linux/byteorder/swab.h> uses GNU C extensions without providing
       fallbacks for non-GNUisms. Fortunately we (or other headers) don't
       need the swab routines. */
#define _LINUX_BYTEORDER_SWAB_H
#endif
#include <linux/version.h>

#ifndef __GNUC__
#define __GNUC__ 1
#endif
#undef __STRICT_ANSI__
#include <asm/types.h>
#include <linux/cdrom.h>
#endif

#include <sys/ioctl.h>
#if defined(__NetBSD__) || defined(__OpenBSD__)
#include <sys/cdio.h>
#endif

void paranoiaCallback(long, int);
      int kdemain(int argc, char ** argv);
#ifndef CDPARANOIA_STATIC
      int FixupTOC(cdrom_drive *d, int tracks);
#endif

}

#include "audiocd.h"

#include <audiocdencoder.h>

#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>

#include <kmacroexpander.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <kdebug.h>
#include <kprotocolmanager.h>
#include <klocale.h>
#include <qregexp.h>
// CDDB
#include <client.h>

using namespace KIO;
using namespace KCDDB;

#define QFL1(x) QString::fromLatin1(x)
#define DEFAULT_CD_DEVICE "/dev/cdrom"
#define CDDB_INFORMATION "CDDB Information"

int start_of_first_data_as_in_toc;
int hack_track;

/* Only do this if we have shared libcdda_cdparanoia. */
#ifndef CDPARANOIA_STATIC
/* Mega hack.  This function comes from libcdda_interface, and is called by
       it.  We need to override it, so we implement it ourself in the hope, that
       shared lib semantics make the calls in libcdda_interface to FixupTOC end
       up here, instead of it's own copy.  This usually works.
       You don't want to know the reason for this.  (matz) */
int FixupTOC(cdrom_drive *d, int tracks)
{
      int j;
      for (j = 0; j < tracks; j++) {
            if (d->disc_toc[j].dwStartSector < 0)
                  d->disc_toc[j].dwStartSector = 0;
            if (j < tracks-1
                        && d->disc_toc[j].dwStartSector > d->disc_toc[j+1].dwStartSector)
                  d->disc_toc[j].dwStartSector = 0;
      }
      long last = d->disc_toc[0].dwStartSector;
      for (j = 1; j < tracks; j++) {
            if (d->disc_toc[j].dwStartSector < last)
                  d->disc_toc[j].dwStartSector = last;
      }
      start_of_first_data_as_in_toc = -1;
      hack_track = -1;
      if (d->ioctl_fd != -1) {
            int ms_addr;
#ifdef __linux__
            struct cdrom_multisession ms_str;
            ms_str.addr_format = CDROM_LBA;
            if (ioctl(d->ioctl_fd, CDROMMULTISESSION, &ms_str) == -1)
                  return -1;
            ms_addr = ms_str.addr.lba;
#endif
#if defined(__NetBSD__) || defined(__OpenBSD__)
            ms_addr = 0; /* last session */
            if (ioctl(d->ioctl_fd, CDIOREADMSADDR, &ms_addr) == -1)
                  return -1;
#endif

            if (ms_addr > 100) {
                  for (j = tracks-1; j >= 0; j--)
                        if (j > 0 && !IS_AUDIO(d,j) && IS_AUDIO(d,j-1)) {
                              if (d->disc_toc[j].dwStartSector > ms_addr - 11400) {
                                    /* The next two code lines are the purpose of duplicating this
                                     * function, all others are an exact copy of paranoias FixupTOC().
                                     * The gory details: CD-Extra consist of N audio-tracks in the
                                     * first session and one data-track in the next session.  This
                                     * means, the first sector of the data track is not right behind
                                     * the last sector of the last audio track, so all length
                                     * calculation for that last audio track would be wrong.  For this
                                     * the start sector of the data track is adjusted (we don't need
                                     * the real start sector, as we don't rip that track anyway), so
                                     * that the last audio track end in the first session.  All well
                                     * and good so far.  BUT: The CDDB disc-id is based on the real
                                     * TOC entries so this adjustment would result in a wrong Disc-ID.
                                     * We can only solve this conflict, when we save the old
                                     * (toc-based) start sector of the data track.  Of course the
                                     * correct solution would be, to only adjust the _length_ of the
                                     * last audio track, not the start of the next track, but the
                                     * internal structures of cdparanoia are as they are, so the
                                     * length is only implicitely given.  Bloody sh*. */
                                    start_of_first_data_as_in_toc = d->disc_toc[j].dwStartSector;
                                    hack_track = j + 1;
                                    d->disc_toc[j].dwStartSector = ms_addr - 11400;
                              }
                              break;
                        }
                  return 1;
            }
      }
      return 0;
}
#endif

/* libcdda returns for cdda_disc_lastsector() the last sector of the last
       _audio_ track.  How broken. For CDDB Disc-ID we need the real last sector
       to calculate the disc length.  */
long my_last_sector(cdrom_drive *drive)
{
      return cdda_track_lastsector(drive, drive->tracks);
}

/* Stupid CDparanoia returns the first sector of the first _audio_ track
       as first disc sector.  Equally broken to the last sector.  But what is
       even more shitty is, that if it happens that the first audio track is
       the first track at all, it returns a hardcoded _zero_, whatever else
       the TOC told it. This of course happens quite often, as usually the first
       track is audio, if there's audio at all. And usually it even works,
       because most of the time the real TOC offset is 150 frames, which we
       accounted for in our code. This is so unbelievable ugly. */
long my_first_sector(cdrom_drive *drive)
{
      return cdda_track_firstsector(drive, 1);
}

using namespace AudioCD;

int
kdemain(int argc, char ** argv)
{
      // KApplication is used as libkcddb uses ioslaves which need a valid
      // kapp pointer
      KApplication app(argc, argv, "kio_audiocd", false, true);

      kdDebug(7117) << "Starting " << getpid() << endl;

      if (argc != 4)
      {
             kdError(7117) << "Usage: kio_audiocd protocol domain-socket1 domain-socket2" << endl;
             exit(-1);
      }

      AudioCDProtocol slave(argv[2], argv[3]);
      slave.dispatchLoop();

      kdDebug(7117) << "Done" << endl;
      return 0;
}

enum Which_dir {
      Unknown = 0, // Error
      Info, // CDDB info
      Root, // The root directory, shows all these :)
      FullCD, // Show a single file containing all of the data
      EncoderDir // A directory created by an encoder
};

class AudioCDProtocol::Private {
public:
      Private() {
            clearURLargs();
            discid = "";
            s_info = i18n("Information");
            s_fullCD = i18n("Full CD");
      }

      void clearURLargs() {
            req_allTracks = false;
            which_dir = Unknown;
            req_track = -1;
            cddbUserChoice = -1;
      }

      // The type/which of request
      bool req_allTracks;
      Which_dir which_dir;
      int req_track;
      QString fname;
      AudioCDEncoder *encoder_dir_type;

      // Misc settings
      QString device; // URL settable
      int paranoiaLevel; // URL settable

      // Directory strings, never change after init
      QString s_info;
      QString s_fullCD;

      // Current CD
      QString discid;
      uint tracks;
      bool trackIsAudio[100];

      // CDDB items
      KCDDB::CDDB::Result cddbResult;
      CDInfoList cddbList;
      int cddbUserChoice; // URL settable
      KCDDB::CDInfo cddbBestChoice;

      // Template for ..
      QString fileNameTemplate; // URL settable
      QString albumTemplate; // URL settable
      QString rsearch;
      QString rreplace;

      // Current strings for this CD and or cddb selection
      QString cd_title;
      QString cd_artist;
      QString cd_category;
      int cd_year;
      QStringList templateTitles;
      QString templateAlbumName;

      // The current cddb title strings for id3
      QStringList track_titles;
};

int paranoia_read_limited_error;


AudioCDProtocol::AudioCDProtocol (const QCString & pool, const QCString & app)
      : SlaveBase("audiocd", pool, app)
{
      d = new Private;

      // Add encoders
      AudioCDEncoder::find_all_plugins(this, encoders);
      encoderTypeCDA = encoderFromExtension(".cda");
      encoderTypeWAV = encoderFromExtension(".wav");
      encoders.setAutoDelete(true);
}

AudioCDProtocol::~AudioCDProtocol()
{
      delete d;
}

AudioCDEncoder *AudioCDProtocol::encoderFromExtension(const QString& extension)
{
      AudioCDEncoder *encoder;
      for ( encoder = encoders.first(); encoder; encoder = encoders.next() ){
            if(QString(".")+encoder->fileType() == extension)
                  return encoder;
      }
      Q_ASSERT(false);
      return NULL;
}

AudioCDEncoder *AudioCDProtocol::determineEncoder(const QString & filename)
{
      int len = filename.length();
      int pos = filename.findRev('.');
      return encoderFromExtension(filename.right(len - pos));
}

struct cdrom_drive * AudioCDProtocol::initRequest(const KURL & url)
{
      if (url.hasHost())
      {
            error(KIO::ERR_UNSUPPORTED_ACTION,
            i18n("You cannot specify a host with this protocol. "
                         "Please use the audiocd:/ format instead."));
            return 0;
      }

      // Tell encoders to read their settings.
      AudioCDEncoder *encoder;
      for ( encoder = encoders.first(); encoder; encoder = encoders.next() )
            encoder->init();

      // Load OUR Settings.
      loadSettings();

      // Then url parameters can overrule our settings.
      parseURLArgs(url);

      struct cdrom_drive * drive = pickDrive();

      if (0 == drive)
            return 0;

      if (0 != cdda_open(drive))
      {
            kdDebug(7117) << "cdda_open failed" << endl;

            error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.path());
            return 0;
      }

      updateCD(drive);

      //
      // Determine what file or folder that is wanted.
      //

      d->fname = url.filename(false);
      QString dname = url.directory(true, false);
      if (!dname.isEmpty() && dname[0] == '/')
            dname = dname.mid(1);

      // Kong bug where they send dirs as files, double check
      /* A hack, for when konqi wants to list the directory audiocd:/Bla
             it really submits this URL, instead of audiocd:/Bla/ to us. We could
             send (in listDir) the UDS_NAME as "Bla/" for directories, but then
             konqi shows them as "Bla//" in the status line. */
      // See if it is an encoder directory
      for ( encoder = encoders.first(); encoder; encoder = encoders.next() ){
            if(encoder->type() == d->fname){
                  dname = d->fname;
                  d->fname = "";
                  break;
            }
      }
      // Other Hard coded directories
      if (dname.isEmpty() &&
                  (d->fname == d->s_info || d->fname == QFL1("Information") ||
                   d->fname == d->s_fullCD ))
            {
                  dname = d->fname;
                  d->fname = "";
            }
      // end hack


      // See which directory they want
      d->which_dir = Unknown;
      for ( encoder = encoders.first(); encoder; encoder = encoders.next() ){
            if(encoder->type() == dname){
                  d->which_dir = EncoderDir;
                  d->encoder_dir_type = encoder;
                  break;
            }
      }
      if ( Unknown == d->which_dir ){
            if (dname.isEmpty())
                  d->which_dir = Root;
            else if (dname == d->s_info || dname == QFL1("Information"))
                  d->which_dir = Info;
            else if (dname == d->s_fullCD || dname == QFL1("Full CD"))
                  d->which_dir = FullCD;
      }

      // See if the url is a track
      d->req_track = -1;
      if (!d->fname.isEmpty()){
            QString name(d->fname);

            // Remove extension
            int dot = name.findRev('.');
            if (dot >= 0)
                  name.truncate(dot);

            // See if it matches a cddb title
            uint trackNumber;
            for (trackNumber = 0; trackNumber < d->tracks; trackNumber++){
                  if (d->templateTitles[trackNumber] == name)
                        break;
            }
            if (trackNumber < d->tracks)
                  d->req_track = trackNumber;
            else {
                  /* Not found in title list. Try hard to find a number in the
                         string. */
                  unsigned int start = 0;
                  unsigned int end = 0;
                  // Find where the numbers start
                  while (start < name.length()){
                        if (name[start++].isDigit())
                              break;
                  }
                  // Find where the numbers end
                  for (end = start; end < name.length(); end++)
                        if (!name[end].isDigit())
                              break;
                  if (start < name.length()){
                        bool ok;
                        // The external representation counts from 1 so subtrac 1.
                        d->req_track = name.mid(start-1, end - start+2).toInt(&ok) - 1;
                        if (!ok)
                              d->req_track = -1;
                  }
            }
      }
      if (d->req_track >= (int)d->tracks)
            d->req_track = -1;

      // Are we in the directory that lists "full CD" files?
      d->req_allTracks = (dname.contains(d->s_fullCD) || dname.contains(QFL1("Full CD")));

      kdDebug(7117) << "dir=" << dname << " file=" << d->fname
            << " req_track=" << d->req_track << " which_dir=" << d->which_dir << " full CD?=" << d->req_allTracks << endl;
      return drive;
}

00465 bool AudioCDProtocol::getSectorsForRequest(struct cdrom_drive * drive, long & firstSector, long & lastSector) const
{
      if (d->req_allTracks)
      { // we rip all the tracks of the CD
            firstSector = cdda_track_firstsector(drive, 1);
            lastSector  = cdda_track_lastsector(drive, cdda_tracks(drive));
      }
      else
      { // we only rip the selected track
            int trackNumber = d->req_track + 1;

            if (trackNumber <= 0 || trackNumber > cdda_tracks(drive))
                  return false;
            firstSector = cdda_track_firstsector(drive, trackNumber);
            lastSector = cdda_track_lastsector(drive, trackNumber);
      }
      return true;
}

void AudioCDProtocol::get(const KURL & url)
{
      struct cdrom_drive * drive = initRequest(url);
      if (!drive)
            return;

      if( d->fname.contains(i18n(CDDB_INFORMATION))){
            uint choice = 1;
            if(d->fname != QString("%1.txt").arg(i18n(CDDB_INFORMATION))){
                  choice= d->fname.section('_',1,1).section('.',0,0).toInt();
            }
            uint count = 1;
            CDInfoList::iterator it;
            bool found = false;
            for ( it = d->cddbList.begin(); it != d->cddbList.end(); ++it ){
                  if(count == choice){
                        mimeType("text/html");
                        data(QCString( (*it).toString().latin1() ));
                        // send an empty QByteArray to signal end of data.
                        data(QByteArray());
                        finished();
                        found = true;
                        break;
                  }
                  count++;
            }
            if(!found && d->fname.contains(i18n(CDDB_INFORMATION)+":")){
                  mimeType("text/html");
                  //data(QCString( d->fname.latin1() ));
                  // send an empty QByteArray to signal end of data.
                  data(QByteArray());
                  finished();
                  found = true;
            }
            if( !found )
                  error(KIO::ERR_DOES_NOT_EXIST, url.path());
            cdda_close(drive);
            return;
      }

      long firstSector, lastSector;
      if (!getSectorsForRequest(drive, firstSector, lastSector))
      {
            error(KIO::ERR_DOES_NOT_EXIST, url.path());
            cdda_close(drive);
            return;
      }

      AudioCDEncoder *encoder = determineEncoder(d->fname);
      if(!encoder){
       cdda_close(drive);
       return;
      }

      if(d->cddbResult == KCDDB::CDDB::Success){
            QString trackName;
            // do we rip the whole CD?
            if (d->req_allTracks)
                  // YES => the title of the file is the title of the CD
                  trackName = d->cd_title.utf8().data();
            else
                  // Passing the actual track title and not the template to the encoder for id3 type info
                  // NO => title of the track.
                  trackName = d->track_titles[d->req_track];

            encoder->fillSongInfo(trackName, d->cd_artist, d->cd_title, d->cd_category, d->req_track+1, d->cd_year);
      }
      long totalByteCount = CD_FRAMESIZE_RAW * (lastSector - firstSector + 1);
      long time_secs = (8 * totalByteCount) / (44100 * 2 * 16);

      unsigned long size = encoder->size(time_secs);
      totalSize(size);
      emit mimeType(QFL1(encoder->mimeType()));

      // Read data (track/disk) from the cd
      paranoiaRead(drive, firstSector, lastSector, encoder, url.fileName(), size);

      // send an empty QByteArray to signal end of data.
      data(QByteArray());

      cdda_close(drive);

      finished();
}

void AudioCDProtocol::stat(const KURL & url)
{
      struct cdrom_drive * drive = initRequest(url);
      if (!drive)
            return;

      bool isFile = !d->fname.isEmpty();

      // the track number. 0 if ripping
      // the whole CD.
      uint trackNumber = d->req_track + 1;

      if (!d->req_allTracks)
      { // we only want to rip one track.
            // does this track exist?
            if (isFile && (trackNumber < 1 || trackNumber > d->tracks))
            {
                  error(KIO::ERR_DOES_NOT_EXIST, url.path());
                  return;
            }
      }

      UDSEntry entry;

      UDSAtom atom;
      atom.m_uds = KIO::UDS_NAME;
      atom.m_str = url.fileName().replace('/', QFL1("%2F"));
      kdDebug(7117) << k_funcinfo << atom.m_str << endl;
      entry.append(atom);

      atom.m_uds = KIO::UDS_FILE_TYPE;
      atom.m_long = isFile ? S_IFREG : S_IFDIR;
      entry.append(atom);

      const mode_t _umask = ::umask(0);
      ::umask(_umask);

      atom.m_uds = KIO::UDS_ACCESS;
      atom.m_long = 0666 & (~_umask);
      entry.append(atom);

      atom.m_uds = KIO::UDS_SIZE;
      if (!isFile)
      {
            atom.m_long = cdda_tracks(drive);
      }
      else
      {
                  AudioCDEncoder *encoder = determineEncoder(d->fname);
                  long firstSector, lastSector;
                  getSectorsForRequest(drive, firstSector, lastSector);
                  atom.m_long = fileSize(firstSector, lastSector, encoder);
      }

      entry.append(atom);

      statEntry(entry);

      cdda_close(drive);

      finished();
}

void AudioCDProtocol::updateCD(struct cdrom_drive * drive)
{
      d->tracks = cdda_tracks(drive);

      KCDDB::TrackOffsetList qvl;

      for(uint i=0; i< d->tracks; i++){
                  d->trackIsAudio[i] = cdda_track_audiop(drive, i + 1);
                  // Does this ever happen??
                  // kdDebug(7117) << "Hack track: " << hack_track << endl;
                  if (((int)i+1) != hack_track)
                        qvl.append(cdda_track_firstsector(drive, i + 1) + 150);
                  else
                        qvl.append(start_of_first_data_as_in_toc + 150);
      }

      qvl.append(my_first_sector(drive)+150);
      qvl.append(my_last_sector(drive)+150);

      KCDDB::CDDB cddb;
      QString id = cddb.trackOffsetListToId(qvl);
      if (id == d->discid){
            return;
      }
      d->discid = id;
      d->templateTitles.clear();
      d->track_titles.clear();
      KCDDB::Client c;

      d->cddbResult = c.lookup(qvl);

      if (d->cddbResult == KCDDB::CDDB::Success)
      {
            d->cddbList = c.lookupResponse();
            d->cddbBestChoice = c.bestLookupResponse();
            generateTemplateTitles();
            return;
      }

      for (uint i = 1; i <= d->tracks; i++)
      {
            QString s;
            QString num;
            num.sprintf("%02d", i);
            if (cdda_track_audiop(drive, i))
                  s = i18n("Track %1").arg(num);
            else
                  s.sprintf("data%02d", i);
            d->templateTitles.append( s );

            // keep both lists in sync
            d->track_titles.append( QString::null );
      }
}

static void app_entry(UDSEntry& e, unsigned int uds, const QString& str)
{
      UDSAtom a;
      a.m_uds = uds;
      a.m_str = str;
      e.append(a);
}

static void app_entry(UDSEntry& e, unsigned int uds, long l)
{
      UDSAtom a;
      a.m_uds = uds;
      a.m_long = l;
      e.append(a);
}

static void app_dir(UDSEntry& e, const QString & n, size_t s)
{
      e.clear();
      app_entry(e, KIO::UDS_NAME, QFile::decodeName(n.local8Bit()));
      app_entry(e, KIO::UDS_FILE_TYPE, S_IFDIR);
      app_entry(e, KIO::UDS_ACCESS, 0400);
      app_entry(e, KIO::UDS_SIZE, s);
}

static void app_file(UDSEntry& e, const QString & n, size_t s)
{
      e.clear();
      app_entry(e, KIO::UDS_NAME, QFile::decodeName(n.local8Bit()));
      app_entry(e, KIO::UDS_FILE_TYPE, S_IFREG);
      app_entry(e, KIO::UDS_ACCESS, 0400);
      app_entry(e, KIO::UDS_SIZE, s);
}

void AudioCDProtocol::listDir(const KURL & url)
{
      struct cdrom_drive * drive = initRequest(url);

      // Some error checking before proceeding
      if (!drive)
            return;

      if (d->which_dir == Unknown){
            error(KIO::ERR_DOES_NOT_EXIST, url.path());
            cdda_close(drive);
            return;
      }

      if ( !d->fname.isEmpty() ){
            error(KIO::ERR_IS_FILE, url.path());
            cdda_close(drive);
            return;
      }

      // Generate templated names every time
      // because the template might have changed.
      generateTemplateTitles();

      UDSEntry entry;
      // If the tracks should be listed in this directory
      bool list_tracks = true;

      if (d->which_dir == Info){
            CDInfoList::iterator it;
            uint count = 1;
            for ( it = d->cddbList.begin(); it != d->cddbList.end(); ++it ){
                  (*it).toString();
                  if(count == 1)
                        app_file(entry, QString("%1.txt").arg(i18n(CDDB_INFORMATION)), ((*it).toString().length())+1);
                  else
                        app_file(entry, QString("%1_%2.txt").arg(i18n(CDDB_INFORMATION)).arg(count), ((*it).toString().length())+1);
                  count++;
                  listEntry(entry, false);
            }
            // Error
            if( count == 1 ) {
                  app_file(entry, QString("%1: %2.txt").arg(i18n(CDDB_INFORMATION)).arg(CDDB::resultToString(d->cddbResult)), ((*it).toString().length())+1);
                  count++;
                  listEntry(entry, false);
            }

            list_tracks = false;
      }

      if (d->which_dir == Root){
            // List virtual directories.
            app_dir(entry, d->s_fullCD, encoders.count());
            listEntry(entry, false);

            // Either >0 cddb results or cddb error file
            app_dir(entry, d->s_info, d->cddbList.count());
            listEntry(entry, false);

            // List the encoders
            AudioCDEncoder *encoder;
            for ( encoder = encoders.first(); encoder; encoder = encoders.next() ){
                  // Skip the directory that is in the root (you can still go in it, just don't show it)
                  if( encoder == encoderTypeWAV )
                        continue;
                  app_dir(entry, encoder->type(), d->tracks);
                  listEntry(entry, false);
            }
      }

      // Now fill in the tracks for the current directory

      if (list_tracks && d->which_dir == FullCD) {
            // if we're listing the "full CD" subdirectory :
            if ( (d->which_dir == FullCD) ) {
                  AudioCDEncoder *encoder;
                  for ( encoder = encoders.first(); encoder; encoder = encoders.next() ){
                        if (d->cddbResult != KCDDB::CDDB::Success)
                              addEntry(QFL1("Full CD"), encoder, drive, -1);
                        else
                              addEntry(d->templateAlbumName, encoder, drive, -1);
                  }
            }
      }

      if (list_tracks && d->which_dir != FullCD) {
            // listing another dir than the "FullCD" one.
            for (uint trackNumber = 1; trackNumber <= d->tracks; trackNumber++)
            {
                  // Skip data tracks
                  if (!d->trackIsAudio[trackNumber-1])
                        continue;

                  switch (d->which_dir) {
                        case Root:{
                              addEntry(d->templateTitles[trackNumber - 1],
                                    encoderTypeWAV, drive, trackNumber);
                              break;
                        }
                        case EncoderDir:
                              addEntry(d->templateTitles[trackNumber - 1],
                                    d->encoder_dir_type, drive, trackNumber);
                              break;
                        case Info:
                        case Unknown:
                        default:
                              error(KIO::ERR_INTERNAL, url.path());
                              cdda_close(drive);
                              return;
                  }
            }
      }

      totalSize(entry.count());
      listEntry(entry, true);
      cdda_close(drive);
      finished();
}

00840 void AudioCDProtocol::addEntry(const QString& trackTitle, AudioCDEncoder *encoder, struct cdrom_drive * drive, int trackNo)
{
      if(!encoder || !drive)
            return;

      long theFileSize = 0;
      if (trackNo == -1)
      { // adding entry for the full CD
            theFileSize = fileSize(cdda_track_firstsector(drive, 1),
                         cdda_track_lastsector(drive, cdda_tracks(drive)),
                         encoder);
      }
      else
      { // adding one regular track
            long firstSector = cdda_track_firstsector(drive, trackNo);
            long lastSector = cdda_track_lastsector(drive, trackNo);
            theFileSize = fileSize(firstSector, lastSector, encoder);
      }
      UDSEntry entry;
      app_file(entry, trackTitle + QString(".")+encoder->fileType(), theFileSize);
      listEntry(entry, false);
}

00863 long AudioCDProtocol::fileSize(long firstSector, long lastSector, AudioCDEncoder *encoder)
{
      if(!encoder)
            return 0;

      long filesize = CD_FRAMESIZE_RAW * ( lastSector - firstSector + 1 );
      long length_seconds = (filesize) / 176400;

      return encoder->size(length_seconds);
}

struct cdrom_drive *AudioCDProtocol::pickDrive()
{
      QCString device(QFile::encodeName(d->device));

      struct cdrom_drive * drive = 0;

      if (!device.isEmpty() && device != "/")
            drive = cdda_identify(device, CDDA_MESSAGE_PRINTIT, 0);
      else
      {
            drive = cdda_find_a_cdrom(CDDA_MESSAGE_PRINTIT, 0);

            if (0 == drive)
            {
                  if (QFile(QFile::decodeName(DEFAULT_CD_DEVICE)).exists())
                        drive = cdda_identify(DEFAULT_CD_DEVICE, CDDA_MESSAGE_PRINTIT, 0);
            }
      }

      if (0 == drive) {
            kdDebug(7117) << "Can't find an audio CD" << endl;

            if(!QFile::exists(d->device))
                  error(KIO::ERR_DOES_NOT_EXIST, d->device);
            else {
                  QFileInfo fi(d->device);
                  if(!fi.isReadable())
                        error(KIO::ERR_CANNOT_OPEN_FOR_READING, d->device);
                  else if(!fi.isWritable())
                        error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, d->device);
                  else error(KIO::ERR_UNKNOWN, d->device);
            }
      }

      return drive;
}

00911 void AudioCDProtocol::paranoiaRead(
            struct cdrom_drive * drive,
            long firstSector,
            long lastSector,
            AudioCDEncoder* encoder,
            const QString& fileName,
            unsigned long size
      )
{
      if(!encoder || !drive)
            return;

      cdrom_paranoia * paranoia = paranoia_init(drive);
      if (0 == paranoia) {
            kdDebug(7117) << "paranoia_init failed" << endl;
            return;
      }

      int paranoiaLevel = PARANOIA_MODE_FULL ^ PARANOIA_MODE_NEVERSKIP;
      switch (d->paranoiaLevel)
      {
            case 0:
                  paranoiaLevel = PARANOIA_MODE_DISABLE;
                  break;

            case 1:
                  paranoiaLevel |= PARANOIA_MODE_OVERLAP;
                  paranoiaLevel &= ~PARANOIA_MODE_VERIFY;
                  break;

            case 2:
                  paranoiaLevel |= PARANOIA_MODE_NEVERSKIP;
            default:
                  break;
      }

      paranoia_modeset(paranoia, paranoiaLevel);

      cdda_verbose_set(drive, CDDA_MESSAGE_PRINTIT, CDDA_MESSAGE_PRINTIT);

      paranoia_seek(paranoia, firstSector, SEEK_SET);

      long currentSector(firstSector);

      unsigned long processed = encoder->readInit(CD_FRAMESIZE_RAW * (lastSector - firstSector + 1));
      // TODO test for errors (processed<0)?
      processedSize(processed);
      bool ok = true;

      unsigned long lastSize = size;
      unsigned long diff = 0;

      paranoia_read_limited_error = 0;
      int warned = 0;
      while (currentSector <= lastSector)
      {
            // TODO make the 5 configurable? The default in the lib is 20 fyi
            int16_t * buf = paranoia_read_limited(paranoia, paranoiaCallback, 5);
            if( warned == 0 && paranoia_read_limited_error >= 5 ){
#if KDE_IS_VERSION (3, 2, 89)
                  warning(i18n("AudioCD: Disk damage detected on this track, risk of data corruption."));
#endif
                  warned = 1;
            }
            if (0 == buf) {
                  kdDebug(7117) << "Unrecoverable error in paranoia_read" << endl;
                  ok = false;
                  error( ERR_SLAVE_DEFINED, i18n( "Error reading audio data for %1 from the CD" ).arg( fileName ) );
                  break;
            }

            ++currentSector;

            int encoderProcessed = encoder->read(buf, CD_FRAMESAMPLES);
            if(encoderProcessed == -1){
                  kdDebug(7117) << "Encoder processing error, stopping." << endl;
                  ok = false;
                  error( ERR_SLAVE_DEFINED, i18n( "Couldn't read %1: encoding failed" ).arg( fileName ) );
                  break;
            }
            processed += encoderProcessed;

            /**
             * Because compression size is so 'unknown' use some guesswork
             *
             * 1) First assume that the reported size is correct and
             * only change the totalSize if the guess it outside a range of %5.
             * 2) Only increase in size unless the decrease is %5 of last estimate.
             * This prevents continues small changes which is just annoying.
             */
            unsigned long end = lastSector - firstSector;
            unsigned long cur = currentSector - firstSector;
            unsigned long estSize = (processed / cur ) * end;

            // If our guess is within 5% of reported
            // size then use the reported size.
            unsigned long guess = (long)((100/(float)size)*estSize);
            if((guess > 97 && guess < 103) || estSize == 0){
                  if(processed > lastSize){
                        totalSize(processed+1);
                        lastSize = processed;
                  }
            }
            else{
                  float percentDone = ((float)cur/(float)end);
                  // Calculate estimated amount that will be wrong
                  diff = estSize - lastSize;
                  diff = (diff*(unsigned long)((100/(float)end)*(end-cur)))/2;
                  // Need 1% of data calculated as initial buffer, use %2 to be safe
                  if( percentDone < .02 ){
                        //qDebug("val: %f, diff: %ld", ((float)cur/(float)end), diff);
                        diff = 0;
                  }

                  // We are growing larger, increase total.
                  if(lastSize < estSize){
                        //qDebug("lastGuess: %ld, guess: %ld diff: %ld", lastSize, estSize, diff);
                        totalSize(estSize+diff);
                        lastSize = estSize+diff;
                  }
                  else{
                        int margin = (int)((percentDone)*75);
                        // Don't bother really trying until almost half way done.
                        if( percentDone <= .40 )
                              margin = 7;
                        unsigned long low = lastSize - lastSize/margin;
                        if(estSize < low){
                              //qDebug("low: %ld, estSize: %ld, num: %i", low, estSize, num);
                              totalSize( estSize );
                              lastSize = estSize;
                        }
                  }
            }
            /**
             * End estimation.
             */

            processedSize(processed);
      }

      if(processed > size)
            totalSize(processed);

      long encoderProcessed = encoder->readCleanup();
      if ( encoderProcessed >= 0 ) {
            processed += encoderProcessed;
            if(processed > size)
                  totalSize(processed);
            processedSize(processed);
      }
      else if ( ok ) // i.e. no error message already emitted
            error( ERR_SLAVE_DEFINED, i18n( "Couldn't read %1: encoding failed" ).arg( fileName ) );

      paranoia_free(paranoia);
      paranoia = 0;
}

/**
 * Read the settings from the URL
 * @see loadSettings()
 */
01072 void AudioCDProtocol::parseURLArgs(const KURL & url)
{
      d->clearURLargs();

      QString query(KURL::decode_string(url.query()));

      if (query.isEmpty() || query[0] != '?')
            return;

      query = query.mid(1); // Strip leading '?'.

      QStringList tokens(QStringList::split('&', query));

      for (QStringList::ConstIterator it(tokens.begin()); it != tokens.end(); ++it)
      {
            QString token(*it);

            int equalsPos(token.find('='));
            if (-1 == equalsPos)
                  continue;

            QString attribute(token.left(equalsPos));
            QString value(token.mid(equalsPos + 1));

            if (attribute == QFL1("device"))
                  d->device = value;
            else if (attribute == QFL1("paranoia_level"))
                  d->paranoiaLevel = value.toInt();
            else if (attribute == QFL1("fileNameTemplate"))
                  d->fileNameTemplate = value;
            else if (attribute == QFL1("albumNameTemplate"))
                  d->albumTemplate = value;
            else if (attribute == QFL1("cddbChoice"))
                  d->cddbUserChoice = value.toInt();
            else if (attribute == QFL1("niceLevel")){
                  int niceLevel = value.toInt();
                  if(setpriority(PRIO_PROCESS, getpid(), niceLevel) != 0)
                        kdDebug(7117) << "Setting nice level to (" << niceLevel << ") failed." << endl;
            }
      }
}

/**
 * Read the settings set by the kcm modules
 * @see parseURLArgs()
 */
01118 void AudioCDProtocol::loadSettings()
{
      KConfig *config = new KConfig(QFL1("kcmaudiocdrc"));

      config->setGroup(QFL1("CDDA"));

      if (!config->readBoolEntry(QFL1("autosearch"),true)) {
            d->device = config->readEntry(QFL1("device"),QFL1(DEFAULT_CD_DEVICE));
      }

      d->paranoiaLevel = 1; // enable paranoia error correction, but allow skipping

      if (config->readBoolEntry("disable_paranoia",false)) {
            d->paranoiaLevel = 0; // disable all paranoia error correction
      }

      if (config->readBoolEntry("never_skip",true)) {
            d->paranoiaLevel = 2;
            // never skip on errors of the medium, should be default for high quality
      }

      if(config->hasKey("niceLevel")) {
            int niceLevel = config->readNumEntry("niceLevel", 0);
            if(setpriority(PRIO_PROCESS, getpid(), niceLevel) != 0)
                  kdDebug(7117) << "Setting nice level to (" << niceLevel << ") failed." << endl;
      }

      // The default track filename template
      config->setGroup("FileName");
      d->fileNameTemplate = config->readEntry("file_name_template", "%{albumartist} - %{number} - %{title}");
      d->albumTemplate = config->readEntry("album_template", "%{albumartist} - %{albumtitle}");
      d->rsearch = config->readEntry("regexp_search");
      d->rreplace = config->readEntry("regexp_replace");

      // Tell the encoders to load their settings
      AudioCDEncoder *encoder;
      for ( encoder = encoders.first(); encoder; encoder = encoders.next() ){
            encoder->loadSettings();
      }

      delete config;
}

/**
 * Generates the track titles from the template using the cddb information.
 */
01164 void AudioCDProtocol::generateTemplateTitles()
{
      if (d->cddbResult != KCDDB::CDDB::Success)
            return;

      KCDDB::CDInfo info = d->cddbBestChoice;
      if(d->cddbUserChoice >= 0 && (((uint)d->cddbUserChoice) < d->cddbList.count()))
            info = d->cddbList[d->cddbUserChoice];

      // First save it for the encoders
      // Might it be easier to just save the whole thing?
      d->cd_title = info.title;
      d->cd_artist = info.artist;
      d->cd_category = info.genre;
      d->cd_year = info.year;
      KCDDB::TrackInfoList t = info.trackInfoList;
      for (uint i = 0; i < t.count(); i++)
            d->track_titles.append(t[i].title);

      // Then generate the templates
      d->templateTitles.clear();
      for (uint i = 0; i < d->tracks; i++) {
            QMap<QString, QString> macros;
            macros["albumartist"] = d->cd_artist;
            macros["albumtitle"] = d->cd_title;
            macros["title"] = d->track_titles[i];
            QString n;
            macros["number"] = n.sprintf("%02d", i + 1);
            macros["genre"] = d->cd_category;
            macros["year"] = QString::number(d->cd_year);

            QString title = KMacroExpander::expandMacros(d->fileNameTemplate, macros, '%').replace('/', QFL1("%2F"));
            title.replace( QRegExp(d->rsearch), d->rreplace );
            d->templateTitles.append(title);
      }

      QMap<QString, QString> macros;
      macros["albumartist"] = d->cd_artist;
      macros["albumtitle"] = d->cd_title;
      macros["genre"] = d->cd_category;
      macros["year"] = QString::number(d->cd_year);
      d->templateAlbumName = KMacroExpander::expandMacros(d->albumTemplate, macros, '%').replace('/', QFL1("%2F"));
      d->templateAlbumName.replace( QRegExp(d->rsearch), d->rreplace );
}

/**
 * Based upon the cdparinoia ripping application
 * Only output BAD stuff
 * The higher the paranoia_read_limited_error the worse the problem is
 * FYI: PARANOIA_CB_READ & PARANOIA_CB_VERIFY happen continusly when ripping
 */
void paranoiaCallback(long, int function)
{
      switch(function){
            case PARANOIA_CB_VERIFY:
                  //kdDebug(7117) << "PARANOIA_CB_VERIFY" << endl;
                  break;

            case PARANOIA_CB_READ:
                  //kdDebug(7117) << "PARANOIA_CB_READ" << endl;
                  break;

            case PARANOIA_CB_FIXUP_EDGE:
                  //kdDebug(7117) << "PARANOIA_CB_FIXUP_EDGE" << endl;
                  paranoia_read_limited_error = 2;
                  break;

            case PARANOIA_CB_FIXUP_ATOM:
                  //kdDebug(7117) << "PARANOIA_CB_FIXUP_ATOM" << endl;
                  paranoia_read_limited_error = 6;
                  break;

            case PARANOIA_CB_READERR:
                  kdDebug(7117) << "PARANOIA_CB_READERR" << endl;
                  paranoia_read_limited_error = 6;
                  break;

            case PARANOIA_CB_SKIP:
                  kdDebug(7117) << "PARANOIA_CB_SKIP" << endl;
                  paranoia_read_limited_error = 8;
                  break;

            case PARANOIA_CB_OVERLAP:
                  //kdDebug(7117) << "PARANOIA_CB_OVERLAP" << endl;
                  break;

            case PARANOIA_CB_SCRATCH:
                  kdDebug(7117) << "PARANOIA_CB_SCRATCH" << endl;
                  paranoia_read_limited_error = 7;
                  break;

            case PARANOIA_CB_DRIFT:
                  //kdDebug(7117) << "PARANOIA_CB_DRIFT" << endl;
                  paranoia_read_limited_error = 4;
                  break;

            case PARANOIA_CB_FIXUP_DROPPED:
                  kdDebug(7117) << "PARANOIA_CB_FIXUP_DROPPED" << endl;
                  paranoia_read_limited_error = 5;
                  break;

            case PARANOIA_CB_FIXUP_DUPED:
                  kdDebug(7117) << "PARANOIA_CB_FIXUP_DUPED" << endl;
                  paranoia_read_limited_error = 5;
                  break;
      }
}


Generated by  Doxygen 1.6.0   Back to index