/*
 *
 * $Id: k3biso9660.cpp 690529 2007-07-21 10:51:47Z trueg $
 * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org>
 *
 * This file is part of the K3b project.
 * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * 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.
 * See the file "COPYING" for the exact licensing terms.
 */

#include <config.h>
#include <k3bglobals.h>

#include "k3biso9660.h"
#include "k3biso9660backend.h"

#include <k3bdevice.h>

#include "libisofs/isofs.h"

#include <tqcstring.h>
#include <tqdir.h>
#include <tqfile.h>
#include <tqptrlist.h>

#include <kdebug.h>


/* callback function for libisofs */
int K3bIso9660::read_callback( char* buf, sector_t start, long long len, void* udata )
{
  K3bIso9660* isoF = static_cast<K3bIso9660*>(udata);

  return isoF->read( start, buf, len );
}

/* callback function for libisofs */
int K3bIso9660::isofs_callback( struct iso_directory_record *idr, void *udata )
{
  K3bIso9660 *iso = static_cast<K3bIso9660*> (udata);
  TQString path, isoPath,user,group,symlink;
  int i;
  int access;
  int time,cdate,adate;
  rr_entry rr;
  bool special=false;
  K3bIso9660Entry *entry=0;
  //K3bIso9660Entry *oldentry=0;
  char z_algo[2],z_params[2];
  int z_size=0;

   if (isonum_711(idr->name_len)==1) {
     switch (idr->name[0]) {
     case 0:
      path+=(".");
      special=true;
      break;
     case 1:
      path+=("..");
      special=true;
      break;
    }
  }
  //
  // First extract the raw iso9660 name
  //
  if( !special ) {
    for( i = 0; i < isonum_711( idr->name_len ); i++ ) {
      if( idr->name[i] )
	isoPath += idr->name[i];
      }
  }
  else
    isoPath = path;

  //
  // Now see if we have RockRidge
  //
  if( !iso->plainIso9660() && ParseRR(idr,&rr) > 0 ) {
    iso->m_rr = true;
    if (!special)
      path = TQString::fromLocal8Bit( rr.name );
    symlink=rr.sl;
    access=rr.mode;
    time=0;//rr.st_mtime;
    adate=0;//rr.st_atime;
    cdate=0;//rr.st_ctime;
    user.setNum(rr.uid);
    group.setNum(rr.gid);
    z_algo[0]=rr.z_algo[0];z_algo[1]=rr.z_algo[1];
    z_params[0]=rr.z_params[0];z_params[1]=rr.z_params[1];
    z_size=rr.z_size;
  }
  else {
    access=iso->dirent->permissions() & ~S_IFMT;
    adate=cdate=time=isodate_915(idr->date,0);
    user=iso->dirent->user();
    group=iso->dirent->group();
    if (idr->flags[0] & 2) access |= S_IFDIR; else access |= S_IFREG;
    if (!special) {
      if( !iso->plainIso9660() && iso->jolietLevel() ) {
	for (i=0;i<(isonum_711(idr->name_len)-1);i+=2) {
	  TQChar ch( be2me_16(*((ushort*)&(idr->name[i]))) );
	  if (ch==';') break;
	  path+=ch;
	}
      }
      else {
	// no RR, no Joliet, just plain iso9660
	path = isoPath;

	// remove the version field
	int pos = path.find( ';' );
	if( pos > 0 )
	  path.truncate( pos );
      }
      if (path.endsWith(".")) path.setLength(path.length()-1);
    }
  }

  if( !iso->plainIso9660() )
    FreeRR(&rr);

  if (idr->flags[0] & 2) {
      entry = new K3bIso9660Directory( iso, isoPath, path, access | S_IFDIR, time, adate, cdate,
				       user, group, symlink,
				       special ? 0 : isonum_733(idr->extent),
				       special ? 0 : isonum_733(idr->size) );
  }
  else {
    entry = new K3bIso9660File( iso, isoPath, path, access, time, adate, cdate,
			        user, group, symlink, isonum_733(idr->extent), isonum_733(idr->size) );
    if (z_size)
      (static_cast<K3bIso9660File*>(entry))->setZF( z_algo, z_params, z_size );
  }
  iso->dirent->addEntry(entry);

  return 0;
}



K3bIso9660Entry::K3bIso9660Entry( K3bIso9660* archive,
				  const TQString& isoName,
				  const TQString& name,
				  int access,
				  int date,
				  int adate,
				  int cdate,
				  const TQString& user,
				  const TQString& group,
				  const TQString& symlink )
  : m_adate( adate ),
    m_cdate( cdate ),
    m_name( name ),
    m_isoName( isoName ),
    m_date( date ),
    m_access( access ),
    m_user( user ),
    m_group( group ),
    m_symlink( symlink ),
    m_archive( archive )
{
}


K3bIso9660Entry::~K3bIso9660Entry()
{
}






K3bIso9660File::K3bIso9660File( K3bIso9660* archive,
				const TQString& isoName,
				const TQString& name,
				int access,
				int date,
				int adate,
				int cdate,
				const TQString& user,
				const TQString& group,
				const TQString& symlink,
				unsigned int pos,
				unsigned int size )
  : K3bIso9660Entry( archive, isoName, name, access, date, adate, cdate, user, group, symlink ),
    m_startSector(pos),
    m_size(size)
{
  m_algo[0] = 0;
  m_algo[1] = 0;
  m_parms[0] = 0;
  m_parms[1] = 0;
  m_realsize = 0;
}

K3bIso9660File::~K3bIso9660File()
{
}

void K3bIso9660File::setZF(char algo[2],char parms[2],int realsize)
{
  m_algo[0]=algo[0];m_algo[1]=algo[1];
  m_parms[0]=parms[0];m_parms[1]=parms[1];
  m_realsize=realsize;
}


int K3bIso9660File::read( unsigned int pos, char* data, int maxlen ) const
{
  if( pos >= size() )
    return 0;

  unsigned long startSec = m_startSector + pos/2048;
  int startSecOffset = pos%2048;
  char* buffer = data;
  bool buffered = false;
  unsigned long bufferLen = maxlen+startSecOffset;

  // cut to size
  if( pos + maxlen > size() )
    bufferLen = size() - pos + startSecOffset;

  // pad to 2048
  if( bufferLen%2048 )
    bufferLen += (2048-(bufferLen%2048));

  // we need to buffer if we changed the startSec or need a bigger buffer
  if( startSecOffset || bufferLen > (unsigned int)maxlen ) {
    buffered = true;
    buffer = new char[bufferLen];
  }

  int read = archive()->read( startSec, buffer, bufferLen/2048 )*2048;

  if( buffered ) {
    if( read > 0 ) {
      // cut to requested data
      read -= startSecOffset;
      if( read + pos > size() )
	read = size() - pos;
      if( read > maxlen )
	read = maxlen;

      ::memcpy( data, buffer+startSecOffset, read );
    }
    delete [] buffer;

    return read;
  }
  else {
    // cut read data
    if( read + pos > size() )
      read = size() - pos;

    return read;
  }
}


bool K3bIso9660File::copyTo( const TQString& url ) const
{
  TQFile of( url );
  if( of.open( IO_WriteOnly ) ) {
    char buffer[2048*10];
    unsigned int pos = 0;
    int r = 0;
    while( ( r = read( pos, buffer, 2048*10 ) ) > 0 ) {
      of.writeBlock( buffer, r );
      pos += r;
    }

    return !r;
  }
  else {
    kdDebug() << "(K3bIso9660File) could not open " << url << " for writing." << endl;
    return false;
  }
}


K3bIso9660Directory::K3bIso9660Directory( K3bIso9660* archive,
					  const TQString& isoName,
					  const TQString& name,
					  int access,
					  int date,
					  int adate,
					  int cdate,
					  const TQString& user,
					  const TQString& group,
					  const TQString& symlink,
					  unsigned int pos,
					  unsigned int size  )
  : K3bIso9660Entry( archive, isoName, name, access, date, adate, cdate, user, group, symlink ),
    m_bExpanded( size == 0 ), // we can only expand entries that represent an actual directory
    m_startSector(pos),
    m_size(size)
{
  m_entries.setAutoDelete( true );
}

K3bIso9660Directory::~K3bIso9660Directory()
{
}


void K3bIso9660Directory::expand()
{
  if( !m_bExpanded ) {
    archive()->dirent = this;
    if( ProcessDir( &K3bIso9660::read_callback, m_startSector, m_size, &K3bIso9660::isofs_callback, archive() ) )
      kdDebug() << "(K3bIso9660) failed to expand dir: " << name() << " with size: " << m_size << endl;

    m_bExpanded = true;
  }
}


TQStringList K3bIso9660Directory::entries() const
{
  // create a fake const method to fool the user ;)
  const_cast<K3bIso9660Directory*>(this)->expand();

  TQStringList l;

  TQDictIterator<K3bIso9660Entry> it( m_entries );
  for( ; it.current(); ++it )
    l.append( it.currentKey() );

  return l;
}


TQStringList K3bIso9660Directory::iso9660Entries() const
{
  // create a fake const method to fool the user ;)
  const_cast<K3bIso9660Directory*>(this)->expand();

  TQStringList l;

  TQDictIterator<K3bIso9660Entry> it( m_iso9660Entries );
  for( ; it.current(); ++it )
    l.append( it.currentKey() );

  return l;
}


K3bIso9660Entry* K3bIso9660Directory::entry( const TQString& n )
{
  if( n.isEmpty() )
    return 0;

  expand();

  TQString name(n);

  // trailing slash ? -> remove
  if( name.length() > 1 && name[name.length()-1] == '/' ) {
    name.truncate( name.length()-1 );
  }

  int pos = name.find( '/' );
  while( pos == 0 ) {
    if( name.length() > 1 ) {
      name = name.mid( 1 ); // remove leading slash
      pos = name.find( '/' ); // look again
    }
    else // "/"
      return this;
  }

  if ( pos != -1 ) {
    TQString left = name.left( pos );
    TQString right = name.mid( pos + 1 );

    K3bIso9660Entry* e = m_entries[ left ];
    if ( !e || !e->isDirectory() )
      return 0;
    return static_cast<K3bIso9660Directory*>(e)->entry( right );
  }

  return m_entries[ name ];
}


K3bIso9660Entry* K3bIso9660Directory::iso9660Entry( const TQString& n )
{
  if( n.isEmpty() )
    return 0;

  expand();

  TQString name(n);

  // trailing slash ? -> remove
  if( name.length() > 1 && name[name.length()-1] == '/' ) {
    name.truncate( name.length()-1 );
  }

  int pos = name.find( '/' );
  while( pos == 0 ) {
    if( name.length() > 1 ) {
      name = name.mid( 1 ); // remove leading slash
      pos = name.find( '/' ); // look again
    }
    else // "/"
      return this;
  }

  if ( pos != -1 ) {
    TQString left = name.left( pos );
    TQString right = name.mid( pos + 1 );

    K3bIso9660Entry* e = m_iso9660Entries[ left ];
    if ( !e || !e->isDirectory() )
      return 0;
    return static_cast<K3bIso9660Directory*>(e)->iso9660Entry( right );
  }

  return m_iso9660Entries[ name ];
}


const K3bIso9660Entry* K3bIso9660Directory::entry( const TQString& name ) const
{
  return const_cast<K3bIso9660Directory*>(this)->entry( name );
}


const K3bIso9660Entry* K3bIso9660Directory::iso9660Entry( const TQString& name ) const
{
  return const_cast<K3bIso9660Directory*>(this)->iso9660Entry( name );
}


void K3bIso9660Directory::addEntry( K3bIso9660Entry* entry )
{
  m_entries.insert( entry->name(), entry );
  m_iso9660Entries.insert( entry->isoName(), entry );
}





class K3bIso9660::Private
{
public:
  Private()
    : cdDevice(0),
      fd(-1),
      isOpen(false),
      startSector(0),
      plainIso9660(false),
      backend(0) {
  }

  TQPtrList<K3bIso9660Directory> elToritoDirs;
  TQPtrList<K3bIso9660Directory> jolietDirs;
  TQPtrList<K3bIso9660Directory> isoDirs;
  TQPtrList<K3bIso9660Directory> rrDirs; // RockRidge

  K3bIso9660SimplePrimaryDescriptor primaryDesc;

  K3bDevice::Device* cdDevice;
  int fd;

  bool isOpen;

  // only used for direkt K3bDevice::Device access
  unsigned int startSector;

  bool plainIso9660;

  K3bIso9660Backend* backend;
};


K3bIso9660::K3bIso9660( const TQString& filename )
  :  m_filename( filename )
{
  d = new Private();
}


K3bIso9660::K3bIso9660( int fd )
{
  d = new Private();
  d->fd = fd;
}


K3bIso9660::K3bIso9660( K3bIso9660Backend* backend )
{
  d = new Private();
  d->backend = backend;
}


K3bIso9660::K3bIso9660( K3bDevice::Device* dev, unsigned int startSector )
{
  d = new Private();
  d->cdDevice = dev;
  d->startSector = startSector;
}


K3bIso9660::~K3bIso9660()
{
  close();
  delete d->backend;
  delete d;
}


void K3bIso9660::setStartSector( unsigned int startSector )
{
  d->startSector = startSector;
}


void K3bIso9660::setPlainIso9660( bool b )
{
  d->plainIso9660 = b;
}


bool K3bIso9660::plainIso9660() const
{
  return d->plainIso9660;
}


int K3bIso9660::read( unsigned int sector, char* data, int count )
{
  if( count == 0 )
    return 0;
  else
    return d->backend->read( sector, data, count );
}


void K3bIso9660::addBoot(struct el_torito_boot_descriptor* bootdesc)
{
  int i,size;
  boot_head boot;
  boot_entry *be;
  TQString path;
  K3bIso9660File *entry;

  entry=new K3bIso9660File( this, "Catalog", "Catalog", dirent->permissions() & ~S_IFDIR,
			    dirent->date(), dirent->adate(), dirent->cdate(),
			    dirent->user(), dirent->group(), TQString(),
			    isonum_731(bootdesc->boot_catalog), 2048 );
  dirent->addEntry(entry);
  if (!ReadBootTable(&K3bIso9660::read_callback,isonum_731(bootdesc->boot_catalog),&boot,this)) {
    i=1;
    be=boot.defentry;
    while (be) {
      size=BootImageSize(&K3bIso9660::read_callback,
			 isonum_711(((struct default_entry*) be->data)->media),
			 isonum_731(((struct default_entry*) be->data)->start),
			 isonum_721(((struct default_entry*) be->data)->seccount),
			 this);
      path="Default Image";
      if (i>1) path += " (" + TQString::number(i) + ")";
      entry=new K3bIso9660File( this, path, path, dirent->permissions() & ~S_IFDIR,
				dirent->date(), dirent->adate(), dirent->cdate(),
				dirent->user(), dirent->group(), TQString(),
				isonum_731(((struct default_entry*) be->data)->start), size<<9 );
      dirent->addEntry(entry);
      be=be->next;
      i++;
    }

    FreeBootTable(&boot);
  }
}


bool K3bIso9660::open()
{
  if( d->isOpen )
    return true;

  if( !d->backend ) {
    // create a backend

    if( !m_filename.isEmpty() )
      d->backend = new K3bIso9660FileBackend( m_filename );

    else if( d->fd > 0 )
      d->backend = new K3bIso9660FileBackend( d->fd );

    else if( d->cdDevice ) {
      // now check if we have a scrambled video dvd
      if( d->cdDevice->copyrightProtectionSystemType() == 1 ) {

	kdDebug() << "(K3bIso9660) found encrypted dvd. using libdvdcss." << endl;

	// open the libdvdcss stuff
	d->backend = new K3bIso9660LibDvdCssBackend( d->cdDevice );
	if( !d->backend->open() ) {
	  // fallback to devicebackend
	  delete d->backend;
	  d->backend = new K3bIso9660DeviceBackend( d->cdDevice );
	}
      }
      else
	d->backend = new K3bIso9660DeviceBackend( d->cdDevice );
    }
  }

  d->isOpen = d->backend->open();
  if( !d->isOpen )
    return false;

  iso_vol_desc *desc;
  TQString path,tmp,uid,gid;
  k3b_struct_stat buf;
  int access,c_i,c_j;
  struct el_torito_boot_descriptor* bootdesc;


  /* We'll use the permission and user/group of the 'host' file except
   * in Rock Ridge, where the permissions are stored on the file system
   */
  if ( k3b_stat( TQFile::encodeName(m_filename), &buf ) < 0 ) {
    /* defaults, if stat fails */
    memset(&buf,0,sizeof(k3b_struct_stat));
    buf.st_mode=0777;
  }
  uid.setNum(buf.st_uid);
  gid.setNum(buf.st_gid);
  access = buf.st_mode & ~S_IFMT;


  int c_b=1;
  c_i=1;c_j=1;

  desc = ReadISO9660( &K3bIso9660::read_callback, d->startSector, this );

  if (!desc) {
    kdDebug() << "K3bIso9660::openArchive no volume descriptors" << endl;
    close();
    return false;
  }

  while (desc) {

    m_rr = false;

    switch (isonum_711(desc->data.type)) {
    case ISO_VD_BOOT:

      bootdesc=(struct el_torito_boot_descriptor*) &(desc->data);
      if( !memcmp( EL_TORITO_ID, bootdesc->system_id, ISODCL(8,39) ) ) {
	path="El Torito Boot";
	if( c_b > 1 )
	  path += " (" + TQString::number(c_b) + ")";

	dirent = new K3bIso9660Directory( this, path, path, access | S_IFDIR,
					  buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, TQString() );
	d->elToritoDirs.append( dirent );

	addBoot(bootdesc);
	c_b++;
      }
      break;

    case ISO_VD_PRIMARY:
      createSimplePrimaryDesc( (struct iso_primary_descriptor*)&desc->data );
      // fall through
    case ISO_VD_SUPPLEMENTARY:
      {
	struct iso_primary_descriptor* primaryDesc = (struct iso_primary_descriptor*)&desc->data;
	struct iso_directory_record* idr = (struct iso_directory_record*)&primaryDesc->root_directory_record;

	m_joliet = JolietLevel(&desc->data);

	// skip joliet in plain iso mode
	if( m_joliet && plainIso9660() )
	  break;

	if (m_joliet) {
	  path = "Joliet level " + TQString::number(m_joliet);
	  if( c_j > 1 )
	    path += " (" + TQString::number(c_j) + ")";
	}
	else {
	  path = TQString::fromLocal8Bit( primaryDesc->volume_id, 32 );
	  if( c_i > 1 )
	    path += " (" + TQString::number(c_i) + ")";
	}

	dirent = new K3bIso9660Directory( this, path, path, access | S_IFDIR,
					  buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, TQString() );

	// expand the root entry
	ProcessDir( &K3bIso9660::read_callback, isonum_733(idr->extent),isonum_733(idr->size),&K3bIso9660::isofs_callback,this);

	if (m_joliet)
	  c_j++;
	else
	  c_i++;

	if( m_joliet )
	  d->jolietDirs.append( dirent );
	else {
	  if( m_rr )
	    d->rrDirs.append( dirent );
	  d->isoDirs.append( dirent );
	}

	break;
      }
    }
    desc = desc->next;
  }

  FreeISO9660(desc);

  return true;
}


bool K3bIso9660::isOpen() const
{
  return d->isOpen;
}


void K3bIso9660::createSimplePrimaryDesc( struct iso_primary_descriptor* desc )
{
  d->primaryDesc.volumeId = TQString(TQString::fromLocal8Bit( desc->volume_id, 32 )).stripWhiteSpace();
  d->primaryDesc.systemId = TQString(TQString::fromLocal8Bit( desc->system_id, 32 )).stripWhiteSpace();
  d->primaryDesc.volumeSetId = TQString(TQString::fromLocal8Bit( desc->volume_set_id, 128 )).stripWhiteSpace();
  d->primaryDesc.publisherId = TQString(TQString::fromLocal8Bit( desc->publisher_id, 128 )).stripWhiteSpace();
  d->primaryDesc.preparerId = TQString(TQString::fromLocal8Bit( desc->preparer_id, 128 )).stripWhiteSpace();
  d->primaryDesc.applicationId = TQString(TQString::fromLocal8Bit( desc->application_id, 128 )).stripWhiteSpace();
  d->primaryDesc.volumeSetSize = isonum_723(desc->volume_set_size);
  d->primaryDesc.volumeSetNumber = isonum_723(desc->volume_set_size);
  d->primaryDesc.logicalBlockSize = isonum_723(desc->logical_block_size);
  d->primaryDesc.volumeSpaceSize = isonum_733(desc->volume_space_size);
}


void K3bIso9660::close()
{
  if( d->isOpen ) {
    d->backend->close();

    // Since the first isoDir is the KArchive
    // root we must not delete it but all the
    // others.

    d->elToritoDirs.setAutoDelete(true);
    d->jolietDirs.setAutoDelete(true);
    d->isoDirs.setAutoDelete(true);
    d->elToritoDirs.clear();
    d->jolietDirs.clear();
    d->isoDirs.clear();

    d->isOpen = false;
  }
}


const K3bIso9660Directory* K3bIso9660::firstJolietDirEntry() const
{
  return d->jolietDirs.first();
}


const K3bIso9660Directory* K3bIso9660::firstIsoDirEntry() const
{
  return d->isoDirs.first();
}


const K3bIso9660Directory* K3bIso9660::firstElToritoEntry() const
{
  return d->elToritoDirs.first();
}


const K3bIso9660Directory* K3bIso9660::firstRRDirEntry() const
{
  return d->rrDirs.first();
}


const K3bIso9660SimplePrimaryDescriptor& K3bIso9660::primaryDescriptor() const
{
  return d->primaryDesc;
}


void K3bIso9660::debug() const
{
  if( isOpen() ) {
    kdDebug() << "System Id:         " << primaryDescriptor().systemId << endl;
    kdDebug() << "Volume Id:         " << primaryDescriptor().volumeId << endl;
    kdDebug() << "Volume Set Id:     " << primaryDescriptor().volumeSetId << endl;
    kdDebug() << "Preparer Id:       " << primaryDescriptor().preparerId << endl;
    kdDebug() << "Publisher Id:      " << primaryDescriptor().publisherId << endl;
    kdDebug() << "Application Id:    " << primaryDescriptor().applicationId << endl;
    kdDebug() << "Volume Set Size:   " << primaryDescriptor().volumeSetSize << endl;
    kdDebug() << "Volume Set Number: " << primaryDescriptor().volumeSetNumber << endl;

    if( firstIsoDirEntry() ) {
      kdDebug() << "First ISO Dir entry:" << endl;
      kdDebug() << "----------------------------------------------" << endl;
      debugEntry( firstIsoDirEntry(), 0 );
      kdDebug() << "----------------------------------------------" << endl << endl;
    }
    if( firstRRDirEntry() ) {
      kdDebug() << "First RR Dir entry:" << endl;
      kdDebug() << "----------------------------------------------" << endl;
      debugEntry( firstRRDirEntry(), 0 );
      kdDebug() << "----------------------------------------------" << endl << endl;
    }
    if( firstJolietDirEntry() ) {
      kdDebug() << "First Joliet Dir entry:" << endl;
      kdDebug() << "----------------------------------------------" << endl;
      debugEntry( firstJolietDirEntry(), 0 );
      kdDebug() << "----------------------------------------------" << endl << endl;
    }
  }
}


void K3bIso9660::debugEntry( const K3bIso9660Entry* entry, int depth ) const
{
  if( !entry ) {
    kdDebug() << "(K3bIso9660::debugEntry) null entry." << endl;
    return;
  }

  TQString spacer;
  spacer.fill( ' ', depth*3 );
  kdDebug() << spacer << "- " << entry->name() << " (" << entry->isoName() << ")" << endl;
  if( entry->isDirectory() ) {
    const K3bIso9660Directory* dir = dynamic_cast<const K3bIso9660Directory*>(entry);
    TQStringList entries = dir->entries();
    for( TQStringList::const_iterator it = entries.begin(); it != entries.end(); ++it ) {
      debugEntry( dir->entry( *it ), depth+1 );
    }
  }
}


K3bIso9660SimplePrimaryDescriptor::K3bIso9660SimplePrimaryDescriptor()
  : volumeSetSize(0),
    volumeSetNumber(0),
    logicalBlockSize(0),
    volumeSpaceSize(0)
{
}


bool operator==( const K3bIso9660SimplePrimaryDescriptor& d1,
		 const K3bIso9660SimplePrimaryDescriptor& d2 )
{
  return( d1.volumeId == d2.volumeId &&
	  d1.systemId == d2.systemId &&
	  d1.volumeSetId == d2.volumeSetId &&
	  d1.publisherId == d2.publisherId &&
	  d1.preparerId == d2.preparerId &&
	  d1.applicationId == d2.applicationId &&
	  d1.volumeSetSize == d2.volumeSetSize &&
	  d1.volumeSetNumber == d2.volumeSetNumber &&
	  d1.logicalBlockSize == d2.logicalBlockSize &&
	  d1.volumeSpaceSize == d2.volumeSpaceSize );
}


bool operator!=( const K3bIso9660SimplePrimaryDescriptor& d1,
		 const K3bIso9660SimplePrimaryDescriptor& d2 )
{
  return( d1.volumeId != d2.volumeId ||
	  d1.systemId != d2.systemId ||
	  d1.volumeSetId != d2.volumeSetId ||
	  d1.publisherId != d2.publisherId ||
	  d1.preparerId != d2.preparerId ||
	  d1.applicationId != d2.applicationId ||
	  d1.volumeSetSize != d2.volumeSetSize ||
	  d1.volumeSetNumber != d2.volumeSetNumber ||
	  d1.logicalBlockSize != d2.logicalBlockSize ||
	  d1.volumeSpaceSize != d2.volumeSpaceSize );
}
