diff --git a/CMakeLists.txt b/CMakeLists.txt index f5c1199cd..c1f5c4344 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,258 +1,259 @@ project(k3b) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) ################## K3b version ################################ set(K3B_VERSION_MAJOR 1) -set(K3B_VERSION_MINOR 60) +set(K3B_VERSION_MINOR 61) set(K3B_VERSION_RELEASE 0) set(K3B_VERSION_STRING "${K3B_VERSION_MAJOR}.${K3B_VERSION_MINOR}.${K3B_VERSION_RELEASE}") # increase on BC breakage set(K3B_LIB_VERSION_MAJOR 6) # increase after adding functionality set(K3B_LIB_VERSION_MINOR 0) set(K3B_LIB_VERSION_RELEASE 0) ################## K3b options ################################# option(K3B_BUILD_K3BSETUP "Build k3bsetup" ON) option(K3B_DEBUG "K3b additional debugging support") option(K3B_ENABLE_MUSICBRAINZ "Support for querying metadata about audio tracks from Musicbrainz." ON) option(K3B_ENABLE_DVD_RIPPING "Support for ripping Video DVDs with optional decryption." ON) option(K3B_ENABLE_TAGLIB "Support for reading audio file metadata using Taglib." ON) option(K3B_BUILD_API_DOCS "Build the API documentation for the K3b libs." OFF) # plugin options option(K3B_BUILD_FFMPEG_DECODER_PLUGIN "Build FFmpeg decoder plugin" ON) option(K3B_BUILD_OGGVORBIS_DECODER_PLUGIN "Build Ogg-Vorbis decoder plugin" ON) option(K3B_BUILD_OGGVORBIS_ENCODER_PLUGIN "Build Ogg-Vorbis encoder plugin" ON) option(K3B_BUILD_MAD_DECODER_PLUGIN "Build MAD mp3 decoder plugin" ON) option(K3B_BUILD_MUSE_DECODER_PLUGIN "Build Musepack decoder plugin" ON) option(K3B_BUILD_FLAC_DECODER_PLUGIN "Build Flac decoder plugin" ON) option(K3B_BUILD_SNDFILE_DECODER_PLUGIN "Build libsndfile decoder plugin" ON) option(K3B_BUILD_LAME_ENCODER_PLUGIN "Build Lame encoder plugin" ON) option(K3B_BUILD_SOX_ENCODER_PLUGIN "Build Sox encoder plugin" ON) option(K3B_BUILD_EXTERNAL_ENCODER_PLUGIN "Build external app encoder plugin" ON) option(K3B_BUILD_WAVE_DECODER_PLUGIN "Build Wave decoder plugin" ON) ################## K3b requirements ################################# find_package(KDE4 REQUIRED) include(KDE4Defaults) include(MacroLibrary) include(MacroLogFeature) if(K3B_ENABLE_DVD_RIPPING) macro_optional_find_package(DvdRead) macro_log_feature( DVDREAD_FOUND "libDVDRead" "Libdvdread provides a simple foundation for reading DVD video disks." "http://www.dtek.chalmers.se/groups/dvd/downloads.shtml" FALSE "" "") endif(K3B_ENABLE_DVD_RIPPING) if(K3B_ENABLE_TAGLIB) macro_optional_find_package(Taglib) macro_log_feature( TAGLIB_FOUND "Taglib" "Read and write tags in audio files" "http://www.freshmeat.net/projects/taglib" FALSE "1.4.0" "") endif(K3B_ENABLE_TAGLIB) if(K3B_ENABLE_MUSICBRAINZ) macro_optional_find_package(MusicBrainz) macro_log_feature( MUSICBRAINZ_FOUND "Musicbrainz" "Provide information about the CD, about the artist or about related information" "http://musicbrainz.org/" FALSE "1.1" "") endif(K3B_ENABLE_MUSICBRAINZ) find_package(Kcddb) macro_log_feature( KCDDB_FOUND "KCddb" "KCddb is used to retrieve audio CD meta data from the internet." "http://multimedia.kde.org" TRUE "" "") if(K3B_BUILD_FFMPEG_DECODER_PLUGIN) macro_optional_find_package(FFmpeg) macro_log_feature( FFMPEG_FOUND "FFmpeg" "Needed for the K3b FFmpeg decoder plugin which can decode virtually all audio types." "http://ffmpeg.org/" FALSE "" "") endif(K3B_BUILD_FFMPEG_DECODER_PLUGIN) if(K3B_BUILD_FLAC_DECODER_PLUGIN) macro_optional_find_package(Flac) macro_optional_find_package(Flac++) macro_log_feature( FLAC_FOUND "Flac" "Needed for the Flac audio decoder plugin." "http://flac.sourceforge.net/" FALSE "" "") macro_log_feature( FLAC++_FOUND "Flac++" "Needed for the Flac audio decoder plugin." "http://flac.sourceforge.net/" FALSE "" "") endif(K3B_BUILD_FLAC_DECODER_PLUGIN) if(K3B_BUILD_MAD_DECODER_PLUGIN) macro_optional_find_package(Mad) macro_log_feature( MAD_FOUND "Mad mp3" "Needed for the mp3 audio decoder plugin." "http://www.underbit.com/products/mad/" FALSE "" "") endif(K3B_BUILD_MAD_DECODER_PLUGIN) if(K3B_BUILD_MUSE_DECODER_PLUGIN) macro_optional_find_package(Muse) macro_log_feature( MUSE_FOUND "Muse" "Needed for the Musepack audio decoder plugin" "http://www.musepack.net/" FALSE "" "") endif(K3B_BUILD_MUSE_DECODER_PLUGIN) if(K3B_BUILD_SNDFILE_DECODER_PLUGIN) macro_optional_find_package(Sndfile) macro_log_feature( SNDFILE_FOUND "Sndfile" "Needed for the libsndfile audio decoder plugin." "http://www.mega-nerd.com/libsndfile/" FALSE "" "") endif(K3B_BUILD_SNDFILE_DECODER_PLUGIN) if(K3B_BUILD_LAME_ENCODER_PLUGIN) macro_optional_find_package(Lame) macro_log_feature( LAME_FOUND "Lame mp3 encoder" "Needed for the lame mpf encoder encoder plugin." "http://lame.sourceforge.net/" FALSE "" "") endif(K3B_BUILD_LAME_ENCODER_PLUGIN) if(K3B_BUILD_OGGVORBIS_DECODER_PLUGIN OR K3B_BUILD_OGGVORBIS_ENCODER_PLUGIN) macro_optional_find_package(OggVorbis) macro_log_feature( OGGVORBIS_FOUND "Ogg Vorbis" "Needed for the K3b Ogg Vorbis decoder and encoder plugins." "http://www.vorbis.com/" FALSE "" "") endif(K3B_BUILD_OGGVORBIS_DECODER_PLUGIN OR K3B_BUILD_OGGVORBIS_ENCODER_PLUGIN) ################## K3b build settings ################################# include(ConfigureChecks.cmake) macro_bool_to_01(K3B_BUILD_K3BSETUP BUILD_K3BSETUP) macro_bool_to_01(ADD_K3B_DEBUG K3B_DEBUG) if(K3B_ENABLE_DVD_RIPPING AND DVDREAD_FOUND) set(ENABLE_DVD_RIPPING 1) endif(K3B_ENABLE_DVD_RIPPING AND DVDREAD_FOUND) if(K3B_ENABLE_TAGLIB AND TAGLIB_FOUND) set(ENABLE_TAGLIB 1) endif(K3B_ENABLE_TAGLIB AND TAGLIB_FOUND) if(K3B_ENABLE_MUSICBRAINZ AND MUSICBRAINZ_FOUND) set(ENABLE_MUSICBRAINZ 1) endif(K3B_ENABLE_MUSICBRAINZ AND MUSICBRAINZ_FOUND) if(K3B_BUILD_FFMPEG_DECODER_PLUGIN AND FFMPEG_FOUND) set(BUILD_FFMPEG_DECODER_PLUGIN 1) endif(K3B_BUILD_FFMPEG_DECODER_PLUGIN AND FFMPEG_FOUND) if(K3B_BUILD_FLAC_DECODER_PLUGIN AND FLAC_FOUND AND FLAC++_FOUND) set(BUILD_FLAC_DECODER_PLUGIN 1) endif(K3B_BUILD_FLAC_DECODER_PLUGIN AND FLAC_FOUND AND FLAC++_FOUND) if(K3B_BUILD_MAD_DECODER_PLUGIN AND MAD_FOUND) set(BUILD_MAD_DECODER_PLUGIN 1) endif(K3B_BUILD_MAD_DECODER_PLUGIN AND MAD_FOUND) if(K3B_BUILD_MUSE_DECODER_PLUGIN AND MUSE_FOUND) set(BUILD_MUSE_DECODER_PLUGIN 1) endif(K3B_BUILD_MUSE_DECODER_PLUGIN AND MUSE_FOUND) if(K3B_BUILD_SNDFILE_DECODER_PLUGIN AND SNDFILE_FOUND) set(BUILD_SNDFILE_DECODER_PLUGIN 1) endif(K3B_BUILD_SNDFILE_DECODER_PLUGIN AND SNDFILE_FOUND) if(K3B_BUILD_LAME_ENCODER_PLUGIN AND LAME_FOUND) set(BUILD_LAME_ENCODER_PLUGIN 1) endif(K3B_BUILD_LAME_ENCODER_PLUGIN AND LAME_FOUND) if(K3B_BUILD_OGGVORBIS_DECODER_PLUGIN AND OGGVORBIS_FOUND) set(BUILD_OGGVORBIS_DECODER_PLUGIN 1) endif(K3B_BUILD_OGGVORBIS_DECODER_PLUGIN AND OGGVORBIS_FOUND) if(K3B_BUILD_OGGVORBIS_ENCODER_PLUGIN AND OGGVORBIS_FOUND) set(BUILD_OGGVORBIS_ENCODER_PLUGIN 1) endif(K3B_BUILD_OGGVORBIS_ENCODER_PLUGIN AND OGGVORBIS_FOUND) if(K3B_BUILD_WAVE_DECODER_PLUGIN) set(BUILD_WAVE_DECODER_PLUGIN 1) endif(K3B_BUILD_WAVE_DECODER_PLUGIN) if(K3B_BUILD_SOX_ENCODER_PLUGIN) set(BUILD_SOX_ENCODER_PLUGIN 1) endif(K3B_BUILD_SOX_ENCODER_PLUGIN) if(K3B_BUILD_EXTERNAL_ENCODER_PLUGIN) set(BUILD_EXTERNAL_ENCODER_PLUGIN 1) endif(K3B_BUILD_EXTERNAL_ENCODER_PLUGIN) if(NOT WIN32 AND NOT APPLE) set(ENABLE_HAL_SUPPORT 1) endif(NOT WIN32 AND NOT APPLE) configure_file (config-k3b.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-k3b.h ) ################## K3b apidox ################################ if(K3B_BUILD_API_DOCS) find_package(Doxygen) if(DOXYGEN_EXECUTABLE) configure_file(${k3b_SOURCE_DIR}/Doxyfile.cmake ${k3b_BINARY_DIR}/Doxyfile) if(EXISTS ${QT_DOC_DIR}/html) set(QTDOCS "${QT_DOC_DIR}/html") else(EXISTS ${QT_DOC_DIR}/html) set(QTDOCS "http://doc.trolltech.com/4.3/") endif(EXISTS ${QT_DOC_DIR}/html) add_custom_target( apidox COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile COMMAND docs/html/installdox -l qt4.tag@${QTDOCS} docs/html/*.html) endif(DOXYGEN_EXECUTABLE) endif(K3B_BUILD_API_DOCS) ################## K3b build environment ################################ add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) add_definitions (-DQT3_SUPPORT -DQT3_SUPPORT_WARNINGS) include_directories( ${KDE4_INCLUDES} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/libk3bdevice ${CMAKE_CURRENT_BINARY_DIR}/libk3bdevice ${CMAKE_CURRENT_SOURCE_DIR}/libk3b/plugin - ${CMAKE_CURRENT_SOURCE_DIR}/libk3b/core) + ${CMAKE_CURRENT_SOURCE_DIR}/libk3b/core + ${CMAKE_CURRENT_SOURCE_DIR}/libk3b/tools/qprocess) add_subdirectory( libk3bdevice ) add_subdirectory( libk3b ) add_subdirectory( src ) add_subdirectory( kioslaves ) add_subdirectory( plugins ) # TODO port to strigi #add_subdirectory( kfile-plugins ) if(K3B_BUILD_K3BSETUP) add_subdirectory(k3bsetup) endif(K3B_BUILD_K3BSETUP) ################## K3b config summary ################################ macro_display_feature_log() diff --git a/k3bsetup/k3bsetup2.cpp b/k3bsetup/k3bsetup2.cpp index d8864920f..3c21d0b46 100644 --- a/k3bsetup/k3bsetup2.cpp +++ b/k3bsetup/k3bsetup2.cpp @@ -1,303 +1,304 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * Copyright (C) 2009 Michal Malek * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * Copyright (C) 2009 Michal Malek * * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include #include #include #include #include #include #include "k3bsetup2.h" #include "k3bsetupdevices.h" #include "k3bsetupprograms.h" #include #include #include #include #include + class K3bSetup2::Private { public: K3b::SetupDevices* devicesModel; K3b::SetupPrograms* programsModel; KConfig* config; }; K_PLUGIN_FACTORY(K3bSetup2Factory, registerPlugin();) K_EXPORT_PLUGIN(K3bSetup2Factory("k3bsetup")) K3bSetup2::K3bSetup2( QWidget *parent, const QVariantList& ) : KCModule( K3bSetup2Factory::componentData(), parent ) { d = new Private(); d->config = new KConfig( "k3bsetup2rc" ); KAboutData* aboutData = new KAboutData("k3bsetup2", 0, ki18n("K3bSetup 2"), "2.0", KLocalizedString(), KAboutData::License_GPL, ki18n("(C) 2003-2007 Sebastian Trueg"), ki18n(0L)); aboutData->addAuthor(ki18n("Sebastian Trueg"), KLocalizedString(), "trueg@k3b.org"); setAboutData( aboutData ); QHBoxLayout* box = new QHBoxLayout( this ); box->setMargin(0); box->setSpacing( KDialog::spacingHint() ); KTextEdit* label = new KTextEdit( this ); label->setText( "

K3b::Setup

" + i18n("

This simple setup assistant is able to set the permissions needed by K3b in order to " "burn CDs and DVDs. " "

It does not take things like devfs or resmgr into account. In most cases this is not a " "problem but on some systems the permissions may be altered the next time you login or restart " "your computer. In those cases it is best to consult the distribution documentation." "

Caution: Although K3b::Setup 2 should not be able " "to mess up your system no guarantee can be given.") ); label->setReadOnly( true ); label->setFixedWidth( 200 ); QWidget* w = new QWidget( this ); setupUi( w ); // TODO: enable this and let root specify users m_editUsers->hide(); textLabel2->hide(); box->addWidget( label ); box->addWidget( w ); d->devicesModel = new K3b::SetupDevices( this ); d->programsModel = new K3b::SetupPrograms(this ); connect( d->devicesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotDataChanged()) ); connect( d->devicesModel, SIGNAL(modelReset()), this, SLOT(slotDataChanged()) ); connect( d->programsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotDataChanged()) ); connect( d->programsModel, SIGNAL(modelReset()), this, SLOT(slotDataChanged()) ); connect( m_checkUseBurningGroup, SIGNAL(toggled(bool)), this, SLOT(slotBurningGroup()) ); connect( m_editBurningGroup, SIGNAL(textChanged(const QString&)), this, SLOT(slotBurningGroup()) ); connect( m_editSearchPath, SIGNAL(changed()), this, SLOT(slotSearchPrograms()) ); m_viewDevices->setModel( d->devicesModel ); m_viewDevices->header()->setResizeMode( QHeaderView::ResizeToContents ); m_viewPrograms->setModel( d->programsModel ); m_viewPrograms->header()->setResizeMode( QHeaderView::ResizeToContents ); load(); // // This is a hack to work around a kcm bug which makes the faulty assumption that // every module starts without anything to apply // QTimer::singleShot( 0, this, SLOT(slotDataChanged()) ); if( getuid() != 0 /*|| !d->config->isConfigWritable()*/ ) { m_checkUseBurningGroup->setEnabled( false ); m_editBurningGroup->setEnabled( false ); m_editUsers->setEnabled( false ); m_viewDevices->setEnabled( false ); m_viewPrograms->setEnabled( false ); m_editSearchPath->setEnabled( false ); } } K3bSetup2::~K3bSetup2() { delete d->config; delete d; } QString K3bSetup2::quickHelp() const { return i18n("

K3b::Setup 2

" "

This simple setup assistant is able to set the permissions needed by K3b in order to " "burn CDs and DVDs." "

It does not take into account devfs or resmgr, or similar. In most cases this is not a " "problem, but on some systems the permissions may be altered the next time you login or restart " "your computer. In these cases it is best to consult the distribution's documentation." "

The important task that K3b::Setup 2 performs is grant write access to the CD and DVD devices." "

Caution: Although K3b::Setup 2 should not be able " "to damage your system, no guarantee can be given."); } void K3bSetup2::defaults() { m_checkUseBurningGroup->setChecked(false); m_editBurningGroup->setText( "burning" ); d->devicesModel->defaults(); d->programsModel->defaults(); } void K3bSetup2::load() { d->devicesModel->load( *d->config ); d->programsModel->load( *d->config ); KConfigGroup grp(d->config, "General Settings" ); m_checkUseBurningGroup->setChecked( grp.readEntry( "use burning group", false ) ); m_editBurningGroup->setText( grp.readEntry( "burning group", "burning" ) ); // load search path m_editSearchPath->clear(); m_editSearchPath->insertStringList( d->programsModel->searchPaths() ); } void K3bSetup2::save() { QString burningGroup = m_editBurningGroup->text(); KConfigGroup grp(d->config, "General Settings" ); grp.writeEntry( "use burning group", m_checkUseBurningGroup->isChecked() ); grp.writeEntry( "burning group", burningGroup.isEmpty() ? QString("burning") : burningGroup ); grp.sync(); d->devicesModel->save( *d->config ); d->programsModel->save( *d->config ); bool success = true; struct group* g = 0; if( m_checkUseBurningGroup->isChecked() && !burningGroup.isEmpty() ) { // TODO: create the group if it's not there g = getgrnam( burningGroup.toLocal8Bit() ); if( !g ) { KMessageBox::error( this, i18n("There is no group %1.",burningGroup) ); return; } } Q_FOREACH( const QString& dev, d->devicesModel->selectedDevices() ) { if( g != 0 ) { if( ::chmod( QFile::encodeName(dev), S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP ) ) success = false; if( ::chown( QFile::encodeName(dev), (gid_t)-1, g->gr_gid ) ) success = false; } else { if( ::chmod( QFile::encodeName(dev), S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH ) ) success = false; } } Q_FOREACH( const K3b::ExternalBin* bin, d->programsModel->selectedPrograms() ) { if( g != 0 ) { if( ::chown( QFile::encodeName(bin->path), (gid_t)0, g->gr_gid ) ) success = false; int perm = 0; if( K3b::SetupPrograms::shouldRunSuidRoot( bin ) ) perm = S_ISUID|S_IRWXU|S_IXGRP; else perm = S_IRWXU|S_IXGRP|S_IRGRP; if( ::chmod( QFile::encodeName(bin->path), perm ) ) success = false; } else { if( ::chown( QFile::encodeName(bin->path), 0, 0 ) ) success = false; int perm = 0; if( K3b::SetupPrograms::shouldRunSuidRoot( bin ) ) perm = S_ISUID|S_IRWXU|S_IXGRP|S_IXOTH; else perm = S_IRWXU|S_IXGRP|S_IRGRP|S_IXOTH|S_IROTH; if( ::chmod( QFile::encodeName(bin->path), perm ) ) success = false; } } if( success ) KMessageBox::information( this, i18n("Successfully updated all permissions.") ); else { if( getuid() ) KMessageBox::error( this, i18n("Could not update all permissions. You should run K3b::Setup 2 as root.") ); else KMessageBox::error( this, i18n("Could not update all permissions.") ); } // WE MAY USE "newgrp -" to reinitialize the environment if we add users to a group d->devicesModel->update(); d->programsModel->update(); } void K3bSetup2::slotDataChanged() { emit changed( ( getuid() != 0 ) ? false : d->devicesModel->changesNeeded() || d->programsModel->changesNeeded() ); } void K3bSetup2::slotBurningGroup() { if( m_checkUseBurningGroup->isChecked() ) { d->devicesModel->setBurningGroup( m_editBurningGroup->text() ); d->programsModel->setBurningGroup( m_editBurningGroup->text() ); } else { d->devicesModel->setBurningGroup( QString() ); d->programsModel->setBurningGroup( QString() ); } } void K3bSetup2::slotSearchPrograms() { d->programsModel->setSearchPaths( m_editSearchPath->items() ); } #include "k3bsetup2.moc" diff --git a/libk3b/CMakeLists.txt b/libk3b/CMakeLists.txt index f34bd87af..e718a26df 100644 --- a/libk3b/CMakeLists.txt +++ b/libk3b/CMakeLists.txt @@ -1,251 +1,253 @@ project(libk3b) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/core ${CMAKE_CURRENT_SOURCE_DIR}/plugin ${CMAKE_CURRENT_SOURCE_DIR}/tools + ${CMAKE_CURRENT_SOURCE_DIR}/tools/qprocess ${CMAKE_CURRENT_SOURCE_DIR}/projects ${CMAKE_CURRENT_SOURCE_DIR}/projects/audiocd ${CMAKE_CURRENT_SOURCE_DIR}/projects/datacd ${CMAKE_CURRENT_SOURCE_DIR}/projects/videocd ${CMAKE_CURRENT_SOURCE_DIR}/projects/mixedcd ${CMAKE_CURRENT_SOURCE_DIR}/jobs ${CMAKE_CURRENT_SOURCE_DIR}/videodvd ${CMAKE_CURRENT_BINARY_DIR}/core ${CMAKE_CURRENT_BINARY_DIR}/plugin ${CMAKE_CURRENT_BINARY_DIR}/tools ${CMAKE_CURRENT_BINARY_DIR}/projects ${CMAKE_CURRENT_BINARY_DIR}/jobs ) set(CMAKE_REQUIRED_LIBRARIES m) CHECK_FUNCTION_EXISTS(lrint HAVE_LRINT) CHECK_FUNCTION_EXISTS(lrintf HAVE_LRINTF) configure_file(config-libk3b.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-libk3b.h ) add_subdirectory( core ) add_subdirectory( plugin ) add_subdirectory( tools ) add_subdirectory( projects ) add_subdirectory( jobs ) macro_optional_find_package(Samplerate) set(k3b_jobs_SRCS jobs/k3bdatatrackreader.cpp jobs/k3breadcdreader.cpp jobs/k3bcdcopyjob.cpp jobs/k3bclonejob.cpp jobs/k3baudiosessionreadingjob.cpp jobs/k3bdvdcopyjob.cpp jobs/k3baudiofileanalyzerjob.cpp jobs/k3baudiocuefilewritingjob.cpp jobs/k3bbinimagewritingjob.cpp jobs/k3biso9660imagewritingjob.cpp jobs/k3bdvdformattingjob.cpp jobs/k3bblankingjob.cpp jobs/k3bclonetocreader.cpp jobs/k3bverificationjob.cpp jobs/k3bdvdbooktypejob.cpp ) ########### next target ############### if(ENABLE_DVD_RIPPING) add_subdirectory(videodvd) set(k3b_jobs_SRCS ${k3b_jobs_SRCS} jobs/k3bvideodvdtitletranscodingjob.cpp jobs/k3bvideodvdtitledetectclippingjob.cpp ) endif(ENABLE_DVD_RIPPING) set(k3b_core_SRCS - core/k3process.cpp - core/k3processcontroller.cpp core/k3bcore.cpp core/k3bglobals.cpp core/k3bdefaultexternalprograms.cpp core/k3bexternalbinmanager.cpp core/k3bversion.cpp core/k3bprocess.cpp core/k3bjob.cpp core/k3bthread.cpp core/k3bthreadjob.cpp core/k3bglobalsettings.cpp core/k3bsimplejobhandler.cpp core/k3bthreadjobcommunicationevent.cpp) set(k3b_tools_SRCS tools/k3bwavefilewriter.cpp tools/k3bbusywidget.cpp tools/k3bdeviceselectiondialog.cpp tools/k3bmd5job.cpp tools/k3btitlelabel.cpp tools/k3bstringutils.cpp tools/k3bdevicecombobox.cpp tools/k3bstdguiitems.cpp tools/k3bvalidators.cpp tools/k3bthroughputestimator.cpp tools/k3biso9660.cpp tools/k3bmultichoicedialog.cpp tools/k3bdevicehandler.cpp tools/k3bcdparanoialib.cpp tools/k3blistview.cpp tools/k3bmsfedit.cpp tools/k3bcdtextvalidator.cpp tools/k3bintvalidator.cpp tools/k3bexceptions.cpp tools/k3bprogressdialog.cpp tools/k3bpushbutton.cpp tools/k3blistviewitemanimator.cpp tools/k3bthreadwidget.cpp tools/k3bsignalwaiter.cpp tools/k3blibdvdcss.cpp tools/k3biso9660backend.cpp - tools/k3bpipe.cpp tools/k3bchecksumpipe.cpp + tools/k3bchecksumpipe.cpp tools/k3bintmapcombobox.cpp tools/k3bdirsizejob.cpp tools/k3brichtextlabel.cpp tools/k3bactivepipe.cpp tools/k3bfilesplitter.cpp tools/k3bfilesysteminfo.cpp tools/k3bdevicemodel.cpp tools/k3bmedium.cpp tools/k3bmediacache.cpp tools/k3bcddb.cpp + tools/qprocess/k3bqprocess.cpp + tools/qprocess/k3bqprocess_unix.cpp + tools/qprocess/k3bkprocess.cpp ) set(k3b_libisofs_SRCS tools/libisofs/isofs.cpp) set(k3b_videodvd_SRCS videodvd/k3bvideodvd.cpp videodvd/k3bvideodvdtime.cpp videodvd/k3bvideodvdvideostream.cpp) set(k3b_plugin_SRCS plugin/k3bplugin.cpp plugin/k3bpluginconfigwidget.cpp plugin/k3bpluginmanager.cpp plugin/k3baudiodecoder.cpp plugin/k3baudioencoder.cpp plugin/k3bprojectplugin.cpp ) set(k3b_project_SRCS projects/k3babstractwriter.cpp projects/k3bgrowisofswriter.cpp projects/k3bgrowisofshandler.cpp projects/k3bdoc.cpp projects/k3bcdrdaowriter.cpp projects/k3bcdrecordwriter.cpp projects/k3binffilewriter.cpp projects/k3btocfilewriter.cpp projects/k3bimagefilereader.cpp - projects/k3bcuefileparser.cpp - projects/k3bpipebuffer.cpp) + projects/k3bcuefileparser.cpp ) +# projects/k3bpipebuffer.cpp) set(k3b_project_audiocd_SRCS projects/audiocd/k3baudiojob.cpp projects/audiocd/k3baudiotrack.cpp projects/audiocd/k3baudiodoc.cpp projects/audiocd/k3baudiofile.cpp projects/audiocd/k3baudiozerodata.cpp projects/audiocd/k3baudiodatasource.cpp projects/audiocd/k3baudionormalizejob.cpp projects/audiocd/k3baudiojobtempdata.cpp projects/audiocd/k3baudioimager.cpp projects/audiocd/k3baudiomaxspeedjob.cpp projects/audiocd/k3baudiocdtracksource.cpp projects/audiocd/k3baudiocdtrackdrag.cpp projects/audiocd/k3baudiodatasourceiterator.cpp ) set(k3b_project_datacd projects/datacd/k3bdatajob.cpp projects/datacd/k3bdatadoc.cpp projects/datacd/k3bdataitem.cpp projects/datacd/k3bdiritem.cpp projects/datacd/k3bfileitem.cpp projects/datacd/k3bisoimager.cpp projects/datacd/k3bbootitem.cpp projects/datacd/k3bisooptions.cpp projects/datacd/k3bfilecompilationsizehandler.cpp projects/datacd/k3bsessionimportitem.cpp projects/datacd/k3bmkisofshandler.cpp projects/datacd/k3bdatapreparationjob.cpp projects/datacd/k3bmsinfofetcher.cpp projects/datacd/k3bdatamultisessionparameterjob.cpp ) set(k3b_project_mixedcd projects/mixedcd/k3bmixeddoc.cpp projects/mixedcd/k3bmixedjob.cpp ) set(k3b_project_movixcd projects/movixcd/k3bmovixprogram.cpp projects/movixcd/k3bmovixdoc.cpp projects/movixcd/k3bmovixjob.cpp projects/movixcd/k3bmovixfileitem.cpp projects/movixcd/k3bmovixdocpreparer.cpp ) set(k3b_project_videocd projects/videocd/k3bvcddoc.cpp projects/videocd/k3bvcdtrack.cpp projects/videocd/k3bvcdjob.cpp projects/videocd/k3bvcdoptions.cpp projects/videocd/k3bvcdxmlview.cpp ) set(k3b_project_mpeg_info projects/videocd/mpeginfo/k3bmpeginfo.cpp ) set(k3b_project_videodvd projects/videodvd/k3bvideodvddoc.cpp projects/videodvd/k3bvideodvdjob.cpp projects/videodvd/k3bvideodvdimager.cpp ) set(k3b_samplerate_SRCS plugin/libsamplerate/samplerate.c plugin/libsamplerate/src_sinc.c plugin/libsamplerate/src_zoh.c plugin/libsamplerate/src_linear.c) set(k3b_LIB_SRCS ${k3b_core_SRCS} ${k3b_tools_SRCS} ${k3b_plugin_SRCS} ${k3b_project_SRCS} ${k3b_jobs_SRCS} ${k3b_libisofs_SRCS} ${k3b_project_audiocd_SRCS} ${k3b_project_datacd} ${k3b_project_mixedcd} ${k3b_project_movixcd} ${k3b_project_mpeg_info} ${k3b_project_videocd} ${k3b_project_videodvd} ) if (ENABLE_DVD_RIPPING) set(k3b_LIB_SRCS ${k3b_LIB_SRCS} ${k3b_videodvd_SRCS} ) endif (ENABLE_DVD_RIPPING) if(NOT SAMPLERATE_FOUND) set(k3b_LIB_SRCS ${k3b_LIB_SRCS} ${k3b_samplerate_SRCS}) endif(NOT SAMPLERATE_FOUND) kde4_add_library(k3b SHARED ${k3b_LIB_SRCS}) target_link_libraries(k3b ${KDE4_KDECORE_LIBS} ${KDE4_KUTILS_LIBS} ${KDE4_SOLID_LIBS} ${KDE4_KDE3SUPPORT_LIBS} ${KDE4_KPTY_LIBS} ${KCDDB_LIBRARIES} k3bdevice dl ) if(SAMPLERATE_FOUND) target_link_libraries(k3b ${SAMPLERATE_LIBRARIES}) endif(SAMPLERATE_FOUND) if(ENABLE_DVD_RIPPING) target_link_libraries(k3b ${DVDREAD_LIBRARIES}) endif(ENABLE_DVD_RIPPING) set_target_properties(k3b PROPERTIES VERSION ${K3B_LIB_VERSION_MAJOR}.${K3B_LIB_VERSION_MINOR}.${K3B_LIB_VERSION_RELEASE} SOVERSION ${K3B_LIB_VERSION_MAJOR}) install(TARGETS k3b ${INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/libk3b/core/k3bdefaultexternalprograms.cpp b/libk3b/core/k3bdefaultexternalprograms.cpp index 9c8d0ebb1..28a154df5 100644 --- a/libk3b/core/k3bdefaultexternalprograms.cpp +++ b/libk3b/core/k3bdefaultexternalprograms.cpp @@ -1,1085 +1,1084 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3bdefaultexternalprograms.h" #include "k3bexternalbinmanager.h" #include #include #include #include #include #include #include #include -#include +#include -#include -#include -#include namespace { class ExternalScanner : public KProcess { public: - ExternalScanner( QObject *parent = 0 ); + ExternalScanner( QObject* parent = 0 ); - QString getVersion(const int &pos) const; + QString getVersion( int pos ) const; QByteArray getData() const { return m_data; } bool run(); private: QByteArray m_data; }; - ExternalScanner::ExternalScanner( QObject *parent ) + ExternalScanner::ExternalScanner( QObject* parent ) : KProcess( parent ) { setOutputChannelMode( MergedChannels ); } bool ExternalScanner::run() { start(); if( waitForFinished( -1 ) ) { m_data = readAll(); return true; - } else + } + else { return false; + } } - QString ExternalScanner::getVersion(const int &pos) const + QString ExternalScanner::getVersion( int pos ) const { QString tmp = m_data; int sPos = tmp.indexOf( QRegExp("\\d"), pos ); if( sPos < 0 ) return QString(); int endPos = tmp.indexOf( QRegExp("\\s"), sPos + 1 ); if( endPos < 0 ) return QString(); return tmp.mid( sPos, endPos - sPos ); } } void K3b::addDefaultPrograms( K3b::ExternalBinManager* m ) { m->addProgram( new K3b::CdrecordProgram(false) ); m->addProgram( new K3b::MkisofsProgram() ); m->addProgram( new K3b::ReadcdProgram() ); m->addProgram( new K3b::CdrdaoProgram() ); m->addProgram( new K3b::GrowisofsProgram() ); m->addProgram( new K3b::DvdformatProgram() ); // m->addProgram( new K3b::DvdBooktypeProgram() ); } void K3b::addTranscodePrograms( K3b::ExternalBinManager* m ) { static const char* transcodeTools[] = { "transcode", 0, // K3b 1.0 only uses the transcode binary "tcprobe", "tccat", "tcscan", "tcextract", "tcdecode", 0 }; for( int i = 0; transcodeTools[i]; ++i ) m->addProgram( new K3b::TranscodeProgram( transcodeTools[i] ) ); } void K3b::addVcdimagerPrograms( K3b::ExternalBinManager* m ) { // don't know if we need more vcdTools in the future (vcdxrip) static const char* vcdTools[] = { "vcdxbuild", "vcdxminfo", "vcdxrip", 0 }; for( int i = 0; vcdTools[i]; ++i ) m->addProgram( new K3b::VcdbuilderProgram( vcdTools[i] ) ); } K3b::CdrecordProgram::CdrecordProgram( bool dvdPro ) : K3b::ExternalProgram( dvdPro ? "cdrecord-prodvd" : "cdrecord" ), m_dvdPro(dvdPro) { } // // This is a hack for Debian based systems which use // a wrapper cdrecord script to call cdrecord.mmap or cdrecord.shm // depending on the kernel version. // For 2.0.x and 2.2.x kernels the shm version is used. In all // other cases it's the mmap version. // // But since it may be that someone manually installed cdrecord // replacing the wrapper we check if cdrecord is a script. // static QString& debianWeirdnessHack( QString& path ) { if( QFile::exists( path + ".mmap" ) ) { kDebug() << "(K3b::CdrecordProgram) checking for Debian cdrecord wrapper script."; if( QFileInfo( path ).size() < 1024 ) { kDebug() << "(K3b::CdrecordProgram) Debian Wrapper script size fits. Checking file."; QFile f( path ); f.open( QIODevice::ReadOnly ); QString s = QTextStream( &f ).readAll(); if( s.contains( "cdrecord.mmap" ) && s.contains( "cdrecord.shm" ) ) { kDebug() << "(K3b::CdrecordProgram) Found Debian Wrapper script."; QString ext; if( K3b::kernelVersion().versionString().left(3) > "2.2" ) ext = ".mmap"; else ext = ".shm"; kDebug() << "(K3b::CdrecordProgram) Using cdrecord" << ext; path += ext; } } } return path; } bool K3b::CdrecordProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; bool wodim = false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); if( QFile::exists( path + "wodim" ) ) { wodim = true; path += "wodim"; } else if( QFile::exists( path + "cdrecord" ) ) { path += "cdrecord"; } else return false; } debianWeirdnessHack( path ); K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path << "-version"; if( vp.run() ) { QString out = vp.getData(); int pos = -1; if( wodim ) { pos = out.indexOf( "Wodim" ); } else if( m_dvdPro ) { pos = out.indexOf( "Cdrecord-ProDVD" ); } else { pos = out.indexOf( "Cdrecord" ); } if( pos < 0 ) return false; QString ver = vp.getVersion( pos ); if (ver.isEmpty()) return false; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = ver; if( wodim ) bin->addFeature( "wodim" ); pos = out.indexOf( "Copyright") + 14; int endPos = out.indexOf( "\n", pos ); // cdrecord does not use local encoding for the copyright statement but plain latin1 bin->copyright = QString::fromLatin1( out.mid( pos, endPos-pos ).toLocal8Bit() ).trimmed(); } else { kDebug() << "(K3b::CdrecordProgram) could not start " << path; return false; } if( !m_dvdPro && bin->version.suffix().endsWith( "-dvd" ) ) { bin->addFeature( "dvd-patch" ); bin->version = QString(bin->version.versionString()).remove("-dvd"); } // probe features ExternalScanner fp; fp << path << "-help"; if( fp.run() ) { QByteArray out = fp.getData(); if( out.contains( "gracetime" ) ) bin->addFeature( "gracetime" ); if( out.contains( "-overburn" ) ) bin->addFeature( "overburn" ); if( out.contains( "-text" ) ) bin->addFeature( "cdtext" ); if( out.contains( "-clone" ) ) bin->addFeature( "clone" ); if( out.contains( "-tao" ) ) bin->addFeature( "tao" ); if( out.contains( "cuefile=" ) && ( wodim || bin->version > K3b::Version( 2, 1, -1, "a14") ) ) // cuefile handling was still buggy in a14 bin->addFeature( "cuefile" ); // new mode 2 options since cdrecord 2.01a12 // we use both checks here since the help was not updated in 2.01a12 yet (well, I // just double-checked and the help page is proper but there is no harm in having // two checks) // and the version check does not handle versions like 2.01-dvd properly if( out.contains( "-xamix" ) || bin->version >= K3b::Version( 2, 1, -1, "a12" ) || wodim ) bin->addFeature( "xamix" ); // check if we run cdrecord as root struct stat s; if( !::stat( QFile::encodeName(path), &s ) ) { if( (s.st_mode & S_ISUID) && s.st_uid == 0 ) bin->addFeature( "suidroot" ); } } else { kDebug() << "(K3b::CdrecordProgram) could not start " << bin->path; delete bin; return false; } if( bin->version < K3b::Version( 2, 0 ) && !wodim ) bin->addFeature( "outdated" ); // FIXME: are these version correct? if( bin->version >= K3b::Version("1.11a38") || wodim ) bin->addFeature( "plain-atapi" ); if( bin->version > K3b::Version("1.11a17") || wodim ) bin->addFeature( "hacked-atapi" ); if( bin->version >= K3b::Version( 2, 1, 1, "a02" ) || wodim ) bin->addFeature( "short-track-raw" ); if( bin->version >= K3b::Version( 2, 1, -1, "a13" ) || wodim ) bin->addFeature( "audio-stdin" ); if( bin->version >= K3b::Version( "1.11a02" ) || wodim ) bin->addFeature( "burnfree" ); else bin->addFeature( "burnproof" ); // FIXME: cdrecord Blu-ray support not 100% yet // if ( bin->version >= K3b::Version( 2, 1, 1, "a29" ) && !wodim ) // bin->addFeature( "blu-ray" ); addBin( bin ); return true; } K3b::MkisofsProgram::MkisofsProgram() : K3b::ExternalProgram( "mkisofs" ) { } bool K3b::MkisofsProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; bool genisoimage = false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); if( QFile::exists( path + "genisoimage" ) ) { genisoimage = true; path += "genisoimage"; } else if( QFile::exists( path + "mkisofs" ) ) { path += "mkisofs"; } else return false; } K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path << "-version"; if( vp.run() ) { QString out = vp.getData(); int pos = -1; if( genisoimage ) pos = out.indexOf( "genisoimage" ); else pos = out.indexOf( "mkisofs" ); if( pos < 0 ) return false; QString ver = vp.getVersion( pos ); if( ver.isEmpty() ) return false; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = ver; if( genisoimage ) bin->addFeature( "genisoimage" ); } else { kDebug() << "(K3b::MkisofsProgram) could not start " << path; return false; } // probe features ExternalScanner fp; fp << path << "-help"; if( fp.run() ) { QByteArray out = fp.getData(); if( out.contains( "-udf" ) ) bin->addFeature( "udf" ); if( out.contains( "-dvd-video" ) ) bin->addFeature( "dvd-video" ); if( out.contains( "-joliet-long" ) ) bin->addFeature( "joliet-long" ); if( out.contains( "-xa" ) ) bin->addFeature( "xa" ); if( out.contains( "-sectype" ) ) bin->addFeature( "sectype" ); // check if we run mkisofs as root struct stat s; if( !::stat( QFile::encodeName(path), &s ) ) { if( (s.st_mode & S_ISUID) && s.st_uid == 0 ) bin->addFeature( "suidroot" ); } } else { kDebug() << "(K3b::MkisofsProgram) could not start " << bin->path; delete bin; return false; } if( bin->version < K3b::Version( 1, 14) && !genisoimage ) bin->addFeature( "outdated" ); if( bin->version >= K3b::Version( 1, 15, -1, "a40" ) || genisoimage ) bin->addFeature( "backslashed_filenames" ); if ( genisoimage && bin->version >= K3b::Version( 1, 1, 4 ) ) bin->addFeature( "no-4gb-limit" ); if ( !genisoimage && bin->version >= K3b::Version( 2, 1, 1, "a32" ) ) bin->addFeature( "no-4gb-limit" ); addBin(bin); return true; } K3b::ReadcdProgram::ReadcdProgram() : K3b::ExternalProgram( "readcd" ) { } bool K3b::ReadcdProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; bool readom = false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); if( QFile::exists( path + "readom" ) ) { readom = true; path += "readom"; } else if( QFile::exists( path + "readcd" ) ) { path += "readcd"; } else return false; } if( !QFile::exists( path ) ) return false; K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path << "-version"; if( vp.run() ) { QString out = vp.getData(); int pos = -1; if( readom ) pos = out.indexOf( "readom" ); else pos = out.indexOf( "readcd" ); if( pos < 0 ) return false; QString ver = vp.getVersion( pos ); if( ver.isEmpty() ) return false; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = ver; if( readom ) bin->addFeature( "readom" ); } else { kDebug() << "(K3b::MkisofsProgram) could not start " << path; return false; } // probe features ExternalScanner fp; fp << path << "-help"; if( fp.run() ) { QByteArray out = fp.getData(); if( out.contains( "-clone" ) ) bin->addFeature( "clone" ); // check if we run mkisofs as root struct stat s; if( !::stat( QFile::encodeName(path), &s ) ) { if( (s.st_mode & S_ISUID) && s.st_uid == 0 ) bin->addFeature( "suidroot" ); } } else { kDebug() << "(K3b::ReadcdProgram) could not start " << bin->path; delete bin; return false; } // FIXME: are these version correct? if( bin->version >= K3b::Version("1.11a38") || readom ) bin->addFeature( "plain-atapi" ); if( bin->version > K3b::Version("1.11a17") || readom ) bin->addFeature( "hacked-atapi" ); addBin(bin); return true; } K3b::CdrdaoProgram::CdrdaoProgram() : K3b::ExternalProgram( "cdrdao" ) { } bool K3b::CdrdaoProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); path.append("cdrdao"); } if( !QFile::exists( path ) ) return false; K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path ; if( vp.run() ) { QString out = vp.getData(); int pos = out.indexOf( "Cdrdao version" ); if( pos < 0 ) return false; QString ver = vp.getVersion( pos ); if( ver.isEmpty() ) return false; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = ver; int endPos = out.indexOf( QRegExp("[0-9]"), pos ); endPos = out.indexOf( QRegExp("\\s"), endPos + 1 ); pos = out.indexOf( "(C)", endPos+1 ) + 4; endPos = out.indexOf( '\n', pos ); bin->copyright = out.mid( pos, endPos-pos ); } else { kDebug() << "(K3b::CdrdaoProgram) could not start " << path; return false; } // probe features ExternalScanner fp; fp << path << "write" << "-h"; if( fp.run() ) { QByteArray out = fp.getData(); if( out.contains( "--overburn" ) ) bin->addFeature( "overburn" ); if( out.contains( "--multi" ) ) bin->addFeature( "multisession" ); if( out.contains( "--buffer-under-run-protection" ) ) bin->addFeature( "disable-burnproof" ); // check if we run cdrdao as root struct stat s; if( !::stat( QFile::encodeName(path), &s ) ) { if( (s.st_mode & S_ISUID) && s.st_uid == 0 ) bin->addFeature( "suidroot" ); } } else { kDebug() << "(K3b::CdrdaoProgram) could not start " << bin->path; delete bin; return false; } // SuSE 9.0 ships with a patched cdrdao 1.1.7 which contains an updated libschily // Gentoo ships with a patched cdrdao 1.1.7 which contains scglib support if( bin->version > K3b::Version( 1, 1, 7 ) || bin->version == K3b::Version( 1, 1, 7, "-gentoo" ) || bin->version == K3b::Version( 1, 1, 7, "-suse" ) ) { // bin->addFeature( "plain-atapi" ); bin->addFeature( "hacked-atapi" ); } if( bin->version >= K3b::Version( 1, 1, 8 ) ) bin->addFeature( "plain-atapi" ); addBin(bin); return true; } K3b::TranscodeProgram::TranscodeProgram( const QString& transcodeProgram ) : K3b::ExternalProgram( transcodeProgram ), m_transcodeProgram( transcodeProgram ) { } bool K3b::TranscodeProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; if( path[path.length()-1] != '/' ) path.append("/"); QString appPath = path + m_transcodeProgram; if( !QFile::exists( appPath ) ) return false; K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << appPath << "-v"; if( vp.run() ) { QString out = vp.getData(); int pos = out.indexOf( "transcode v" ); if( pos < 0 ) return false; pos += 11; int endPos = out.indexOf( QRegExp("[\\s\\)]"), pos+1 ); if( endPos < 0 ) return false; bin = new K3b::ExternalBin( this ); bin->path = appPath; bin->version = out.mid( pos, endPos-pos ); } else { kDebug() << "(K3b::TranscodeProgram) could not start " << appPath; return false; } // // Check features // QString modInfoBin = path + "tcmodinfo"; ExternalScanner modp; modp << modInfoBin << "-p"; if( modp.run() ) { QString modPath = modp.getData().simplified(); QDir modDir( modPath ); if( !modDir.entryList( QStringList() << "*export_xvid*", QDir::Files ).isEmpty() ) bin->addFeature( "xvid" ); if( !modDir.entryList( QStringList() << "*export_lame*", QDir::Files ).isEmpty() ) bin->addFeature( "lame" ); if( !modDir.entryList( QStringList() << "*export_ffmpeg*", QDir::Files ).isEmpty() ) bin->addFeature( "ffmpeg" ); if( !modDir.entryList( QStringList() << "*export_ac3*", QDir::Files ).isEmpty() ) bin->addFeature( "ac3" ); } addBin(bin); return true; } K3b::VcdbuilderProgram::VcdbuilderProgram( const QString& p ) : K3b::ExternalProgram( p ), m_vcdbuilderProgram( p ) { } bool K3b::VcdbuilderProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); path.append(m_vcdbuilderProgram); } if( !QFile::exists( path ) ) return false; K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path << "-V"; if( vp.run() ) { QString out = vp.getData(); int pos = out.indexOf( "GNU VCDImager" ); if( pos < 0 ) return false; pos += 14; int endPos = out.indexOf( QRegExp("[\\n\\)]"), pos+1 ); if( endPos < 0 ) return false; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = out.mid( pos, endPos-pos ).trimmed(); pos = out.indexOf( "Copyright" ) + 14; endPos = out.indexOf( "\n", pos ); bin->copyright = out.mid( pos, endPos-pos ).trimmed(); } else { kDebug() << "(K3b::VcdbuilderProgram) could not start " << path; return false; } addBin(bin); return true; } K3b::NormalizeProgram::NormalizeProgram() : K3b::ExternalProgram( "normalize" ) { } bool K3b::NormalizeProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); path.append("normalize"); } if( !QFile::exists( path ) ) return false; K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path << "--version"; if( vp.run() ) { QString out = vp.getData(); int pos = out.indexOf( "normalize" ); if( pos < 0 ) return false; QString ver = vp.getVersion( pos ); if( ver.isEmpty() ) return false; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = ver; pos = out.indexOf( "Copyright" )+14; int endPos = out.indexOf( "\n", pos ); bin->copyright = out.mid( pos, endPos-pos ).trimmed(); } else { kDebug() << "(K3b::CdrecordProgram) could not start " << path; return false; } addBin( bin ); return true; } K3b::GrowisofsProgram::GrowisofsProgram() : K3b::ExternalProgram( "growisofs" ) { } bool K3b::GrowisofsProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); path.append("growisofs"); } if( !QFile::exists( path ) ) return false; K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path << "-version"; if( vp.run() ) { QString out = vp.getData(); int pos = out.indexOf( "growisofs" ); if( pos < 0 ) return false; pos = out.indexOf( QRegExp("\\d"), pos ); if( pos < 0 ) return false; int endPos = out.indexOf( ',', pos+1 ); if( endPos < 0 ) return false; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = out.mid( pos, endPos-pos ); } else { kDebug() << "(K3b::GrowisofsProgram) could not start " << path; return false; } // fixed Copyright: bin->copyright = "Andy Polyakov "; // check if we run growisofs as root struct stat s; if( !::stat( QFile::encodeName(path), &s ) ) { if( (s.st_mode & S_ISUID) && s.st_uid == 0 ) bin->addFeature( "suidroot" ); } if ( bin->version >= K3b::Version( 5, 20 ) ) bin->addFeature( "dual-layer" ); if ( bin->version > K3b::Version( 5, 17 ) ) bin->addFeature( "tracksize" ); if ( bin->version >= K3b::Version( 5, 15 ) ) bin->addFeature( "daosize" ); if ( bin->version >= K3b::Version( 6, 0 ) ) bin->addFeature( "buffer" ); if ( bin->version >= K3b::Version( 7, 0 ) ) bin->addFeature( "blu-ray" ); addBin( bin ); return true; } K3b::DvdformatProgram::DvdformatProgram() : K3b::ExternalProgram( "dvd+rw-format" ) { } bool K3b::DvdformatProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); path.append("dvd+rw-format"); } if( !QFile::exists( path ) ) return false; K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path; if( vp.run() ) { // different locales make searching for the +- char difficult // so we simply ignore it. QString out = vp.getData(); int pos = out.indexOf( QRegExp("DVD.*RW(/-RAM)? format utility") ); if( pos < 0 ) return false; pos = out.indexOf( "version", pos ); if( pos < 0 ) return false; pos += 8; // the version ends in a dot. int endPos = out.indexOf( QRegExp("\\.\\D"), pos ); if( endPos < 0 ) return false; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = out.mid( pos, endPos-pos ); } else { kDebug() << "(K3b::DvdformatProgram) could not start " << path; return false; } // fixed Copyright: bin->copyright = "Andy Polyakov "; // check if we run dvd+rw-format as root struct stat s; if( !::stat( QFile::encodeName(path), &s ) ) { if( (s.st_mode & S_ISUID) && s.st_uid == 0 ) bin->addFeature( "suidroot" ); } addBin( bin ); return true; } K3b::DvdBooktypeProgram::DvdBooktypeProgram() : K3b::ExternalProgram( "dvd+rw-booktype" ) { } bool K3b::DvdBooktypeProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); path.append("dvd+rw-booktype"); } if( !QFile::exists( path ) ) return false; K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path; if( vp.run() ) { QString out = vp.getData(); int pos = out.indexOf( "dvd+rw-booktype" ); if( pos < 0 ) return false; bin = new K3b::ExternalBin( this ); bin->path = path; // No version information. Create dummy version bin->version = K3b::Version( 1, 0, 0 ); } else { kDebug() << "(K3b::DvdBooktypeProgram) could not start " << path; return false; } addBin( bin ); return true; } K3b::Cdda2wavProgram::Cdda2wavProgram() : K3b::ExternalProgram( "cdda2wav" ) { } bool K3b::Cdda2wavProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); path.append("cdda2wav"); } if( !QFile::exists( path ) ) return false; K3b::ExternalBin* bin = 0; // probe version ExternalScanner vp; vp << path << "-h"; if( vp.run() ) { QString out = vp.getData(); int pos = out.indexOf( "cdda2wav" ); if( pos < 0 ) return false; pos = out.indexOf( "Version", pos ); if( pos < 0 ) return false; pos += 8; // the version does not end in a space but the kernel info int endPos = out.indexOf( QRegExp("[^\\d\\.]"), pos ); if( endPos < 0 ) return false; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = out.mid( pos, endPos-pos ); // features (we do this since the cdda2wav help says that the short // options will disappear soon) if( out.indexOf( "-info-only" ) ) bin->addFeature( "info-only" ); // otherwise use the -J option if( out.indexOf( "-no-infofile" ) ) bin->addFeature( "no-infofile" ); // otherwise use the -H option if( out.indexOf( "-gui" ) ) bin->addFeature( "gui" ); // otherwise use the -g option if( out.indexOf( "-bulk" ) ) bin->addFeature( "bulk" ); // otherwise use the -B option if( out.indexOf( "dev=" ) ) bin->addFeature( "dev" ); // otherwise use the -B option } else { kDebug() << "(K3b::Cdda2wavProgram) could not start " << path; return false; } // check if we run as root struct stat s; if( !::stat( QFile::encodeName(path), &s ) ) { if( (s.st_mode & S_ISUID) && s.st_uid == 0 ) bin->addFeature( "suidroot" ); } addBin( bin ); return true; } diff --git a/libk3b/core/k3bglobals.cpp b/libk3b/core/k3bglobals.cpp index f23b04cc1..38dc8fbe4 100644 --- a/libk3b/core/k3bglobals.cpp +++ b/libk3b/core/k3bglobals.cpp @@ -1,604 +1,603 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 - #include "k3bglobals.h" #include "k3bglobalsettings.h" #include "k3bversion.h" #include "k3bdevice.h" #include "k3bdevicemanager.h" #include "k3bdeviceglobals.h" #include "k3bexternalbinmanager.h" #include "k3bcore.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) # include # include # include # define bswap_16(x) bswap16(x) # define bswap_32(x) bswap32(x) # define bswap_64(x) bswap64(x) #else # include #endif #ifdef HAVE_SYS_STATVFS_H # include #endif #ifdef HAVE_SYS_VFS_H # include #endif /* struct Sample { unsigned char msbLeft; unsigned char lsbLeft; unsigned char msbRight; unsigned char lsbRight; short left() const { return ( msbLeft << 8 ) | lsbLeft; } short right() const { return ( msbRight << 8 ) | lsbRight; } void left( short d ) { msbLeft = d >> 8; lsbLeft = d; } void right( short d ) { msbRight = d >> 8; lsbRight = d; } }; */ QString K3b::framesToString( int h, bool showFrames ) { int m = h / 4500; int s = (h % 4500) / 75; int f = h % 75; QString str; if( showFrames ) { // cdrdao needs the MSF format where 1 second has 75 frames! str.sprintf( "%.2i:%.2i:%.2i", m, s, f ); } else str.sprintf( "%.2i:%.2i", m, s ); return str; } /*QString K3b::sizeToTime(long size) { int h = size / sizeof(Sample) / 588; return framesToString(h, false); }*/ qint16 K3b::swapByteOrder( const qint16& i ) { return bswap_16( i ); //((i << 8) & 0xff00) | ((i >> 8 ) & 0xff); } qint32 K3b::swapByteOrder( const qint32& i ) { //return ((i << 24) & 0xff000000) | ((i << 8) & 0xff0000) | ((i >> 8) & 0xff00) | ((i >> 24) & 0xff ); return bswap_32( i ); } qint64 K3b::swapByteOrder( const qint64& i ) { return bswap_64( i ); } int K3b::round( double d ) { return (int)( floor(d) + 0.5 <= d ? ceil(d) : floor(d) ); } QString K3b::findUniqueFilePrefix( const QString& _prefix, const QString& path ) { QString url; if( path.isEmpty() || !QFile::exists(path) ) url = defaultTempPath(); else url = prepareDir( path ); QString prefix = _prefix; if( prefix.isEmpty() ) prefix = "k3b_"; // now create the unique prefix QDir dir( url ); QStringList entries = dir.entryList( QDir::NoFilter, QDir::Name ); int i = 0; for( QStringList::iterator it = entries.begin(); it != entries.end(); ++it ) { if( (*it).startsWith( prefix + QString::number(i) ) ) { i++; it = entries.begin(); } } return url + prefix + QString::number(i); } QString K3b::findTempFile( const QString& ending, const QString& d ) { return findUniqueFilePrefix( "k3b_", d ) + ( ending.isEmpty() ? QString() : (QString::fromLatin1(".") + ending) ); } QString K3b::defaultTempPath() { return prepareDir( k3bcore->globalSettings()->defaultTempPath() ); } QString K3b::prepareDir( const QString& dir ) { if(dir.isEmpty()) return QString(); else if ( !dir.endsWith( '/' ) ) return dir + '/'; else return dir; } QString K3b::parentDir( const QString& path ) { QString parent = path; if( path.isEmpty()) return QString(); if( path[path.length()-1] == '/' ) parent.truncate( parent.length()-1 ); int pos = parent.lastIndexOf( '/' ); if( pos >= 0 ) parent.truncate( pos+1 ); else // relative path, do anything... parent = "/"; return parent; } QString K3b::fixupPath( const QString& path ) { QString s; bool lastWasSlash = false; for( int i = 0; i < path.length(); ++i ) { if( path[i] == '/' ) { if( !lastWasSlash ) { lastWasSlash = true; s.append( "/" ); } } else { lastWasSlash = false; s.append( path[i] ); } } return s; } K3b::Version K3b::kernelVersion() { // initialize kernel version K3b::Version v; utsname unameinfo; if( ::uname(&unameinfo) == 0 ) { v = QString::fromLocal8Bit( unameinfo.release ); kDebug() << "kernel version: " << v; } else kError() << "could not determine kernel version." ; return v; } K3b::Version K3b::simpleKernelVersion() { return kernelVersion().simplify(); } QString K3b::systemName() { QString v; utsname unameinfo; if( ::uname(&unameinfo) == 0 ) { v = QString::fromLocal8Bit( unameinfo.sysname ); } else kError() << "could not determine system name." ; return v; } bool K3b::kbFreeOnFs( const QString& path, unsigned long& size, unsigned long& avail ) { #ifdef HAVE_SYS_STATVFS_H struct statvfs fs; if( ::statvfs( QFile::encodeName(path), &fs ) == 0 ) { unsigned long kBfak = fs.f_frsize/1024; size = fs.f_blocks*kBfak; avail = fs.f_bavail*kBfak; return true; } else #endif return false; } KIO::filesize_t K3b::filesize( const KUrl& url ) { KIO::filesize_t fSize = 0; if( url.isLocalFile() ) { k3b_struct_stat buf; k3b_stat( QFile::encodeName( url.path() ), &buf ); fSize = (KIO::filesize_t)buf.st_size; } else { KIO::UDSEntry uds; KIO::NetAccess::stat( url, uds, 0 ); fSize = uds.numberValue( KIO::UDSEntry::UDS_SIZE ); } return fSize; } KIO::filesize_t K3b::imageFilesize( const KUrl& url ) { KIO::filesize_t size = K3b::filesize( url ); int cnt = 0; while( KIO::NetAccess::exists( url.url() + '.' + QString::number(cnt).rightJustified( 3, '0' ), KIO::NetAccess::SourceSide, 0 ) ) size += K3b::filesize( url.url() + '.' + QString::number(cnt++).rightJustified( 3, '0' ) ); return size; } QString K3b::cutFilename( const QString& name, int len ) { if( name.length() > len ) { QString ret = name; // determine extension (we think of an extension to be at most 5 chars in length) int pos = name.indexOf( '.', -6 ); if( pos > 0 ) len -= (name.length() - pos); ret.truncate( len ); if( pos > 0 ) ret.append( name.mid( pos ) ); return ret; } else return name; } QString K3b::removeFilenameExtension( const QString& name ) { QString v = name; int dotpos = v.lastIndexOf( '.' ); if( dotpos > 0 ) v.truncate( dotpos ); return v; } QString K3b::appendNumberToFilename( const QString& name, int num, unsigned int maxlen ) { // determine extension (we think of an extension to be at most 5 chars in length) QString result = name; QString ext; int pos = name.indexOf( '.', -6 ); if( pos > 0 ) { ext = name.mid(pos); result.truncate( pos ); } ext.prepend( QString::number(num) ); result.truncate( maxlen - ext.length() ); return result + ext; } bool K3b::plainAtapiSupport() { // FIXME: what about BSD? return ( K3b::simpleKernelVersion() >= K3b::Version( 2, 5, 40 ) ); } bool K3b::hackedAtapiSupport() { // IMPROVEME!!! // FIXME: since when does the kernel support this? return ( K3b::simpleKernelVersion() >= K3b::Version( 2, 4, 0 ) ); } QString K3b::externalBinDeviceParameter( K3b::Device::Device* dev, const K3b::ExternalBin* bin ) { Q_UNUSED( bin ); return dev->blockDeviceName(); } K3b::WritingApp K3b::writingAppFromString( const QString& s ) { if( s.toLower() == "cdrdao" ) return K3b::WRITING_APP_CDRDAO; else if( s.toLower() == "cdrecord" ) return K3b::WRITING_APP_CDRECORD; else if( s.toLower() == "growisofs" ) return K3b::WRITING_APP_GROWISOFS; else if( s.toLower() == "dvd+rw-format" ) return K3b::WRITING_APP_DVD_RW_FORMAT; else return K3b::WRITING_APP_DEFAULT; } QString K3b::writingAppToString( K3b::WritingApp app ) { switch( app ) { case WRITING_APP_CDRECORD: return "cdrecord"; case WRITING_APP_CDRDAO: return "cdrdao"; case WRITING_APP_GROWISOFS: return "growisofs"; case WRITING_APP_DVD_RW_FORMAT: return "dvd+rw-format"; default: return "auto"; } } QString K3b::writingModeString( K3b::WritingModes modes ) { if( modes == WRITING_MODE_AUTO ) return i18n("Auto"); else return K3b::Device::writingModeString( ( int )modes ); } QString K3b::resolveLink( const QString& file ) { QFileInfo f( file ); return f.canonicalFilePath(); } KUrl K3b::convertToLocalUrl( const KUrl& url ) { if( !url.isLocalFile() ) { return KIO::NetAccess::mostLocalUrl( url, 0 ); } return url; } KUrl::List K3b::convertToLocalUrls( const KUrl::List& urls ) { KUrl::List r; for( KUrl::List::const_iterator it = urls.constBegin(); it != urls.constEnd(); ++it ) r.append( convertToLocalUrl( *it ) ); return r; } qint16 K3b::fromLe16( char* data ) { #ifdef WORDS_BIGENDIAN // __BYTE_ORDER == __BIG_ENDIAN return swapByteOrder( *((qint16*)data) ); #else return *((qint16*)data); #endif } qint32 K3b::fromLe32( char* data ) { #ifdef WORDS_BIGENDIAN // __BYTE_ORDER == __BIG_ENDIAN return swapByteOrder( *((qint32*)data) ); #else return *((qint32*)data); #endif } qint64 K3b::fromLe64( char* data ) { #ifdef WORDS_BIGENDIAN // __BYTE_ORDER == __BIG_ENDIAN return swapByteOrder( *((qint64*)data) ); #else return *((qint64*)data); #endif } QString K3b::findExe( const QString& name ) { // first we search the path QString bin = KStandardDirs::findExe( name ); // then go on with our own little list if( bin.isEmpty() ) bin = KStandardDirs::findExe( name, k3bcore->externalBinManager()->searchPath().join(":") ); return bin; } bool K3b::isMounted( K3b::Device::Device* dev ) { if( !dev ) return false; else return( KMountPoint::currentMountPoints().findByDevice( dev->blockDeviceName() ).data() != 0 ); } bool K3b::unmount( K3b::Device::Device* dev ) { if( !dev ) return false; QString mntDev = dev->blockDeviceName(); // first try to unmount it the standard way if( KIO::NetAccess::synchronousRun( KIO::unmount( mntDev, false ), 0 ) ) return true; Solid::StorageAccess *sa = dev->solidStorage(); if ( sa && sa->teardown() ){ return true; } QString mntPath = KMountPoint::currentMountPoints().findByDevice( dev->blockDeviceName() )->mountPoint(); if ( mntPath.isEmpty() ) { mntPath = dev->blockDeviceName(); } QString umountBin = K3b::findExe( "umount" ); if( !umountBin.isEmpty() ) { KProcess p; p << umountBin; p << "-l"; // lazy unmount p << mntPath; p.start(); if (p.waitForFinished(-1)) return true; } // now try pmount QString pumountBin = K3b::findExe( "pumount" ); if( !pumountBin.isEmpty() ) { KProcess p; p << pumountBin; p << "-l"; // lazy unmount p << mntPath; p.start(); return p.waitForFinished(-1); } else { return false; } } bool K3b::mount( K3b::Device::Device* dev ) { if( !dev ) return false; QString mntDev = dev->blockDeviceName(); // first try to mount it the standard way if( KIO::NetAccess::synchronousRun( KIO::mount( true, 0, mntDev, false ), 0 ) ) return true; Solid::StorageAccess* sa = dev->solidStorage(); if ( sa && sa->setup() ) { return true; } // now try pmount QString pmountBin = K3b::findExe( "pmount" ); if( !pmountBin.isEmpty() ) { KProcess p; p << pmountBin; p << mntDev; p.start(); return p.waitForFinished(-1); } // and the most simple one QString mountBin = K3b::findExe( "mount" ); if( !mountBin.isEmpty() ) { KProcess p; p << mountBin; p << mntDev; p.start(); return p.waitForFinished(-1); } return false; } bool K3b::eject( K3b::Device::Device* dev ) { if( K3b::isMounted( dev ) ) K3b::unmount( dev ); if ( dev->solidDevice().as()->eject() ) { return true; } else { return dev->eject(); } } diff --git a/libk3b/core/k3bprocess.cpp b/libk3b/core/k3bprocess.cpp index bc1a55d34..862e06ca7 100644 --- a/libk3b/core/k3bprocess.cpp +++ b/libk3b/core/k3bprocess.cpp @@ -1,280 +1,214 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3bprocess.h" #include "k3bexternalbinmanager.h" #include -#include #include -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +namespace { + QStringList splitOutput( const QByteArray& data, + QString& unfinishedLine, + bool suppressEmptyLines ) + { + // + // The stderr splitting is mainly used for parsing of messages + // That's why we simplify the data before proceeding + // + int len = data.length(); + + QByteArray buffer; + for( int i = 0; i < len; i++ ) { + if( data[i] == '\b' ) { + while( i < len && + data[i] == '\b' ) // we replace multiple backspaces with a single line feed + i++; + buffer += '\n'; + } + if ( i < len ) { + if( data[i] == '\r' ) + buffer += '\n'; + else if( data[i] == '\t' ) // replace tabs with a single space + buffer += " "; + else + buffer += data[i]; + } + } + + QStringList lines = QString::fromLocal8Bit( buffer ).split( '\n', suppressEmptyLines ? QString::SkipEmptyParts : QString::KeepEmptyParts ); + + // in case we suppress empty lines we need to handle the following separately + // to make sure we join unfinished lines correctly + if( suppressEmptyLines && buffer.startsWith( '\n' ) ) + lines.prepend( QString() ); -class K3b::Process::Data + if( !unfinishedLine.isEmpty() ) { + lines.first().prepend( unfinishedLine ); + unfinishedLine.truncate(0); + + kDebug() << "(K3b::Process) joined line: '" << (lines.first()) << "'"; + } + + QStringList::iterator it; + + // check if line ends with a newline + // if not save the last line because it is not finished + if ( !buffer.isEmpty() ) { + QByteRef c = buffer[buffer.length()-1]; + bool hasUnfinishedLine = ( c != '\n' && c != '\r' && QChar( c ) != QChar(46) ); // What is unicode 46?? It is printed as a point + if( hasUnfinishedLine ) { + kDebug() << "(K3b::Process) found unfinished line: '" << lines.last() << "'"; + kDebug() << "(K3b::Process) last char: '" << buffer.right(1) << "'"; + unfinishedLine = lines.takeLast(); + } + } + + return lines; + } +} + + +class K3b::Process::Private { public: QString unfinishedStdoutLine; QString unfinishedStderrLine; - int dupStdoutFd; - int dupStdinFd; - - bool rawStdin; - bool rawStdout; - bool suppressEmptyLines; + + bool bSplitStdout; }; -K3b::Process::Process() - : K3Process(), - m_bSplitStdout(false) +K3b::Process::Process( QObject* parent ) + : K3bKProcess( parent ), + d( new Private() ) { - d = new Data(); - d->dupStdinFd = d->dupStdoutFd = -1; - d->rawStdout = d->rawStdin = false; + setNextOpenMode( ReadWrite|Unbuffered ); d->suppressEmptyLines = true; + d->bSplitStdout = false; + + connect( this, SIGNAL(readyReadStandardError()), + this, SLOT(slotReadyReadStandardError()) ); + connect( this, SIGNAL(readyReadStandardOutput()), + this, SLOT(slotReadyReadStandardOutput()) ); } + K3b::Process::~Process() { delete d; } K3b::Process& K3b::Process::operator<<( const K3b::ExternalBin* bin ) { - return this->operator<<( bin->path ); + return static_cast( K3bKProcess::operator<<( bin->path ) ); } -K3b::Process& K3b::Process::operator<<( const QString& arg ) -{ - static_cast(this)->operator<<( arg ); - return *this; -} K3b::Process& K3b::Process::operator<<( const char* arg ) { - static_cast(this)->operator<<( arg ); - return *this; + return static_cast( K3bKProcess::operator<<( QLatin1String( arg ) ) ); } + K3b::Process& K3b::Process::operator<<( const QByteArray& arg ) { - static_cast(this)->operator<<( arg ); - return *this; + return static_cast( K3bKProcess::operator<<( QLatin1String( arg ) ) ); } -K3b::Process& K3b::Process::operator<<( const QStringList& args ) + +K3b::Process& K3b::Process::operator<<( const QLatin1String& arg ) { - static_cast(this)->operator<<( args ); - return *this; + return static_cast( K3bKProcess::operator<<( arg ) ); } -bool K3b::Process::start( Communication com ) +void K3b::Process::setSplitStdout( bool b ) { - connect( this, SIGNAL(receivedStderr(K3Process*, char*, int)), - this, SLOT(slotSplitStderr(K3Process*, char*, int)) ); - connect( this, SIGNAL(receivedStdout(K3Process*, char*, int)), - this, SLOT(slotSplitStdout(K3Process*, char*, int)) ); - connect( this, SIGNAL( processExited(K3Process*) ), - this, SLOT( slotProcessExited(K3Process*) ) ); - - return K3Process::start( NotifyOnExit, com ); + d->bSplitStdout = b; } -void K3b::Process::slotSplitStdout( K3Process*, char* data, int len ) + +void K3b::Process::slotReadyReadStandardOutput() { - if( m_bSplitStdout ) { - QStringList lines = splitOutput( data, len, d->unfinishedStdoutLine, d->suppressEmptyLines ); + if( d->bSplitStdout ) { + QStringList lines = splitOutput( readAllStandardOutput(), d->unfinishedStdoutLine, d->suppressEmptyLines ); for( QStringList::iterator it = lines.begin(); it != lines.end(); ++it ) { QString& str = *it; // just to be sure since something in splitOutput does not do this right if( d->suppressEmptyLines && str.isEmpty() ) continue; emit stdoutLine( str ); } } - - if( d->dupStdoutFd != -1 ) { - ::write( d->dupStdoutFd, data, len ); - } } -void K3b::Process::slotSplitStderr( K3Process*, char* data, int len ) +void K3b::Process::slotReadyReadStandardError() { - QStringList lines = splitOutput( data, len, d->unfinishedStderrLine, d->suppressEmptyLines ); + QStringList lines = splitOutput( readAllStandardError(), d->unfinishedStderrLine, d->suppressEmptyLines ); for( QStringList::iterator it = lines.begin(); it != lines.end(); ++it ) { QString& str = *it; // just to be sure since something in splitOutput does not do this right if( d->suppressEmptyLines && str.isEmpty() ) continue; emit stderrLine( str ); } } -void K3b::Process::slotProcessExited( K3Process* ) -{ - emit finished( exitStatus(), normalExit() ? QProcess::NormalExit : QProcess::CrashExit ); -} - - -QStringList K3b::Process::splitOutput( char* data, int len, - QString& unfinishedLine, bool suppressEmptyLines ) -{ - // - // The stderr splitting is mainly used for parsing of messages - // That's why we simplify the data before proceeding - // - - QString buffer; - for( int i = 0; i < len; i++ ) { - if( data[i] == '\b' ) { - while( data[i] == '\b' ) // we replace multiple backspaces with a single line feed - i++; - buffer += '\n'; - } - if( data[i] == '\r' ) - buffer += '\n'; - else if( data[i] == '\t' ) // replace tabs with a single space - buffer += " "; - else - buffer += data[i]; - } - - QStringList lines = buffer.split( '\n', suppressEmptyLines ? QString::SkipEmptyParts : QString::KeepEmptyParts ); - - // in case we suppress empty lines we need to handle the following separately - // to make sure we join unfinished lines correctly - if( suppressEmptyLines && buffer[0] == '\n' ) - lines.prepend( QString() ); - - if( !unfinishedLine.isEmpty() ) { - lines.first().prepend( unfinishedLine ); - unfinishedLine.truncate(0); - - kDebug() << "(K3b::Process) joined line: '" << (lines.first()) << "'"; - } - - QStringList::iterator it; - - // check if line ends with a newline - // if not save the last line because it is not finished - QChar c = buffer.right(1).at(0); - bool hasUnfinishedLine = ( c != '\n' && c != '\r' && c != QChar(46) ); // What is unicode 46?? It is printed as a point - if( hasUnfinishedLine ) { - kDebug() << "(K3b::Process) found unfinished line: '" << lines.last() << "'"; - kDebug() << "(K3b::Process) last char: '" << buffer.right(1) << "'"; - unfinishedLine = lines.takeLast(); - } - - return lines; -} - - -int K3b::Process::stdinFd() const -{ - if( d->rawStdin ) - return K3Process::in[1]; - else if( d->dupStdinFd != -1 ) - return d->dupStdinFd; - else - return -1; -} - -void K3b::Process::writeToFd( int fd ) -{ - d->dupStdoutFd = fd; - if( fd != -1 ) - d->rawStdout = false; -} - -void K3b::Process::readFromFd( int fd ) -{ - d->dupStdinFd = fd; - if( fd != -1 ) - d->rawStdin = false; -} - - -void K3b::Process::setRawStdin(bool b) -{ - if( b ) { - d->rawStdin = true; - d->dupStdinFd = -1; - } - else - d->rawStdin = false; -} - - void K3b::Process::setSuppressEmptyLines( bool b ) { d->suppressEmptyLines = b; } -void K3b::Process::closeWriteChannel() +QString K3b::Process::joinedArgs() { - ::close( stdinFd() ); + return program().join( " " ); } -bool K3b::Process::waitForFinished(int timeout) -{ - Q_ASSERT( timeout == -1 ); - - ::waitpid( pid(), 0, 0 ); - - return true; -} -qint64 K3b::Process::write(const char * data, qint64 maxSize) +void K3b::Process::close() { - return ::write( stdinFd(), data, maxSize); + kDebug(); + closeWriteChannel(); } -QString K3b::Process::joinedArgs() + +bool K3b::Process::start( KProcess::OutputChannelMode mode ) { - QList a = args(); - QString s; - Q_FOREACH( QByteArray arg, a ) { - s += QString::fromLocal8Bit( arg ) + " "; - } - return s; + kDebug(); + setOutputChannelMode( mode ); + K3bKProcess::start(); + kDebug() << "started"; + return K3bQProcess::waitForStarted(); } #include "k3bprocess.moc" diff --git a/libk3b/core/k3bprocess.h b/libk3b/core/k3bprocess.h index 204d48ed9..ed5356851 100644 --- a/libk3b/core/k3bprocess.h +++ b/libk3b/core/k3bprocess.h @@ -1,161 +1,100 @@ /* * - * Copyright (C) 2003-2008 Sebastian Trueg + * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3B_PROCESS_H #define K3B_PROCESS_H -#include "k3process.h" -#include -#include +//#include +#include "k3bkprocess.h" #include "k3b_export.h" namespace K3b { class ExternalBin; /** * This is an enhanced K3Process. * It splits the stderr output to lines making sure the client gets every line as it * was written by the process. * Aditionally one may set raw stdout and stdin handling using the stdin() and stdout() methods * to get the process' file descriptors. * Last but not least Process is able to duplicate stdout making it possible to connect two * Processes like used in DataJob to duplicate mkisofs' stdout to the stdin of the writer * (cdrecord or cdrdao) */ - class LIBK3B_EXPORT Process : public K3Process + class LIBK3B_EXPORT Process : public K3bKProcess { Q_OBJECT public: - Process(); + Process( QObject* parent = 0 ); ~Process(); /** * In the future this might also set the nice value */ Process& operator<<( const ExternalBin* ); - Process& operator<<( const QString& arg ); Process& operator<<( const char* arg ); Process& operator<<( const QByteArray& arg ); - Process& operator<<( const QStringList& args ); - - bool start( Communication com ); - - /** - * get stdin file descriptor - * Only makes sense while process is running. - * - * Only use with setRawStdin - */ - int stdinFd() const; + Process& operator<<( const QLatin1String& arg ); /** - * Make the process write to @fd instead of Stdout. - * This means you won't get any stdoutReady() or receivedStdout() - * signals anymore. - * - * Only use this before starting the process. + * returned joined list of program arguments */ - void writeToFd( int fd ); + QString joinedArgs(); - /** - * Make the process read from @fd instead of Stdin. - * This means you won't get any wroteStdin() - * signals anymore. - * - * Only use this before starting the process. - */ - void readFromFd( int fd ); + bool isRunning() const { return state() == QProcess::Running; } /** - * If set true the process' stdin fd will be available - * through @stdinFd. - * Be aware that you will not get any wroteStdin signals - * anymore. - * - * Only use this before starting the process. + * Reimplemented from QProcess. + * Closes the write channel but does not kill the process + * as QProcess does. */ - void setRawStdin(bool b); + void close(); /** - * close stdin channel - * - * Once this class is ported to use KProcess instead of K3Process this - * method can be deleted and the QProcess::closeWriteChannel() can be - * called directly. + * Starts the process in \p mode and then waits for it + * to be started. */ - void closeWriteChannel(); + bool start( KProcess::OutputChannelMode mode ); - /** - * wait until process exited - * - * Once this class is ported to use KProcess instead of K3Process this - * method can be deleted and the QProcess::waitForFinished() can be - * called directly. - * - * The timeout value MUST be -1 for now as everything else is not implemented. - */ - bool waitForFinished(int timeout); - - /** - * write data to stdin - * - * Once this class is ported to use KProcess instead of K3Process this - * method can be deleted and the QProcess::write() can be called directly. - */ - qint64 write(const char * data, qint64 maxSize); - - /** - * returned joined list of program arguments - */ - QString joinedArgs(); + using K3bKProcess::operator<<; public Q_SLOTS: - void setSplitStdout( bool b ) { m_bSplitStdout = b; } + void setSplitStdout( bool b ); /** * default is true */ void setSuppressEmptyLines( bool b ); private Q_SLOTS: - void slotSplitStderr( K3Process*, char*, int ); - void slotSplitStdout( K3Process*, char*, int ); - void slotProcessExited( K3Process * ); + void slotReadyReadStandardError(); + void slotReadyReadStandardOutput(); Q_SIGNALS: void stderrLine( const QString& line ); void stdoutLine( const QString& line ); - /** - * the same as QProcess::finished() - */ - void finished( int exitCode, QProcess::ExitStatus exitStatus ); - private: - static QStringList splitOutput( char*, int, QString&, bool ); - - class Data; - Data* d; - - bool m_bSplitStdout; + class Private; + Private* const d; }; } #endif diff --git a/libk3b/core/k3process.cpp b/libk3b/core/k3process.cpp deleted file mode 100644 index c93201640..000000000 --- a/libk3b/core/k3process.cpp +++ /dev/null @@ -1,1050 +0,0 @@ -/* - This file is part of the KDE libraries - Copyright (C) 1997 Christian Czezatke (e9025461@student.tuwien.ac.at) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - - -#include "k3process.h" -#include "k3processcontroller.h" -#include - -#ifdef __osf__ -#define _OSF_SOURCE -#include -#endif - -#ifdef _AIX -#define _ALL_SOURCE -#endif - -#include -#include - -#include -#include -#include -#include -#include - -#ifdef HAVE_SYS_SELECT_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - - -////////////////// -// private data // -////////////////// - -class K3ProcessPrivate { -public: - K3ProcessPrivate() : - usePty(K3Process::NoCommunication), - addUtmp(false), useShell(false), - pty(0), - priority(0) - { - } - - K3Process::Communication usePty; - bool addUtmp : 1; - bool useShell : 1; - - KPty *pty; - - int priority; - - QMap env; - QString wd; - QByteArray shell; - QByteArray executable; -}; - -///////////////////////////// -// public member functions // -///////////////////////////// - -K3Process::K3Process( QObject* parent ) - : QObject( parent ), - run_mode(NotifyOnExit), - runs(false), - pid_(0), - status(0), - keepPrivs(false), - innot(0), - outnot(0), - errnot(0), - communication(NoCommunication), - input_data(0), - input_sent(0), - input_total(0), - d(new K3ProcessPrivate) -{ - K3ProcessController::ref(); - K3ProcessController::instance()->addKProcess(this); - - - out[0] = out[1] = -1; - in[0] = in[1] = -1; - err[0] = err[1] = -1; -} - -void -K3Process::setEnvironment(const QString &name, const QString &value) -{ - d->env.insert(name, value); -} - -void -K3Process::setWorkingDirectory(const QString &dir) -{ - d->wd = dir; -} - -void -K3Process::setupEnvironment() -{ - QMap::Iterator it; - for(it = d->env.begin(); it != d->env.end(); ++it) - { - setenv(QFile::encodeName(it.key()).data(), - QFile::encodeName(it.value()).data(), 1); - } - if (!d->wd.isEmpty()) - { - chdir(QFile::encodeName(d->wd).data()); - } -} - -void -K3Process::setRunPrivileged(bool keepPrivileges) -{ - keepPrivs = keepPrivileges; -} - -bool -K3Process::runPrivileged() const -{ - return keepPrivs; -} - -bool -K3Process::setPriority(int prio) -{ - if (runs) { - if (setpriority(PRIO_PROCESS, pid_, prio)) - return false; - } else { - if (prio > 19 || prio < (geteuid() ? getpriority(PRIO_PROCESS, 0) : -20)) - return false; - } - d->priority = prio; - return true; -} - -K3Process::~K3Process() -{ - if (run_mode != DontCare) - kill(SIGKILL); - detach(); - - delete d->pty; - delete d; - - K3ProcessController::instance()->removeKProcess(this); - K3ProcessController::deref(); -} - -void K3Process::detach() -{ - if (runs) { - K3ProcessController::instance()->addProcess(pid_); - runs = false; - pid_ = 0; // close without draining - commClose(); // Clean up open fd's and socket notifiers. - } -} - -void K3Process::setBinaryExecutable(const char *filename) -{ - d->executable = filename; -} - -K3Process &K3Process::operator<<(const QStringList& args) -{ - QStringList::ConstIterator it = args.begin(); - for ( ; it != args.end() ; ++it ) - arguments.append(QFile::encodeName(*it)); - return *this; -} - -K3Process &K3Process::operator<<(const QByteArray& arg) -{ - return operator<< (arg.data()); -} - -K3Process &K3Process::operator<<(const char* arg) -{ - arguments.append(arg); - return *this; -} - -K3Process &K3Process::operator<<(const QString& arg) -{ - arguments.append(QFile::encodeName(arg)); - return *this; -} - -void K3Process::clearArguments() -{ - arguments.clear(); -} - -bool K3Process::start(RunMode runmode, Communication comm) -{ - if (runs) { - kDebug(175) << "Attempted to start an already running process" << endl; - return false; - } - - uint n = arguments.count(); - if (n == 0) { - kDebug(175) << "Attempted to start a process without arguments" << endl; - return false; - } - char **arglist; - QByteArray shellCmd; - if (d->useShell) - { - if (d->shell.isEmpty()) { - kDebug(175) << "Invalid shell specified" << endl; - return false; - } - - for (uint i = 0; i < n; i++) { - shellCmd += arguments[i]; - shellCmd += ' '; // CC: to separate the arguments - } - - arglist = static_cast(malloc( 4 * sizeof(char *))); - arglist[0] = d->shell.data(); - arglist[1] = (char *) "-c"; - arglist[2] = shellCmd.data(); - arglist[3] = 0; - } - else - { - arglist = static_cast(malloc( (n + 1) * sizeof(char *))); - for (uint i = 0; i < n; i++) - arglist[i] = arguments[i].data(); - arglist[n] = 0; - } - - run_mode = runmode; - - if (!setupCommunication(comm)) - { - kDebug(175) << "Could not setup Communication!" << endl; - free(arglist); - return false; - } - - // We do this in the parent because if we do it in the child process - // gdb gets confused when the application runs from gdb. -#ifdef HAVE_INITGROUPS - struct passwd *pw = geteuid() ? 0 : getpwuid(getuid()); -#endif - - int fd[2]; - if (pipe(fd)) - fd[0] = fd[1] = -1; // Pipe failed.. continue - - // we don't use vfork() because - // - it has unclear semantics and is not standardized - // - we do way too much magic in the child - pid_ = fork(); - if (pid_ == 0) { - // The child process - - close(fd[0]); - // Closing of fd[1] indicates that the execvp() succeeded! - fcntl(fd[1], F_SETFD, FD_CLOEXEC); - - if (!commSetupDoneC()) - kDebug(175) << "Could not finish comm setup in child!" << endl; - - // reset all signal handlers - struct sigaction act; - sigemptyset(&act.sa_mask); - act.sa_handler = SIG_DFL; - act.sa_flags = 0; - for (int sig = 1; sig < NSIG; sig++) - sigaction(sig, &act, 0L); - - if (d->priority) - setpriority(PRIO_PROCESS, 0, d->priority); - - if (!runPrivileged()) - { - setgid(getgid()); -#ifdef HAVE_INITGROUPS - if (pw) - initgroups(pw->pw_name, pw->pw_gid); -#endif - if (geteuid() != getuid()) - setuid(getuid()); - if (geteuid() != getuid()) - _exit(1); - } - - setupEnvironment(); - - if (runmode == DontCare || runmode == OwnGroup) - setsid(); - - const char *executable = arglist[0]; - if (!d->executable.isEmpty()) - executable = d->executable.data(); - execvp(executable, arglist); - - char resultByte = 1; - write(fd[1], &resultByte, 1); - _exit(-1); - } else if (pid_ == -1) { - // forking failed - - // commAbort(); - pid_ = 0; - free(arglist); - return false; - } - // the parent continues here - free(arglist); - - if (!commSetupDoneP()) - kDebug(175) << "Could not finish comm setup in parent!" << endl; - - // Check whether client could be started. - close(fd[1]); - for(;;) - { - char resultByte; - int n = ::read(fd[0], &resultByte, 1); - if (n == 1) - { - // exec() failed - close(fd[0]); - waitpid(pid_, 0, 0); - pid_ = 0; - commClose(); - return false; - } - if (n == -1) - { - if (errno == EINTR) - continue; // Ignore - } - break; // success - } - close(fd[0]); - - runs = true; - switch (runmode) - { - case Block: - for (;;) - { - commClose(); // drain only, unless obsolete reimplementation - if (!runs) - { - // commClose detected data on the process exit notifification pipe - K3ProcessController::instance()->unscheduleCheck(); - if (waitpid(pid_, &status, WNOHANG) != 0) // error finishes, too - { - commClose(); // this time for real (runs is false) - K3ProcessController::instance()->rescheduleCheck(); - break; - } - runs = true; // for next commClose() iteration - } - else - { - // commClose is an obsolete reimplementation and waited until - // all output channels were closed (or it was interrupted). - // there is a chance that it never gets here ... - waitpid(pid_, &status, 0); - runs = false; - break; - } - } - // why do we do this? i think this signal should be emitted _only_ - // after the process has successfully run _asynchronously_ --ossi - emit processExited(this); - break; - default: // NotifyOnExit & OwnGroup - input_data = 0; // Discard any data for stdin that might still be there - break; - } - return true; -} - - - -bool K3Process::kill(int signo) -{ - if (runs && pid_ > 0 && !::kill(run_mode == OwnGroup ? -pid_ : pid_, signo)) - return true; - return false; -} - - - -bool K3Process::isRunning() const -{ - return runs; -} - - - -pid_t K3Process::pid() const -{ - return pid_; -} - -#ifndef timersub -# define timersub(a, b, result) \ - do { \ - (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ - (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ - if ((result)->tv_usec < 0) { \ - --(result)->tv_sec; \ - (result)->tv_usec += 1000000; \ - } \ - } while (0) -#endif - -bool K3Process::wait(int timeout) -{ - if (!runs) - return true; - -#ifndef __linux__ - struct timeval etv; -#endif - struct timeval tv, *tvp; - if (timeout < 0) - tvp = 0; - else - { -#ifndef __linux__ - gettimeofday(&etv, 0); - etv.tv_sec += timeout; -#else - tv.tv_sec = timeout; - tv.tv_usec = 0; -#endif - tvp = &tv; - } - - int fd = K3ProcessController::instance()->notifierFd(); - for(;;) - { - fd_set fds; - FD_ZERO( &fds ); - FD_SET( fd, &fds ); - -#ifndef __linux__ - if (tvp) - { - gettimeofday(&tv, 0); - timersub(&etv, &tv, &tv); - if (tv.tv_sec < 0) - tv.tv_sec = tv.tv_usec = 0; - } -#endif - - switch( select( fd+1, &fds, 0, 0, tvp ) ) - { - case -1: - if( errno == EINTR ) - break; - // fall through; should happen if tvp->tv_sec < 0 - case 0: - K3ProcessController::instance()->rescheduleCheck(); - return false; - default: - K3ProcessController::instance()->unscheduleCheck(); - if (waitpid(pid_, &status, WNOHANG) != 0) // error finishes, too - { - processHasExited(status); - K3ProcessController::instance()->rescheduleCheck(); - return true; - } - } - } - return false; -} - - - -bool K3Process::normalExit() const -{ - return (pid_ != 0) && !runs && WIFEXITED(status); -} - - -bool K3Process::signalled() const -{ - return (pid_ != 0) && !runs && WIFSIGNALED(status); -} - - -bool K3Process::coreDumped() const -{ -#ifdef WCOREDUMP - return signalled() && WCOREDUMP(status); -#else - return false; -#endif -} - - -int K3Process::exitStatus() const -{ - return WEXITSTATUS(status); -} - - -int K3Process::exitSignal() const -{ - return WTERMSIG(status); -} - - -bool K3Process::writeStdin(const char *buffer, int buflen) -{ - // if there is still data pending, writing new data - // to stdout is not allowed (since it could also confuse - // kprocess ...) - if (input_data != 0) - return false; - - if (communication & Stdin) { - input_data = buffer; - input_sent = 0; - input_total = buflen; - innot->setEnabled(true); - if (input_total) - slotSendData(0); - return true; - } else - return false; -} - -void K3Process::suspend() -{ - if (outnot) - outnot->setEnabled(false); -} - -void K3Process::resume() -{ - if (outnot) - outnot->setEnabled(true); -} - -bool K3Process::closeStdin() -{ - if (communication & Stdin) { - communication = communication & ~Stdin; - delete innot; - innot = 0; - if (!(d->usePty & Stdin)) - close(in[1]); - in[1] = -1; - return true; - } else - return false; -} - -bool K3Process::closeStdout() -{ - if (communication & Stdout) { - communication = communication & ~Stdout; - delete outnot; - outnot = 0; - if (!(d->usePty & Stdout)) - close(out[0]); - out[0] = -1; - return true; - } else - return false; -} - -bool K3Process::closeStderr() -{ - if (communication & Stderr) { - communication = communication & ~Stderr; - delete errnot; - errnot = 0; - if (!(d->usePty & Stderr)) - close(err[0]); - err[0] = -1; - return true; - } else - return false; -} - -bool K3Process::closePty() -{ - if (d->pty && d->pty->masterFd() >= 0) { - if (d->addUtmp) - d->pty->logout(); - d->pty->close(); - return true; - } else - return false; -} - -void K3Process::closeAll() -{ - closeStdin(); - closeStdout(); - closeStderr(); - closePty(); -} - -///////////////////////////// -// protected slots // -///////////////////////////// - - - -void K3Process::slotChildOutput(int fdno) -{ - if (!childOutput(fdno)) - closeStdout(); -} - - -void K3Process::slotChildError(int fdno) -{ - if (!childError(fdno)) - closeStderr(); -} - - -void K3Process::slotSendData(int) -{ - if (input_sent == input_total) { - innot->setEnabled(false); - input_data = 0; - emit wroteStdin(this); - } else { - int result = ::write(in[1], input_data+input_sent, input_total-input_sent); - if (result >= 0) - { - input_sent += result; - } - else if ((errno != EAGAIN) && (errno != EINTR)) - { - kDebug(175) << "Error writing to stdin of child process" << endl; - closeStdin(); - } - } -} - -void K3Process::setUseShell(bool useShell, const char *shell) -{ - d->useShell = useShell; - if (shell && *shell) - d->shell = shell; - else -// #ifdef NON_FREE // ... as they ship non-POSIX /bin/sh -#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__GNU__) && !defined(__DragonFly__) - // Solaris POSIX ... - if (!access( "/usr/xpg4/bin/sh", X_OK )) - d->shell = "/usr/xpg4/bin/sh"; - else - // ... which links here anyway - if (!access( "/bin/ksh", X_OK )) - d->shell = "/bin/ksh"; - else - // dunno, maybe superfluous? - if (!access( "/usr/ucb/sh", X_OK )) - d->shell = "/usr/ucb/sh"; - else -#endif - d->shell = "/bin/sh"; -} - -void K3Process::setUsePty(Communication usePty, bool addUtmp) -{ - d->usePty = usePty; - d->addUtmp = addUtmp; - if (usePty) { - if (!d->pty) - d->pty = new KPty; - } else { - delete d->pty; - d->pty = 0; - } -} - -KPty *K3Process::pty() const -{ - return d->pty; -} - -QString K3Process::quote(const QString &arg) -{ - QChar q('\''); - return QString(arg).replace(q, "'\\''").prepend(q).append(q); -} - - -////////////////////////////// -// private member functions // -////////////////////////////// - - -void K3Process::processHasExited(int state) -{ - // only successfully run NotifyOnExit processes ever get here - - status = state; - runs = false; // do this before commClose, so it knows we're dead - - commClose(); // cleanup communication sockets - - if (run_mode != DontCare) - emit processExited(this); -} - - - -int K3Process::childOutput(int fdno) -{ - if (communication & NoRead) { - int len = -1; - emit receivedStdout(fdno, len); - errno = 0; // Make sure errno doesn't read "EAGAIN" - return len; - } - else - { - char buffer[1025]; - int len; - - len = ::read(fdno, buffer, 1024); - - if (len > 0) { - buffer[len] = 0; // Just in case. - emit receivedStdout(this, buffer, len); - } - return len; - } -} - -int K3Process::childError(int fdno) -{ - char buffer[1025]; - int len; - - len = ::read(fdno, buffer, 1024); - - if (len > 0) { - buffer[len] = 0; // Just in case. - emit receivedStderr(this, buffer, len); - } - return len; -} - - -int K3Process::setupCommunication(Communication comm) -{ - // PTY stuff // - if (d->usePty) - { - // cannot communicate on both stderr and stdout if they are both on the pty - if (!(~(comm & d->usePty) & (Stdout | Stderr))) { - kWarning(175) << "Invalid usePty/communication combination (" << d->usePty << "/" << comm << ")" << endl; - return 0; - } - if (!d->pty->open()) - return 0; - - int rcomm = comm & d->usePty; - int mfd = d->pty->masterFd(); - if (rcomm & Stdin) - in[1] = mfd; - if (rcomm & Stdout) - out[0] = mfd; - if (rcomm & Stderr) - err[0] = mfd; - } - - communication = comm; - - comm = comm & ~d->usePty; - if (comm & Stdin) { - if (socketpair(AF_UNIX, SOCK_STREAM, 0, in)) - goto fail0; - fcntl(in[0], F_SETFD, FD_CLOEXEC); - fcntl(in[1], F_SETFD, FD_CLOEXEC); - } - if (comm & Stdout) { - if (socketpair(AF_UNIX, SOCK_STREAM, 0, out)) - goto fail1; - fcntl(out[0], F_SETFD, FD_CLOEXEC); - fcntl(out[1], F_SETFD, FD_CLOEXEC); - } - if (comm & Stderr) { - if (socketpair(AF_UNIX, SOCK_STREAM, 0, err)) - goto fail2; - fcntl(err[0], F_SETFD, FD_CLOEXEC); - fcntl(err[1], F_SETFD, FD_CLOEXEC); - } - return 1; // Ok - fail2: - if (comm & Stdout) - { - close(out[0]); - close(out[1]); - out[0] = out[1] = -1; - } - fail1: - if (comm & Stdin) - { - close(in[0]); - close(in[1]); - in[0] = in[1] = -1; - } - fail0: - communication = NoCommunication; - return 0; // Error -} - - - -int K3Process::commSetupDoneP() -{ - int rcomm = communication & ~d->usePty; - if (rcomm & Stdin) - close(in[0]); - if (rcomm & Stdout) - close(out[1]); - if (rcomm & Stderr) - close(err[1]); - in[0] = out[1] = err[1] = -1; - - // Don't create socket notifiers if no interactive comm is to be expected - if (run_mode != NotifyOnExit && run_mode != OwnGroup) - return 1; - - if (communication & Stdin) { - fcntl(in[1], F_SETFL, O_NONBLOCK | fcntl(in[1], F_GETFL)); - innot = new QSocketNotifier(in[1], QSocketNotifier::Write, this); - Q_CHECK_PTR(innot); - innot->setEnabled(false); // will be enabled when data has to be sent - QObject::connect(innot, SIGNAL(activated(int)), - this, SLOT(slotSendData(int))); - } - - if (communication & Stdout) { - outnot = new QSocketNotifier(out[0], QSocketNotifier::Read, this); - Q_CHECK_PTR(outnot); - QObject::connect(outnot, SIGNAL(activated(int)), - this, SLOT(slotChildOutput(int))); - if (communication & NoRead) - suspend(); - } - - if (communication & Stderr) { - errnot = new QSocketNotifier(err[0], QSocketNotifier::Read, this ); - Q_CHECK_PTR(errnot); - QObject::connect(errnot, SIGNAL(activated(int)), - this, SLOT(slotChildError(int))); - } - - return 1; -} - - - -int K3Process::commSetupDoneC() -{ - int ok = 1; - - if (d->usePty & Stdin) { - if (dup2(d->pty->slaveFd(), STDIN_FILENO) < 0) ok = 0; - } else if (communication & Stdin) { - if (dup2(in[0], STDIN_FILENO) < 0) ok = 0; - } else { - int null_fd = open( "/dev/null", O_RDONLY ); - if (dup2( null_fd, STDIN_FILENO ) < 0) ok = 0; - close( null_fd ); - } - struct linger so; - memset(&so, 0, sizeof(so)); - if (d->usePty & Stdout) { - if (dup2(d->pty->slaveFd(), STDOUT_FILENO) < 0) ok = 0; - } else if (communication & Stdout) { - if (dup2(out[1], STDOUT_FILENO) < 0 || - setsockopt(out[1], SOL_SOCKET, SO_LINGER, (char *)&so, sizeof(so))) - ok = 0; - if (communication & MergedStderr) { - if (dup2(out[1], STDERR_FILENO) < 0) - ok = 0; - } - } - if (d->usePty & Stderr) { - if (dup2(d->pty->slaveFd(), STDERR_FILENO) < 0) ok = 0; - } else if (communication & Stderr) { - if (dup2(err[1], STDERR_FILENO) < 0 || - setsockopt(err[1], SOL_SOCKET, SO_LINGER, (char *)&so, sizeof(so))) - ok = 0; - } - - // don't even think about closing all open fds here or anywhere else - - // PTY stuff // - if (d->usePty) { - d->pty->setCTty(); - if (d->addUtmp) - d->pty->login(KUser(KUser::UseRealUserID).loginName().toLocal8Bit().data(), getenv("DISPLAY")); - } - - return ok; -} - - - -void K3Process::commClose() -{ - closeStdin(); - - if (pid_) { // detached, failed, and killed processes have no output. basta. :) - // If both channels are being read we need to make sure that one socket - // buffer doesn't fill up whilst we are waiting for data on the other - // (causing a deadlock). Hence we need to use select. - - int notfd = K3ProcessController::instance()->notifierFd(); - - while ((communication & (Stdout | Stderr)) || runs) { - fd_set rfds; - FD_ZERO(&rfds); - struct timeval timeout, *p_timeout; - - int max_fd = 0; - if (communication & Stdout) { - FD_SET(out[0], &rfds); - max_fd = out[0]; - } - if (communication & Stderr) { - FD_SET(err[0], &rfds); - if (err[0] > max_fd) - max_fd = err[0]; - } - if (runs) { - FD_SET(notfd, &rfds); - if (notfd > max_fd) - max_fd = notfd; - // If the process is still running we block until we - // receive data or the process exits. - p_timeout = 0; // no timeout - } else { - // If the process has already exited, we only check - // the available data, we don't wait for more. - timeout.tv_sec = timeout.tv_usec = 0; // timeout immediately - p_timeout = &timeout; - } - - int fds_ready = select(max_fd+1, &rfds, 0, 0, p_timeout); - if (fds_ready < 0) { - if (errno == EINTR) - continue; - break; - } else if (!fds_ready) - break; - - if ((communication & Stdout) && FD_ISSET(out[0], &rfds)) - slotChildOutput(out[0]); - - if ((communication & Stderr) && FD_ISSET(err[0], &rfds)) - slotChildError(err[0]); - - if (runs && FD_ISSET(notfd, &rfds)) { - runs = false; // hack: signal potential exit - return; // don't close anything, we will be called again - } - } - } - - closeStdout(); - closeStderr(); - - closePty(); -} - - - -/////////////////////////// -// CC: Class K3ShellProcess -/////////////////////////// - -K3ShellProcess::K3ShellProcess(const char *shellname): - K3Process(), d(0) -{ - setUseShell( true, shellname ? shellname : getenv("SHELL") ); -} - -K3ShellProcess::~K3ShellProcess() { -} - -QString K3ShellProcess::quote(const QString &arg) -{ - return K3Process::quote(arg); -} - -bool K3ShellProcess::start(RunMode runmode, Communication comm) -{ - return K3Process::start(runmode, comm); -} - - -#include "k3process.moc" diff --git a/libk3b/core/k3process.h b/libk3b/core/k3process.h deleted file mode 100644 index 14c690b07..000000000 --- a/libk3b/core/k3process.h +++ /dev/null @@ -1,889 +0,0 @@ -/* This file is part of the KDE libraries - Copyright (C) 1997 Christian Czezakte (e9025461@student.tuwien.ac.at) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#ifndef K3PROCESS_H -#define K3PROCESS_H - - -#include - -#include // for pid_t -#include -#include -#include - -class QSocketNotifier; -class K3ProcessPrivate; -class KPty; - -/** - * @obsolete Use KProcess and KPtyProcess instead. - * - * Child process invocation, monitoring and control. - * This class works only in the application's main thread. - * - * General usage and features:\n - * - * This class allows a KDE application to start child processes without having - * to worry about UN*X signal handling issues and zombie process reaping. - * - * @see K3ProcIO - * - * Basically, this class distinguishes three different ways of running - * child processes: - * - * @li DontCare -- The child process is invoked and both the child - * process and the parent process continue concurrently. - * - * The process is started in an own session (see setsid(2)). - * - * @li NotifyOnExit -- The child process is invoked and both the - * child and the parent process run concurrently. - * - * When the child process exits, the K3Process instance - * corresponding to it emits the Qt signal processExited(). - * Since this signal is @em not emitted from within a UN*X - * signal handler, arbitrary function calls can be made. - * - * Be aware: When the K3Process object gets destructed, the child - * process will be killed if it is still running! - * This means in particular, that it usually makes no sense to use - * a K3Process on the stack with NotifyOnExit. - * - * @li OwnGroup -- like NotifyOnExit, but the child process is started - * in an own process group (and an own session, FWIW). The behavior of - * kill() changes to killing the whole process group - this makes - * this mode useful for implementing primitive job management. It can be - * used to work around broken wrapper scripts that don't propagate signals - * to the "real" program. However, use this with care, as you disturb the - * shell's job management if your program is started from the command line. - * - * @li Block -- The child process starts and the parent process - * is suspended until the child process exits. (@em Really not recommended - * for programs with a GUI.) - * In this mode the parent can read the child's output, but can't send it any - * input. - * - * K3Process also provides several functions for determining the exit status - * and the pid of the child process it represents. - * - * Furthermore it is possible to supply command-line arguments to the process - * in a clean fashion (no null-terminated stringlists and such...) - * - * A small usage example: - * \code - * K3Process *proc = new K3Process; - * - * *proc << "my_executable"; - * *proc << "These" << "are" << "the" << "command" << "line" << "args"; - * QApplication::connect(proc, SIGNAL(processExited(K3Process *)), - * pointer_to_my_object, SLOT(my_objects_slot(K3Process *))); - * proc->start(); - * \endcode - * - * This will start "my_executable" with the commandline arguments "These"... - * - * When the child process exits, the slot will be invoked. - * - * Communication with the child process:\n - * - * K3Process supports communication with the child process through - * stdin/stdout/stderr. - * - * The following functions are provided for getting data from the child - * process or sending data to the child's stdin (For more information, - * have a look at the documentation of each function): - * - * @li writeStdin() - * -- Transmit data to the child process' stdin. When all data was sent, the - * signal wroteStdin() is emitted. - * - * @li When data arrives at stdout or stderr, the signal receivedStdout() - * resp. receivedStderr() is emitted. - * - * @li You can shut down individual communication channels with - * closeStdin(), closeStdout(), and closeStderr(), resp. - * - * @author Christian Czezatke e9025461@student.tuwien.ac.at - * - **/ -class K3Process : public QObject -{ - Q_OBJECT - -public: - - /** - * Modes in which the communication channels can be opened. - * - * If communication for more than one channel is required, - * the values should be or'ed together, for example to get - * communication with stdout as well as with stdin, you would - * specify @p Stdin | @p Stdout - * - */ - enum CommunicationFlag { - NoCommunication = 0, /**< No communication with the process. */ - Stdin = 1, /**< Connect to write to the process with writeStdin(). */ - Stdout = 2, /**< Connect to read from the process' output. */ - Stderr = 4, /**< Connect to read from the process' stderr. */ - AllOutput = 6, /**< Connects to all output channels. */ - All = 7, /**< Connects to all channels. */ - NoRead = 8, /**< If specified with Stdout, no data is actually read from stdout, - * only the signal receivedStdout(int fd, int &len) is emitted. */ - CTtyOnly = NoRead, /**< Tells setUsePty() to create a PTY for the process - * and make it the process' controlling TTY, but does not - * redirect any I/O channel to the PTY. */ - MergedStderr = 16 /**< If specified with Stdout, the process' stderr will be - * redirected onto the same file handle as its stdout, i.e., - * all error output will be signalled with receivedStdout(). - * Don't specify Stderr if you specify MergedStderr. */ - }; - - Q_DECLARE_FLAGS(Communication, CommunicationFlag) - - /** - * Run-modes for a child process. - */ - enum RunMode { - /** - * The application does not receive notifications from the subprocess when - * it is finished or aborted. - */ - DontCare, - /** - * The application is notified when the subprocess dies. - */ - NotifyOnExit, - /** - * The application is suspended until the started process is finished. - */ - Block, - /** - * Same as NotifyOnExit, but the process is run in an own session, - * just like with DontCare. - */ - OwnGroup - }; - - /** - * Constructor - */ - explicit K3Process( QObject* parent=0L ); - - /** - *Destructor: - * - * If the process is running when the destructor for this class - * is called, the child process is killed with a SIGKILL, but - * only if the run mode is not of type @p DontCare. - * Processes started as @p DontCare keep running anyway. - */ - virtual ~K3Process(); - - /** - * Sets the executable and the command line argument list for this process. - * - * For example, doing an "ls -l /usr/local/bin" can be achieved by: - * \code - * K3Process p; - * ... - * p << "ls" << "-l" << "/usr/local/bin" - * \endcode - * - * @param arg the argument to add - * @return a reference to this K3Process - **/ - K3Process &operator<<(const QString& arg); - /** - * Similar to previous method, takes a char *, supposed to be in locale 8 bit already. - */ - K3Process &operator<<(const char * arg); - /** - * Similar to previous method, takes a QByteArray, supposed to be in locale 8 bit already. - * @param arg the argument to add - * @return a reference to this K3Process - */ - K3Process &operator<<(const QByteArray & arg); - - /** - * Sets the executable and the command line argument list for this process, - * in a single method call, or add a list of arguments. - * @param args the arguments to add - * @return a reference to this K3Process - **/ - K3Process &operator<<(const QStringList& args); - - /** - * Clear a command line argument list that has been set by using - * operator<<. - */ - void clearArguments(); - - /** - * Starts the process. - * For a detailed description of the - * various run modes and communication semantics, have a look at the - * general description of the K3Process class. Note that if you use - * setUsePty( Stdout | Stderr, \ ), you cannot use Stdout | Stderr - * here - instead, use Stdout only to receive the mixed output. - * - * The following problems could cause this function to - * return false: - * - * @li The process is already running. - * @li The command line argument list is empty. - * @li The the @p comm parameter is incompatible with the selected pty usage. - * @li The starting of the process failed (could not fork). - * @li The executable was not found. - * - * @param runmode The Run-mode for the process. - * @param comm Specifies which communication channels should be - * established to the child process (stdin/stdout/stderr). By default, - * no communication takes place and the respective communication - * signals will never get emitted. - * - * @return true on success, false on error - * (see above for error conditions) - **/ - virtual bool start(RunMode runmode = NotifyOnExit, - Communication comm = NoCommunication); - - /** - * Stop the process (by sending it a signal). - * - * @param signo The signal to send. The default is SIGTERM. - * @return true if the signal was delivered successfully. - */ - virtual bool kill(int signo = SIGTERM); - - /** - * Checks whether the process is running. - * @return true if the process is (still) considered to be running - */ - bool isRunning() const; - - /** Returns the process id of the process. - * - * If it is called after - * the process has exited, it returns the process id of the last - * child process that was created by this instance of K3Process. - * - * Calling it before any child process has been started by this - * K3Process instance causes pid() to return 0. - * @return the pid of the process or 0 if no process has been started yet. - **/ - pid_t pid() const; - - /** - * Suspend processing of data from stdout of the child process. - */ - void suspend(); - - /** - * Resume processing of data from stdout of the child process. - */ - void resume(); - - /** - * Suspend execution of the current thread until the child process dies - * or the timeout hits. This function is not recommended for programs - * with a GUI. - * @param timeout timeout in seconds. -1 means wait indefinitely. - * @return true if the process exited, false if the timeout hit. - */ - bool wait(int timeout = -1); - - /** - * Checks whether the process exited cleanly. - * - * @return true if the process has already finished and has exited - * "voluntarily", ie: it has not been killed by a signal. - */ - bool normalExit() const; - - /** - * Checks whether the process was killed by a signal. - * - * @return true if the process has already finished and has not exited - * "voluntarily", ie: it has been killed by a signal. - */ - bool signalled() const; - - /** - * Checks whether a killed process dumped core. - * - * @return true if signalled() returns true and the process - * dumped core. Note that on systems that don't define the - * WCOREDUMP macro, the return value is always false. - */ - bool coreDumped() const; - - /** - * Returns the exit status of the process. - * - * @return the exit status of the process. Note that this value - * is not valid if normalExit() returns false. - */ - int exitStatus() const; - - /** - * Returns the signal the process was killed by. - * - * @return the signal number that caused the process to exit. - * Note that this value is not valid if signalled() returns false. - */ - int exitSignal() const; - - /** - * Transmit data to the child process' stdin. - * - * This function may return false in the following cases: - * - * @li The process is not currently running. - * This implies that you cannot use this function in Block mode. - * - * @li Communication to stdin has not been requested in the start() call. - * - * @li Transmission of data to the child process by a previous call to - * writeStdin() is still in progress. - * - * Please note that the data is sent to the client asynchronously, - * so when this function returns, the data might not have been - * processed by the child process. - * That means that you must not free @p buffer or call writeStdin() - * again until either a wroteStdin() signal indicates that the - * data has been sent or a processExited() signal shows that - * the child process is no longer alive. - * - * If all the data has been sent to the client, the signal - * wroteStdin() will be emitted. - * - * This function does not work when the process is start()ed in Block mode. - * - * @param buffer the buffer to write - * @param buflen the length of the buffer - * @return false if an error has occurred - **/ - bool writeStdin(const char *buffer, int buflen); - - /** - * Shuts down the Stdin communication link. If no pty is used, this - * causes "EOF" to be indicated on the child's stdin file descriptor. - * - * @return false if no Stdin communication link exists (any more). - */ - bool closeStdin(); - - /** - * Shuts down the Stdout communication link. If no pty is used, any further - * attempts by the child to write to its stdout file descriptor will cause - * it to receive a SIGPIPE. - * - * @return false if no Stdout communication link exists (any more). - */ - bool closeStdout(); - - /** - * Shuts down the Stderr communication link. If no pty is used, any further - * attempts by the child to write to its stderr file descriptor will cause - * it to receive a SIGPIPE. - * - * @return false if no Stderr communication link exists (any more). - */ - bool closeStderr(); - - /** - * Deletes the optional utmp entry and closes the pty. - * - * Make sure to shut down any communication links that are using the pty - * before calling this function. - * - * @return false if the pty is not open (any more). - */ - bool closePty(); - - /** - * @brief Close stdin, stdout, stderr and the pty - * - * This is the same that calling all close* functions in a row: - * @see closeStdin, @see closeStdout, @see closeStderr and @see closePty - */ - void closeAll(); - - /** - * Lets you see what your arguments are for debugging. - * @return the list of arguments - */ - const QList &args() /* const */ { return arguments; } - - /** - * Controls whether the started process should drop any - * setuid/setgid privileges or whether it should keep them. - * Note that this function is mostly a dummy, as the KDE libraries - * currently refuse to run with setuid/setgid privileges. - * - * The default is false: drop privileges - * @param keepPrivileges true to keep the privileges - */ - void setRunPrivileged(bool keepPrivileges); - - /** - * Returns whether the started process will drop any - * setuid/setgid privileges or whether it will keep them. - * @return true if the process runs privileged - */ - bool runPrivileged() const; - - /** - * Adds the variable @p name to the process' environment. - * This function must be called before starting the process. - * @param name the name of the environment variable - * @param value the new value for the environment variable - */ - void setEnvironment(const QString &name, const QString &value); - - /** - * Changes the current working directory (CWD) of the process - * to be started. - * This function must be called before starting the process. - * @param dir the new directory - */ - void setWorkingDirectory(const QString &dir); - - /** - * Specify whether to start the command via a shell or directly. - * The default is to start the command directly. - * If @p useShell is true @p shell will be used as shell, or - * if shell is empty, /bin/sh will be used. - * - * When using a shell, the caller should make sure that all filenames etc. - * are properly quoted when passed as argument. - * @see quote() - * @param useShell true if the command should be started via a shell - * @param shell the path to the shell that will execute the process, or - * 0 to use /bin/sh. Use getenv("SHELL") to use the user's - * default shell, but note that doing so is usually a bad idea - * for shell compatibility reasons. - */ - void setUseShell(bool useShell, const char *shell = 0); - - /** - * This function can be used to quote an argument string such that - * the shell processes it properly. This is e. g. necessary for - * user-provided file names which may contain spaces or quotes. - * It also prevents expansion of wild cards and environment variables. - * @param arg the argument to quote - * @return the quoted argument - */ - static QString quote(const QString &arg); - - /** - * Detaches K3Process from child process. All communication is closed. - * No exit notification is emitted any more for the child process. - * Deleting the K3Process will no longer kill the child process. - * Note that the current process remains the parent process of the - * child process. - */ - void detach(); - - /** - * Specify whether to create a pty (pseudo-terminal) for running the - * command. - * This function should be called before starting the process. - * - * @param comm for which stdio handles to use a pty. Note that it is not - * allowed to specify Stdout and Stderr at the same time both here and to - * start (there is only one pty, so they cannot be distinguished). - * @param addUtmp true if a utmp entry should be created for the pty - */ - void setUsePty(Communication comm, bool addUtmp); - - /** - * Obtains the pty object used by this process. The return value is - * valid only after setUsePty() was used with a non-zero argument. - * The pty is open only while the process is running. - * @return a pointer to the pty object - */ - KPty *pty() const; - - /** - * More or less intuitive constants for use with setPriority(). - */ - enum { PrioLowest = 20, PrioLow = 10, PrioLower = 5, PrioNormal = 0, - PrioHigher = -5, PrioHigh = -10, PrioHighest = -19 }; - - /** - * Sets the scheduling priority of the process. - * @param prio the new priority in the range -20 (high) to 19 (low). - * @return false on error; see setpriority(2) for possible reasons. - */ - bool setPriority(int prio); - -Q_SIGNALS: - /** - * Emitted after the process has terminated when - * the process was run in the @p NotifyOnExit (==default option to - * start() ) or the Block mode. - * @param proc a pointer to the process that has exited - **/ - void processExited(K3Process *proc); - - - /** - * Emitted, when output from the child process has - * been received on stdout. - * - * To actually get this signal, the Stdout communication link - * has to be turned on in start(). - * - * @param proc a pointer to the process that has received the output - * @param buffer The data received. - * @param buflen The number of bytes that are available. - * - * You should copy the information contained in @p buffer to your private - * data structures before returning from the slot. - * Example: - * \code - * QString myBuf = QLatin1String(buffer, buflen); - * \endcode - **/ - void receivedStdout(K3Process *proc, char *buffer, int buflen); - - /** - * Emitted when output from the child process has - * been received on stdout. - * - * To actually get this signal, the Stdout communication link - * has to be turned on in start() and the - * NoRead flag must have been passed. - * - * You will need to explicitly call resume() after your call to start() - * to begin processing data from the child process' stdout. This is - * to ensure that this signal is not emitted when no one is connected - * to it, otherwise this signal will not be emitted. - * - * The data still has to be read from file descriptor @p fd. - * @param fd the file descriptor that provides the data - * @param len the number of bytes that have been read from @p fd must - * be written here - **/ - void receivedStdout(int fd, int &len); // KDE4: change, broken API - - - /** - * Emitted, when output from the child process has - * been received on stderr. - * - * To actually get this signal, the Stderr communication link - * has to be turned on in start(). - * - * You should copy the information contained in @p buffer to your private - * data structures before returning from the slot. - * - * @param proc a pointer to the process that has received the data - * @param buffer The data received. - * @param buflen The number of bytes that are available. - **/ - void receivedStderr(K3Process *proc, char *buffer, int buflen); - - /** - * Emitted after all the data that has been - * specified by a prior call to writeStdin() has actually been - * written to the child process. - * @param proc a pointer to the process - **/ - void wroteStdin(K3Process *proc); - - -protected Q_SLOTS: - - /** - * This slot gets activated when data from the child's stdout arrives. - * It usually calls childOutput(). - * @param fdno the file descriptor for the output - */ - void slotChildOutput(int fdno); - - /** - * This slot gets activated when data from the child's stderr arrives. - * It usually calls childError(). - * @param fdno the file descriptor for the output - */ - void slotChildError(int fdno); - - /** - * Called when another bulk of data can be sent to the child's - * stdin. If there is no more data to be sent to stdin currently - * available, this function must disable the QSocketNotifier innot. - * @param dummy ignore this argument - */ - void slotSendData(int dummy); // KDE 4: remove dummy - -protected: - - /** - * Sets up the environment according to the data passed via - * setEnvironment() - */ - void setupEnvironment(); - - /** - * The list of the process' command line arguments. The first entry - * in this list is the executable itself. - */ - QList arguments; - /** - * How to run the process (Block, NotifyOnExit, DontCare). You should - * not modify this data member directly from derived classes. - */ - RunMode run_mode; - /** - * true if the process is currently running. You should not - * modify this data member directly from derived classes. Please use - * isRunning() for reading the value of this data member since it - * will probably be made private in later versions of K3Process. - */ - bool runs; - - /** - * The PID of the currently running process. - * You should not modify this data member in derived classes. - * Please use pid() instead of directly accessing this - * member since it will probably be made private in - * later versions of K3Process. - */ - pid_t pid_; - - /** - * The process' exit status as returned by waitpid(). You should not - * modify the value of this data member from derived classes. You should - * rather use exitStatus() than accessing this data member directly - * since it will probably be made private in further versions of - * K3Process. - */ - int status; - - - /** - * If false, the child process' effective uid & gid will be reset to the - * real values. - * @see setRunPrivileged() - */ - bool keepPrivs; - - /** - * This function is called from start() right before a fork() takes - * place. According to the @p comm parameter this function has to initialize - * the in, out and err data members of K3Process. - * - * This function should return 1 if setting the needed communication channels - * was successful. - * - * The default implementation is to create UNIX STREAM sockets for the - * communication, but you could reimplement this function to establish a - * TCP/IP communication for network communication, for example. - */ - virtual int setupCommunication(Communication comm); - - /** - * Called right after a (successful) fork() on the parent side. This function - * will usually do some communications cleanup, like closing in[0], - * out[1] and out[1]. - * - * Furthermore, it must also create the QSocketNotifiers innot, - * outnot and errnot and connect their Qt signals to the respective - * K3Process slots. - * - * For a more detailed explanation, it is best to have a look at the default - * implementation in kprocess.cpp. - */ - virtual int commSetupDoneP(); - - /** - * Called right after a (successful) fork(), but before an exec() on the child - * process' side. It usually duplicates the in[0], out[1] and - * err[1] file handles to the respective standard I/O handles. - */ - virtual int commSetupDoneC(); - - - /** - * Immediately called after a successfully started process in NotifyOnExit - * mode has exited. This function normally calls commClose() - * and emits the processExited() signal. - * @param state the exit code of the process as returned by waitpid() - */ - virtual void processHasExited(int state); - - /** - * Cleans up the communication links to the child after it has exited. - * This function should act upon the values of pid() and runs. - * See the kprocess.cpp source for details. - * @li If pid() returns zero, the communication links should be closed - * only. - * @li if pid() returns non-zero and runs is false, all data - * immediately available from the communication links should be processed - * before closing them. - * @li if pid() returns non-zero and runs is true, the communication - * links should be monitored for data until the file handle returned by - * K3ProcessController::theKProcessController->notifierFd() becomes ready - * for reading - when it triggers, runs should be reset to false, and - * the function should be immediately left without closing anything. - * - * The previous semantics of this function are forward-compatible, but should - * be avoided, as they are prone to race conditions and can cause K3Process - * (and thus the whole program) to lock up under certain circumstances. At the - * end the function closes the communication links in any case. Additionally - * @li if runs is true, the communication links are monitored for data - * until all of them have returned EOF. Note that if any system function is - * interrupted (errno == EINTR) the polling loop should be aborted. - * @li if runs is false, all data immediately available from the - * communication links is processed. - */ - virtual void commClose(); - - /* KDE 4 - commClose will be changed to perform cleanup only in all cases * - * If @p notfd is -1, all data immediately available from the - * communication links should be processed. - * If @p notfd is not -1, the communication links should be monitored - * for data until the file handle @p notfd becomes ready for reading. - */ -// virtual void commDrain(int notfd); - - /** - * Specify the actual executable that should be started (first argument to execve) - * Normally the the first argument is the executable but you can - * override that with this function. - */ - void setBinaryExecutable(const char *filename); - - /** - * The socket descriptors for stdout. - */ - int out[2]; - /** - * The socket descriptors for stdin. - */ - int in[2]; - /** - * The socket descriptors for stderr. - */ - int err[2]; - - /** - * The socket notifier for in[1]. - */ - QSocketNotifier *innot; - /** - * The socket notifier for out[0]. - */ - QSocketNotifier *outnot; - /** - * The socket notifier for err[0]. - */ - QSocketNotifier *errnot; - - /** - * Lists the communication links that are activated for the child - * process. Should not be modified from derived classes. - */ - Communication communication; - - /** - * Called by slotChildOutput() this function copies data arriving from - * the child process' stdout to the respective buffer and emits the signal - * receivedStdout(). - */ - int childOutput(int fdno); - - /** - * Called by slotChildError() this function copies data arriving from - * the child process' stderr to the respective buffer and emits the signal - * receivedStderr(). - */ - int childError(int fdno); - - /** - * The buffer holding the data that has to be sent to the child - */ - const char *input_data; - /** - * The number of bytes already transmitted - */ - int input_sent; - /** - * The total length of input_data - */ - int input_total; - - /** - * K3ProcessController is a friend of K3Process because it has to have - * access to various data members. - */ - friend class K3ProcessController; - -private: - K3ProcessPrivate* const d; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(K3Process::Communication) - -class K3ShellProcessPrivate; - -/** -* @obsolete -* -* Use K3Process and K3Process::setUseShell(true) instead. -* -* @short A class derived from K3Process to start child -* processes through a shell. -* @author Christian Czezatke -*/ -class K3ShellProcess : public K3Process -{ - Q_OBJECT - -public: - - /** - * Constructor - * - * If no shellname is specified, the user's default shell is used. - */ - explicit K3ShellProcess(const char *shellname=0); - - /** - * Destructor. - */ - ~K3ShellProcess(); - - virtual bool start(RunMode runmode = NotifyOnExit, - Communication comm = NoCommunication); - - static QString quote(const QString &arg); - -private: - K3ShellProcessPrivate* const d; -}; - - - -#endif - diff --git a/libk3b/core/k3processcontroller.cpp b/libk3b/core/k3processcontroller.cpp deleted file mode 100644 index 2d7e4d70a..000000000 --- a/libk3b/core/k3processcontroller.cpp +++ /dev/null @@ -1,334 +0,0 @@ -/* This file is part of the KDE libraries - Copyright (C) 1997 Christian Czezakte (e9025461@student.tuwien.ac.at) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#include "k3processcontroller.h" -#include "k3process.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -class K3ProcessController::Private -{ -public: - Private() - : needcheck( false ), - notifier( 0 ) - { - } - - ~Private() - { - delete notifier; - } - - int fd[2]; - bool needcheck; - QSocketNotifier *notifier; - QList kProcessList; - QList unixProcessList; - static struct sigaction oldChildHandlerData; - static bool handlerSet; - static int refCount; - static K3ProcessController* instance; -}; - -K3ProcessController *K3ProcessController::Private::instance = 0; -int K3ProcessController::Private::refCount = 0; - -void K3ProcessController::ref() -{ - if ( !Private::refCount ) { - Private::instance = new K3ProcessController; - setupHandlers(); - } - Private::refCount++; -} - -void K3ProcessController::deref() -{ - // trueg: the ref handling is broken. I don't understand the code enough to fix it and doubt the - // saved memory in kde3support is worth the time to search for the bug. -#if 0 - Private::refCount--; - if( !Private::refCount ) { - resetHandlers(); - delete Private::instance; - Private::instance = 0; - } -#endif -} - -K3ProcessController* K3ProcessController::instance() -{ - /* - * there were no safety guards in previous revisions, is that ok? - if ( !Private::instance ) { - ref(); - } - */ - - return Private::instance; -} - -K3ProcessController::K3ProcessController() - : d( new Private ) -{ - if( pipe( d->fd ) ) - { - perror( "pipe" ); - abort(); - } - - fcntl( d->fd[0], F_SETFL, O_NONBLOCK ); // in case slotDoHousekeeping is called without polling first - fcntl( d->fd[1], F_SETFL, O_NONBLOCK ); // in case it fills up - fcntl( d->fd[0], F_SETFD, FD_CLOEXEC ); - fcntl( d->fd[1], F_SETFD, FD_CLOEXEC ); - - d->notifier = new QSocketNotifier( d->fd[0], QSocketNotifier::Read ); - d->notifier->setEnabled( true ); - QObject::connect( d->notifier, SIGNAL(activated(int)), - SLOT(slotDoHousekeeping())); -} - -K3ProcessController::~K3ProcessController() -{ -#ifndef Q_OS_MAC -/* not sure why, but this is causing lockups */ - close( d->fd[0] ); - close( d->fd[1] ); -#else -#warning FIXME: why does close() freeze up destruction? -#endif - - delete d; -} - - -extern "C" { -static void theReaper( int num ) -{ - K3ProcessController::theSigCHLDHandler( num ); -} -} - -#ifdef Q_OS_UNIX -struct sigaction K3ProcessController::Private::oldChildHandlerData; -#endif -bool K3ProcessController::Private::handlerSet = false; - -void K3ProcessController::setupHandlers() -{ - if( Private::handlerSet ) - return; - Private::handlerSet = true; - -#ifdef Q_OS_UNIX - struct sigaction act; - sigemptyset( &act.sa_mask ); - - act.sa_handler = SIG_IGN; - act.sa_flags = 0; - sigaction( SIGPIPE, &act, 0L ); - - act.sa_handler = theReaper; - act.sa_flags = SA_NOCLDSTOP; - // CC: take care of SunOS which automatically restarts interrupted system - // calls (and thus does not have SA_RESTART) -#ifdef SA_RESTART - act.sa_flags |= SA_RESTART; -#endif - sigaction( SIGCHLD, &act, &Private::oldChildHandlerData ); - - sigaddset( &act.sa_mask, SIGCHLD ); - // Make sure we don't block this signal. gdb tends to do that :-( - sigprocmask( SIG_UNBLOCK, &act.sa_mask, 0 ); -#else - //TODO: win32 -#endif -} - -void K3ProcessController::resetHandlers() -{ - if( !Private::handlerSet ) - return; - Private::handlerSet = false; - -#ifdef Q_OS_UNIX - sigset_t mask, omask; - sigemptyset( &mask ); - sigaddset( &mask, SIGCHLD ); - sigprocmask( SIG_BLOCK, &mask, &omask ); - - struct sigaction act; - sigaction( SIGCHLD, &Private::oldChildHandlerData, &act ); - if (act.sa_handler != theReaper) { - sigaction( SIGCHLD, &act, 0 ); - Private::handlerSet = true; - } - - sigprocmask( SIG_SETMASK, &omask, 0 ); -#else - //TODO: win32 -#endif - // there should be no problem with SIGPIPE staying SIG_IGN -} - -// the pipe is needed to sync the child reaping with our event processing, -// as otherwise there are race conditions, locking requirements, and things -// generally get harder -void K3ProcessController::theSigCHLDHandler( int arg ) -{ - int saved_errno = errno; - - char dummy = 0; - ::write( instance()->d->fd[1], &dummy, 1 ); - -#ifdef Q_OS_UNIX - if ( Private::oldChildHandlerData.sa_handler != SIG_IGN && - Private::oldChildHandlerData.sa_handler != SIG_DFL ) { - Private::oldChildHandlerData.sa_handler( arg ); // call the old handler - } -#else - //TODO: win32 -#endif - - errno = saved_errno; -} - -int K3ProcessController::notifierFd() const -{ - return d->fd[0]; -} - -void K3ProcessController::unscheduleCheck() -{ - char dummy[16]; // somewhat bigger - just in case several have queued up - if( ::read( d->fd[0], dummy, sizeof(dummy) ) > 0 ) - d->needcheck = true; -} - -void -K3ProcessController::rescheduleCheck() -{ - if( d->needcheck ) - { - d->needcheck = false; - char dummy = 0; - ::write( d->fd[1], &dummy, 1 ); - } -} - -void K3ProcessController::slotDoHousekeeping() -{ - char dummy[16]; // somewhat bigger - just in case several have queued up - ::read( d->fd[0], dummy, sizeof(dummy) ); - - int status; - again: - QList::iterator it( d->kProcessList.begin() ); - QList::iterator eit( d->kProcessList.end() ); - while( it != eit ) - { - K3Process *prc = *it; - if( prc->runs && waitpid( prc->pid_, &status, WNOHANG ) > 0 ) - { - prc->processHasExited( status ); - // the callback can nuke the whole process list and even 'this' - if (!instance()) - return; - goto again; - } - ++it; - } - QList::iterator uit( d->unixProcessList.begin() ); - QList::iterator ueit( d->unixProcessList.end() ); - while( uit != ueit ) - { - if( waitpid( *uit, 0, WNOHANG ) > 0 ) - { - uit = d->unixProcessList.erase( uit ); - deref(); // counterpart to addProcess, can invalidate 'this' - } else - ++uit; - } -} - -bool K3ProcessController::waitForProcessExit( int timeout ) -{ -#ifdef Q_OS_UNIX - for(;;) - { - struct timeval tv, *tvp; - if (timeout < 0) - tvp = 0; - else - { - tv.tv_sec = timeout; - tv.tv_usec = 0; - tvp = &tv; - } - - fd_set fds; - FD_ZERO( &fds ); - FD_SET( d->fd[0], &fds ); - - switch( select( d->fd[0]+1, &fds, 0, 0, tvp ) ) - { - case -1: - if( errno == EINTR ) - continue; - // fall through; should never happen - case 0: - return false; - default: - slotDoHousekeeping(); - return true; - } - } -#else - //TODO: win32 - return false; -#endif -} - -void K3ProcessController::addKProcess( K3Process* p ) -{ - d->kProcessList.append( p ); -} - -void K3ProcessController::removeKProcess( K3Process* p ) -{ - d->kProcessList.removeAll( p ); -} - -void K3ProcessController::addProcess( int pid ) -{ - d->unixProcessList.append( pid ); - ref(); // make sure we stay around when the K3Process goes away -} - -#include "k3processcontroller.moc" diff --git a/libk3b/core/k3processcontroller.h b/libk3b/core/k3processcontroller.h deleted file mode 100644 index 034d4576a..000000000 --- a/libk3b/core/k3processcontroller.h +++ /dev/null @@ -1,135 +0,0 @@ -/* This file is part of the KDE libraries - Copyright (C) 1997 Christian Czezakte (e9025461@student.tuwien.ac.at) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#ifndef K3PROCCTRL_H -#define K3PROCCTRL_H - -#include -#include "k3process.h" - - -/** - * @short Used internally by K3Process - * @internal - * @author Christian Czezatke - * - * A class for internal use by K3Process only. -- Exactly one instance - * of this class is created by KApplication. - * - * This class takes care of the actual (UN*X) signal handling. - */ -class K3ProcessController : public QObject -{ - Q_OBJECT - -public: - /** - * Create an instance if none exists yet. - * Called by KApplication::KApplication() - */ - static void ref(); - - /** - * Destroy the instance if one exists and it is not referenced any more. - * Called by KApplication::~KApplication() - */ - static void deref(); - - /** - * Only a single instance of this class is allowed at a time. - * This method provides access to that instance. - */ - static K3ProcessController *instance(); - - /** - * Automatically called upon SIGCHLD. Never call it directly. - * If your application (or some library it uses) redirects SIGCHLD, - * the new signal handler (and only it) should call the old handler - * returned by sigaction(). - * @internal - */ - static void theSigCHLDHandler(int signal); // KDE4: private - - /** - * Wait for any process to exit and handle their exit without - * starting an event loop. - * This function may cause K3Process to emit any of its signals. - * - * @param timeout the timeout in seconds. -1 means no timeout. - * @return true if a process exited, false - * if no process exited within @p timeout seconds. - */ - bool waitForProcessExit(int timeout); - - /** - * Call this function to defer processing of the data that became available - * on notifierFd(). - */ - void unscheduleCheck(); - - /** - * This function @em must be called at some point after calling - * unscheduleCheck(). - */ - void rescheduleCheck(); - - /* - * Obtain the file descriptor K3ProcessController uses to get notified - * about process exits. select() or poll() on it if you create a custom - * event loop that needs to act upon SIGCHLD. - * @return the file descriptor of the reading end of the notification pipe - */ - int notifierFd() const; - - /** - * @internal - */ - void addKProcess( K3Process* ); - /** - * @internal - */ - void removeKProcess( K3Process* ); - /** - * @internal - */ - void addProcess( int pid ); - -private Q_SLOTS: - void slotDoHousekeeping(); - -private: - friend class I_just_love_gcc; - - static void setupHandlers(); - static void resetHandlers(); - - // Disallow instantiation - K3ProcessController(); - ~K3ProcessController(); - - // Disallow assignment and copy-construction - K3ProcessController( const K3ProcessController& ); - K3ProcessController& operator= ( const K3ProcessController& ); - - class Private; - Private * const d; -}; - -#endif - diff --git a/libk3b/jobs/k3baudiosessionreadingjob.cpp b/libk3b/jobs/k3baudiosessionreadingjob.cpp index a1dca9ef3..6a8df76eb 100644 --- a/libk3b/jobs/k3baudiosessionreadingjob.cpp +++ b/libk3b/jobs/k3baudiosessionreadingjob.cpp @@ -1,250 +1,250 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3baudiosessionreadingjob.h" #include #include #include #include #include #include #include #include #include #include class K3b::AudioSessionReadingJob::Private { public: Private(); ~Private(); - int fd; + QIODevice* ioDev; K3b::CdparanoiaLib* paranoia; K3b::Device::Device* device; K3b::Device::Toc toc; K3b::WaveFileWriter* waveFileWriter; QStringList filenames; int paranoiaMode; int retries; bool neverSkip; }; K3b::AudioSessionReadingJob::Private::Private() - : fd(-1), + : ioDev( 0 ), paranoia(0), waveFileWriter(0), paranoiaMode(0), retries(50), neverSkip(false) { } K3b::AudioSessionReadingJob::Private::~Private() { delete waveFileWriter; delete paranoia; } K3b::AudioSessionReadingJob::AudioSessionReadingJob( K3b::JobHandler* jh, QObject* parent ) : K3b::ThreadJob( jh, parent ), d( new Private() ) { } K3b::AudioSessionReadingJob::~AudioSessionReadingJob() { delete d; } void K3b::AudioSessionReadingJob::setDevice( K3b::Device::Device* dev ) { d->device = dev; d->toc = K3b::Device::Toc(); } void K3b::AudioSessionReadingJob::setToc( const K3b::Device::Toc& toc ) { d->toc = toc; } -void K3b::AudioSessionReadingJob::writeToFd( int fd ) +void K3b::AudioSessionReadingJob::writeTo( QIODevice* ioDev ) { - d->fd = fd; + d->ioDev = ioDev; } void K3b::AudioSessionReadingJob::setImageNames( const QStringList& l ) { d->filenames = l; - d->fd = -1; + d->ioDev = 0; } void K3b::AudioSessionReadingJob::setParanoiaMode( int m ) { d->paranoiaMode = m; } void K3b::AudioSessionReadingJob::setReadRetries( int r ) { d->retries = r; } void K3b::AudioSessionReadingJob::setNeverSkip( bool b ) { d->neverSkip = b; } void K3b::AudioSessionReadingJob::start() { k3bcore->blockDevice( d->device ); K3b::ThreadJob::start(); } void K3b::AudioSessionReadingJob::jobFinished( bool success ) { k3bcore->unblockDevice( d->device ); K3b::ThreadJob::jobFinished( success ); } bool K3b::AudioSessionReadingJob::run() { if( !d->paranoia ) d->paranoia = K3b::CdparanoiaLib::create(); if( !d->paranoia ) { emit infoMessage( i18n("Could not load libcdparanoia."), K3b::Job::ERROR ); return false; } if( d->toc.isEmpty() ) d->toc = d->device->readToc(); if( !d->paranoia->initParanoia( d->device, d->toc ) ) { emit infoMessage( i18n("Could not open device %1", d->device->blockDeviceName()), K3b::Job::ERROR ); return false; } if( !d->paranoia->initReading() ) { emit infoMessage( i18n("Error while initializing audio ripping."), K3b::Job::ERROR ); return false; } d->device->block( true ); // init settings d->paranoia->setMaxRetries( d->retries ); d->paranoia->setParanoiaMode( d->paranoiaMode ); d->paranoia->setNeverSkip( d->neverSkip ); bool writeError = false; unsigned int trackNum = 1; unsigned int currentTrack = 0; unsigned long trackRead = 0; unsigned long totalRead = 0; unsigned int lastTrackPercent = 0; unsigned int lastTotalPercent = 0; bool newTrack = true; int status = 0; char* buffer = 0; - while( !canceled() && (buffer = d->paranoia->read( &status, &trackNum, d->fd == -1 /*when writing to a wav be want little endian */ )) ) { + while( !canceled() && (buffer = d->paranoia->read( &status, &trackNum, !d->ioDev /*when writing to a wav be want little endian */ )) ) { if( currentTrack != trackNum ) { emit nextTrack( trackNum, d->paranoia->toc().count() ); trackRead = 0; lastTrackPercent = 0; currentTrack = trackNum; newTrack = true; } - if( d->fd > 0 ) { - if( ::write( d->fd, buffer, CD_FRAMESIZE_RAW ) != CD_FRAMESIZE_RAW ) { - kDebug() << "(K3b::AudioSessionCopyJob::WorkThread) error while writing to fd " << d->fd; + if( d->ioDev ) { + if( d->ioDev->write( buffer, CD_FRAMESIZE_RAW ) != CD_FRAMESIZE_RAW ) { + kDebug() << "(K3b::AudioSessionCopyJob::WorkThread) error while writing to device " << d->ioDev; writeError = true; break; } } else { if( newTrack ) { newTrack = false; if( !d->waveFileWriter ) d->waveFileWriter = new K3b::WaveFileWriter(); if( d->filenames.count() < ( int )currentTrack ) { kDebug() << "(K3b::AudioSessionCopyJob) not enough image filenames given: " << currentTrack; writeError = true; break; } if( !d->waveFileWriter->open( d->filenames[currentTrack-1] ) ) { emit infoMessage( i18n("Unable to open '%1' for writing.", d->filenames[currentTrack-1]), K3b::Job::ERROR ); writeError = true; break; } } d->waveFileWriter->write( buffer, CD_FRAMESIZE_RAW, K3b::WaveFileWriter::LittleEndian ); } trackRead++; totalRead++; unsigned int trackPercent = 100 * trackRead / d->toc[currentTrack-1].length().lba(); if( trackPercent > lastTrackPercent ) { lastTrackPercent = trackPercent; emit subPercent( lastTrackPercent ); } unsigned int totalPercent = 100 * totalRead / d->paranoia->rippedDataLength(); if( totalPercent > lastTotalPercent ) { lastTotalPercent = totalPercent; emit percent( lastTotalPercent ); } } if( d->waveFileWriter ) d->waveFileWriter->close(); d->paranoia->close(); d->device->block( false ); if( status != K3b::CdparanoiaLib::S_OK ) { emit infoMessage( i18n("Unrecoverable error while ripping track %1.", trackNum), K3b::Job::ERROR ); return false; } return !writeError && !canceled(); } #include "k3baudiosessionreadingjob.moc" diff --git a/libk3b/jobs/k3baudiosessionreadingjob.h b/libk3b/jobs/k3baudiosessionreadingjob.h index ac7a13e90..3c2f66b4d 100644 --- a/libk3b/jobs/k3baudiosessionreadingjob.h +++ b/libk3b/jobs/k3baudiosessionreadingjob.h @@ -1,75 +1,76 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_AUDIOSESSION_READING_JOB_H_ #define _K3B_AUDIOSESSION_READING_JOB_H_ #include #include +class QIODevice; namespace K3b { namespace Device { class Device; class Toc; } class AudioSessionReadingJob : public ThreadJob { Q_OBJECT public: AudioSessionReadingJob( JobHandler*, QObject* parent = 0 ); ~AudioSessionReadingJob(); /** * For now this simply reads all the audio tracks at the beginning * since we only support CD-Extra mixed mode cds. */ void setDevice( Device::Device* ); /** * Use for faster initialization */ void setToc( const Device::Toc& toc ); /** - * the data gets written directly into fd instead of imagefiles. - * To disable just set fd to -1 (the default) + * the data gets written directly into ioDev instead of imagefiles. + * To disable just set ioDev to 0 (the default) */ - void writeToFd( int fd ); + void writeTo( QIODevice* ioDev ); /** * Used if fd == -1 */ void setImageNames( const QStringList& l ); void setParanoiaMode( int m ); void setReadRetries( int ); void setNeverSkip( bool b ); public Q_SLOTS: void start(); private: void jobFinished( bool ); bool run(); class Private; Private* const d; }; } #endif diff --git a/libk3b/jobs/k3bcdcopyjob.cpp b/libk3b/jobs/k3bcdcopyjob.cpp index ac22cb488..e72902bfb 100644 --- a/libk3b/jobs/k3bcdcopyjob.cpp +++ b/libk3b/jobs/k3bcdcopyjob.cpp @@ -1,1253 +1,1253 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bcdcopyjob.h" #include "k3baudiosessionreadingjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K3b::CdCopyJob::Private { public: Private() : canceled(false), running(false), readcdReader(0), dataTrackReader(0), audioSessionReader(0), cdrecordWriter(0), infFileWriter(0), cddb(0) { } bool canceled; bool error; bool readingSuccessful; bool running; int numSessions; bool doNotCloseLastSession; int doneCopies; int currentReadSession; int currentWrittenSession; K3b::Device::Toc toc; QByteArray cdTextRaw; K3b::ReadcdReader* readcdReader; K3b::DataTrackReader* dataTrackReader; K3b::AudioSessionReadingJob* audioSessionReader; K3b::CdrecordWriter* cdrecordWriter; K3b::InfFileWriter* infFileWriter; bool audioReaderRunning; bool dataReaderRunning; bool writerRunning; // image filenames, one for every track QStringList imageNames; // inf-filenames for writing audio tracks QStringList infNames; // indicates if we created a dir or not bool deleteTempDir; KCDDB::Client* cddb; KCDDB::CDInfo cddbInfo; bool haveCddb; bool haveCdText; QVector dataSessionProbablyTAORecorded; // used to determine progress QVector sessionSizes; long overallSize; }; K3b::CdCopyJob::CdCopyJob( K3b::JobHandler* hdl, QObject* parent ) : K3b::BurnJob( hdl, parent ), m_simulate(false), m_copies(1), m_onlyCreateImages(false), m_onTheFly(true), m_ignoreDataReadErrors(false), m_ignoreAudioReadErrors(true), m_noCorrection(false), m_dataReadRetries(128), m_audioReadRetries(5), m_copyCdText(true), m_writingMode( K3b::WRITING_MODE_AUTO ) { d = new Private(); } K3b::CdCopyJob::~CdCopyJob() { delete d->infFileWriter; delete d; } void K3b::CdCopyJob::start() { d->running = true; d->canceled = false; d->error = false; d->readingSuccessful = false; d->audioReaderRunning = d->dataReaderRunning = d->writerRunning = false; d->sessionSizes.clear(); d->dataSessionProbablyTAORecorded.clear(); d->deleteTempDir = false; d->haveCdText = false; d->haveCddb = false; jobStarted(); emit newTask( i18n("Checking Source Medium") ); emit burning(false); emit newSubTask( i18n("Waiting for source medium") ); // wait for a source disk if( waitForMedia( m_readerDevice, K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE, K3b::Device::MEDIA_WRITABLE_CD|K3b::Device::MEDIA_CD_ROM ) < 0 ) { finishJob( true, false ); return; } emit newSubTask( i18n("Checking source medium") ); // FIXME: read ISRCs and MCN connect( K3b::Device::diskInfo( m_readerDevice ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) ); } void K3b::CdCopyJob::slotDiskInfoReady( K3b::Device::DeviceHandler* dh ) { if( dh->success() ) { d->toc = dh->toc(); // // for now we copy audio, pure data (aka 1 data track), cd-extra (2 session, audio and data), // and data multisession which one track per session. // Everything else will be rejected // bool canCopy = true; bool audio = false; d->numSessions = dh->diskInfo().numSessions(); d->doNotCloseLastSession = (dh->diskInfo().diskState() == K3b::Device::STATE_INCOMPLETE); switch( dh->toc().contentType() ) { case K3b::Device::DATA: // check if every track is in it's own session // only then we copy the cd if( (int)dh->toc().count() != dh->diskInfo().numSessions() ) { emit infoMessage( i18n("K3b does not copy CDs containing multiple data tracks."), ERROR ); canCopy = false; } else if( dh->diskInfo().numSessions() > 1 ) emit infoMessage( i18n("Copying Multisession Data CD."), INFO ); else emit infoMessage( i18n("Copying Data CD."), INFO ); break; case K3b::Device::MIXED: audio = true; if( dh->diskInfo().numSessions() != 2 || d->toc[0].type() != K3b::Device::Track::TYPE_AUDIO ) { emit infoMessage( i18n("K3b can only copy CD-Extra mixed mode CDs."), ERROR ); canCopy = false; } else emit infoMessage( i18n("Copying Enhanced Audio CD (CD-Extra)."), INFO ); break; case K3b::Device::AUDIO: audio = true; emit infoMessage( i18n("Copying Audio CD."), INFO ); break; case K3b::Device::NONE: default: emit infoMessage( i18n("The source disk is empty."), ERROR ); canCopy = false; break; } // // A data track recorded in TAO mode has two run-out blocks which cannot be read and contain // zero data anyway. The problem is that I do not know of a valid method to determine if a track // was written in TAO (the control nibble does definitely not work, I never saw one which did not // equal 4). // So the solution for now is to simply try to read the last sector of a data track. If this is not // possible we assume it was written in TAO mode and reduce the length by 2 sectors // unsigned char buffer[2048]; int i = 1; for( K3b::Device::Toc::iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { if( (*it).type() == K3b::Device::Track::TYPE_DATA ) { // we try twice just to be sure if( m_readerDevice->read10( buffer, 2048, (*it).lastSector().lba(), 1 ) || m_readerDevice->read10( buffer, 2048, (*it).lastSector().lba(), 1 ) ) { d->dataSessionProbablyTAORecorded.append(false); kDebug() << "(K3b::CdCopyJob) track " << i << " probably DAO recorded."; } else { d->dataSessionProbablyTAORecorded.append(true); kDebug() << "(K3b::CdCopyJob) track " << i << " probably TAO recorded."; } } ++i; } // // To copy mode2 data tracks we need cdrecord >= 2.01a12 which introduced the -xa1 and -xamix options // if( k3bcore->externalBinManager()->binObject("cdrecord") && !k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) ) { for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) { if( (*it).type() == K3b::Device::Track::TYPE_DATA && ( (*it).mode() == K3b::Device::Track::XA_FORM1 || (*it).mode() == K3b::Device::Track::XA_FORM2 ) ) { emit infoMessage( i18n("K3b needs cdrecord 2.01a12 or newer to copy Mode2 data tracks."), ERROR ); finishJob( true, false ); return; } } } // // It is not possible to create multisession cds in raw writing mode // if( d->numSessions > 1 && m_writingMode == K3b::WRITING_MODE_RAW ) { if( !questionYesNo( i18n("You will only be able to copy the first session in raw writing mode. " "Continue anyway?"), i18n("Multisession CD") ) ) { finishJob( true, false ); return; } else { emit infoMessage( i18n("Only copying first session."), WARNING ); // TODO: remove the second session from the progress stuff } } // // We already create the temp filenames here since we need them to check the free space // if( !m_onTheFly || m_onlyCreateImages ) { if( !prepareImageFiles() ) { finishJob( false, true ); return; } // // check free temp space // KIO::filesize_t imageSpaceNeeded = 0; for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) { if( (*it).type() == K3b::Device::Track::TYPE_AUDIO ) imageSpaceNeeded += (*it).length().audioBytes() + 44; else imageSpaceNeeded += (*it).length().mode1Bytes(); } unsigned long avail, size; QString pathToTest = m_tempPath.left( m_tempPath.lastIndexOf( '/' ) ); if( !K3b::kbFreeOnFs( pathToTest, size, avail ) ) { emit infoMessage( i18n("Unable to determine free space in temporary directory '%1'.",pathToTest), ERROR ); d->error = true; canCopy = false; } else { if( avail < imageSpaceNeeded/1024 ) { emit infoMessage( i18n("Not enough space left in temporary directory."), ERROR ); d->error = true; canCopy = false; } } } if( canCopy ) { if( K3b::isMounted( m_readerDevice ) ) { emit infoMessage( i18n("Unmounting source medium"), INFO ); K3b::unmount( m_readerDevice ); } d->overallSize = 0; // now create some progress helper values for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) { d->overallSize += (*it).length().lba(); if( d->sessionSizes.isEmpty() || (*it).type() == K3b::Device::Track::TYPE_DATA ) d->sessionSizes.append( (*it).length().lba() ); else d->sessionSizes[0] += (*it).length().lba(); } if( audio && !m_onlyCreateImages ) { if( m_copyCdText ) searchCdText(); else queryCddb(); } else startCopy(); } else { finishJob( false, true ); } } else { emit infoMessage( i18n("Unable to read TOC"), ERROR ); finishJob( false, true ); } } void K3b::CdCopyJob::searchCdText() { emit newSubTask( i18n("Searching CD-TEXT") ); connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::CD_TEXT_RAW, m_readerDevice ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotCdTextReady(K3b::Device::DeviceHandler*)) ); } void K3b::CdCopyJob::slotCdTextReady( K3b::Device::DeviceHandler* dh ) { if( dh->success() ) { if( K3b::Device::CdText::checkCrc( dh->cdTextRaw() ) ) { K3b::Device::CdText cdt( dh->cdTextRaw() ); emit infoMessage( i18n("Found CD-TEXT (%1 - %2).",cdt.performer(),cdt.title()), SUCCESS ); d->haveCdText = true; d->cdTextRaw = dh->cdTextRaw(); } else { emit infoMessage( i18n("Found corrupted CD-TEXT. Ignoring it."), WARNING ); d->haveCdText = false; } } else { emit infoMessage( i18n("No CD-TEXT found."), INFO ); d->haveCdText = false; } queryCddb(); } void K3b::CdCopyJob::queryCddb() { emit newSubTask( i18n("Querying Cddb") ); d->haveCddb = false; if( !d->cddb ) { d->cddb = new KCDDB::Client(); d->cddb->setBlockingMode( false ); connect( d->cddb, SIGNAL( finished( KCDDB::Result ) ), this, SLOT( slotCddbQueryFinished( KCDDB::Result ) ) ); } d->cddb->config().readConfig(); d->cddb->lookup( K3b::CDDB::createTrackOffsetList( d->toc ) ); } void K3b::CdCopyJob::slotCddbQueryFinished( KCDDB::Result result ) { if( result == KCDDB::Success ) { d->cddbInfo = d->cddb->lookupResponse().first(); d->haveCddb = true; emit infoMessage( i18n("Found Cddb entry (%1 - %2).", d->cddbInfo.get( KCDDB::Artist ).toString(), d->cddbInfo.get( KCDDB::Title ).toString() ), SUCCESS ); // save the entry locally d->cddb->store( d->cddbInfo, K3b::CDDB::createTrackOffsetList( d->toc ) ); } else if ( result == KCDDB::MultipleRecordFound ) { KCDDB::CDInfoList results = d->cddb->lookupResponse(); int i = K3b::CDDB::MultiEntriesDialog::selectCddbEntry( results, qApp->activeWindow() ); if ( i >= 0 ) { d->haveCddb = true; d->cddbInfo = results[i]; // save the entry locally d->cddb->store( d->cddbInfo, K3b::CDDB::createTrackOffsetList( d->toc ) ); } else { d->haveCddb = false; } } else if( result == KCDDB::NoRecordFound ) { emit infoMessage( i18n("No Cddb entry found."), WARNING ); } else { emit infoMessage( i18n("Cddb error (%1).", KCDDB::resultToString( result ) ), ERROR ); } startCopy(); } void K3b::CdCopyJob::startCopy() { d->currentWrittenSession = d->currentReadSession = 1; d->doneCopies = 0; if ( d->haveCdText && d->haveCddb ) { K3b::Device::CdText cdt( d->cdTextRaw ); if ( !questionYesNo( i18n( "Found CD-TEXT (%1 - %2) and Cddb (%3 - %4) entries. " "Which one should be used to generate the CD-TEXT on the new CD?", cdt.performer(), cdt.title(), d->cddbInfo.get( KCDDB::Artist ).toString(), d->cddbInfo.get( KCDDB::Title ).toString() ), i18n( "CD-TEXT" ), i18n( "Use CD-TEXT data" ), i18n( "Use Cddb entry" ) ) ) { d->haveCdText = false; } } if( m_onTheFly ) { emit newSubTask( i18n("Preparing write process...") ); if( writeNextSession() ) readNextSession(); else { finishJob( d->canceled, d->error ); } } else readNextSession(); } void K3b::CdCopyJob::cancel() { d->canceled = true; if( d->writerRunning ) { // // we will handle cleanup in slotWriterFinished() // if we are writing onthefly the reader won't be able to write // anymore and will finish unsuccessfully, too // d->cdrecordWriter->cancel(); } else if( d->audioReaderRunning ) d->audioSessionReader->cancel(); else if( d->dataReaderRunning ) // d->readcdReader->cancel(); d->dataTrackReader->cancel(); } bool K3b::CdCopyJob::prepareImageFiles() { kDebug() << "(K3b::CdCopyJob) prepareImageFiles()"; d->imageNames.clear(); d->infNames.clear(); d->deleteTempDir = false; QFileInfo fi( m_tempPath ); if( d->toc.count() > 1 || d->toc.contentType() == K3b::Device::AUDIO ) { // create a directory which contains all the images and inf and stuff // and save it in some cool structure bool tempDirReady = false; if( !fi.isDir() ) { if( QFileInfo( m_tempPath.section( '/', 0, -2 ) ).isDir() ) { if( !QFile::exists( m_tempPath ) ) { QDir dir( m_tempPath.section( '/', 0, -2 ) ); dir.mkdir( m_tempPath.section( '/', -1 ) ); tempDirReady = true; } else m_tempPath = m_tempPath.section( '/', 0, -2 ); } else { emit infoMessage( i18n("Specified an unusable temporary path. Using default."), WARNING ); m_tempPath = K3b::defaultTempPath(); } } // create temp dir if( !tempDirReady ) { QDir dir( m_tempPath ); m_tempPath = K3b::findUniqueFilePrefix( "k3bCdCopy", m_tempPath ); kDebug() << "(K3b::CdCopyJob) creating temp dir: " << m_tempPath; if( !dir.mkdir( m_tempPath ) ) { emit infoMessage( i18n("Unable to create temporary directory '%1'.",m_tempPath), ERROR ); return false; } d->deleteTempDir = true; } m_tempPath = K3b::prepareDir( m_tempPath ); emit infoMessage( i18n("Using temporary directory %1.",m_tempPath), INFO ); // create temp filenames int i = 1; for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) { if( (*it).type() == K3b::Device::Track::TYPE_AUDIO ) { d->imageNames.append( m_tempPath + QString("Track%1.wav").arg(QString::number(i).rightJustified(2, '0')) ); d->infNames.append( m_tempPath + QString("Track%1.inf").arg(QString::number(i).rightJustified(2, '0')) ); } else d->imageNames.append( m_tempPath + QString("Track%1.iso").arg(QString::number(i).rightJustified(2, '0')) ); ++i; } kDebug() << "(K3b::CdCopyJob) created image filenames:"; for( int i = 0; i < d->imageNames.count(); ++i ) kDebug() << "(K3b::CdCopyJob) " << d->imageNames[i]; return true; } else { // we only need a single image file if( !fi.isFile() || questionYesNo( i18n("Do you want to overwrite %1?",m_tempPath), i18n("File Exists") ) ) { if( fi.isDir() ) m_tempPath = K3b::findTempFile( "iso", m_tempPath ); else if( !QFileInfo( m_tempPath.section( '/', 0, -2 ) ).isDir() ) { emit infoMessage( i18n("Specified an unusable temporary path. Using default."), WARNING ); m_tempPath = K3b::findTempFile( "iso" ); } // else the user specified a file in an existing dir emit infoMessage( i18n("Writing image file to %1.",m_tempPath), INFO ); } else return false; d->imageNames.append( m_tempPath ); return true; } } void K3b::CdCopyJob::readNextSession() { if( !m_onTheFly || m_onlyCreateImages ) { if( d->numSessions > 1 ) emit newTask( i18n("Reading Session %1",d->currentReadSession) ); else emit newTask( i18n("Reading Source Medium") ); if( d->currentReadSession == 1 ) emit newSubTask( i18n("Reading track %1 of %2",QString::number(1),d->toc.count()) ); } // there is only one situation where we need the audiosessionreader: // if the first session is an audio session. That means the first track // is an audio track if( d->currentReadSession == 1 && d->toc[0].type() == K3b::Device::Track::TYPE_AUDIO ) { if( !d->audioSessionReader ) { d->audioSessionReader = new K3b::AudioSessionReadingJob( this, this ); connect( d->audioSessionReader, SIGNAL(nextTrack(int, int)), this, SLOT(slotReadingNextTrack(int, int)) ); connectSubJob( d->audioSessionReader, SLOT(slotSessionReaderFinished(bool)), K3b::Job::DEFAULT_SIGNAL_CONNECTION, K3b::Job::DEFAULT_SIGNAL_CONNECTION, SLOT(slotReaderProgress(int)), SLOT(slotReaderSubProgress(int)) ); } d->audioSessionReader->setDevice( m_readerDevice ); d->audioSessionReader->setToc( d->toc ); d->audioSessionReader->setParanoiaMode( m_paranoiaMode ); d->audioSessionReader->setReadRetries( m_audioReadRetries ); d->audioSessionReader->setNeverSkip( !m_ignoreAudioReadErrors ); if( m_onTheFly ) - d->audioSessionReader->writeToFd( d->cdrecordWriter->fd() ); + d->audioSessionReader->writeTo( d->cdrecordWriter->ioDevice() ); else d->audioSessionReader->setImageNames( d->imageNames ); // the audio tracks are always the first tracks d->audioReaderRunning = true; d->audioSessionReader->start(); } else { if( !d->dataTrackReader ) { d->dataTrackReader = new K3b::DataTrackReader( this, this ); connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) ); connect( d->dataTrackReader, SIGNAL(processedSize(int, int)), this, SLOT(slotReaderProcessedSize(int, int)) ); connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotSessionReaderFinished(bool)) ); connect( d->dataTrackReader, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( d->dataTrackReader, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } d->dataTrackReader->setDevice( m_readerDevice ); d->dataTrackReader->setIgnoreErrors( m_ignoreDataReadErrors ); d->dataTrackReader->setNoCorrection( m_noCorrection ); d->dataTrackReader->setRetries( m_dataReadRetries ); if( m_onlyCreateImages ) d->dataTrackReader->setSectorSize( K3b::DataTrackReader::MODE1 ); else d->dataTrackReader->setSectorSize( K3b::DataTrackReader::AUTO ); K3b::Device::Track* track = 0; int dataTrackIndex = 0; if( d->toc.contentType() == K3b::Device::MIXED ) { track = &d->toc[d->toc.count()-1]; dataTrackIndex = 0; } else { track = &d->toc[d->currentReadSession-1]; // only one track per session dataTrackIndex = d->currentReadSession-1; } // HACK: if the track is TAO recorded cut the two run-out sectors if( d->dataSessionProbablyTAORecorded.count() > dataTrackIndex && d->dataSessionProbablyTAORecorded[dataTrackIndex] ) d->dataTrackReader->setSectorRange( track->firstSector(), track->lastSector() - 2 ); else d->dataTrackReader->setSectorRange( track->firstSector(), track->lastSector() ); int trackNum = d->currentReadSession; if( d->toc.contentType() == K3b::Device::MIXED ) trackNum = d->toc.count(); if( m_onTheFly ) - d->dataTrackReader->writeToFd( d->cdrecordWriter->fd() ); + d->dataTrackReader->writeTo( d->cdrecordWriter->ioDevice() ); else d->dataTrackReader->setImagePath( d->imageNames[trackNum-1] ); d->dataReaderRunning = true; if( !m_onTheFly || m_onlyCreateImages ) slotReadingNextTrack( 1, 1 ); d->dataTrackReader->start(); } } bool K3b::CdCopyJob::writeNextSession() { // we emit our own task since the cdrecord task is way too simple if( d->numSessions > 1 ) { if( m_simulate ) emit newTask( i18n("Simulating Session %1",d->currentWrittenSession) ); else if( m_copies > 1 ) emit newTask( i18n("Writing Copy %1 (Session %2)",d->doneCopies+1,d->currentWrittenSession) ); else emit newTask( i18n("Writing Copy (Session %1)",d->currentWrittenSession) ); } else { if( m_simulate ) emit newTask( i18n("Simulating") ); else if( m_copies > 1 ) emit newTask( i18n("Writing Copy %1",d->doneCopies+1) ); else emit newTask( i18n("Writing Copy") ); } if ( d->currentWrittenSession == 1 ) { emit newSubTask( i18n("Waiting for media") ); if( waitForMedia( m_writerDevice, K3b::Device::STATE_EMPTY, K3b::Device::MEDIA_WRITABLE_CD ) < 0 ) { finishJob( true, false ); return false; } } if( !d->cdrecordWriter ) { d->cdrecordWriter = new K3b::CdrecordWriter( m_writerDevice, this, this ); connect( d->cdrecordWriter, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( d->cdrecordWriter, SIGNAL(percent(int)), this, SLOT(slotWriterProgress(int)) ); connect( d->cdrecordWriter, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); connect( d->cdrecordWriter, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) ); connect( d->cdrecordWriter, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); connect( d->cdrecordWriter, SIGNAL(nextTrack(int, int)), this, SLOT(slotWritingNextTrack(int, int)) ); connect( d->cdrecordWriter, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); connect( d->cdrecordWriter, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); connect( d->cdrecordWriter, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)), this, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)) ); connect( d->cdrecordWriter, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) ); // connect( d->cdrecordWriter, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); connect( d->cdrecordWriter, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( d->cdrecordWriter, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } d->cdrecordWriter->setBurnDevice( m_writerDevice ); d->cdrecordWriter->clearArguments(); d->cdrecordWriter->setSimulate( m_simulate ); d->cdrecordWriter->setBurnSpeed( m_speed ); // create the cdrecord arguments if( d->currentWrittenSession == 1 && d->toc[0].type() == K3b::Device::Track::TYPE_AUDIO ) { // // Audio session // if( !d->infFileWriter ) d->infFileWriter = new K3b::InfFileWriter(); // // create the inf files if not already done // if( d->infNames.isEmpty() || !QFile::exists( d->infNames[0] ) ) { int trackNumber = 1; for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) { const K3b::Device::Track& track = *it; if( track.type() == K3b::Device::Track::TYPE_DATA ) break; d->infFileWriter->setTrack( track ); d->infFileWriter->setTrackNumber( trackNumber ); if( d->haveCddb ) { d->infFileWriter->setTrackTitle( d->cddbInfo.track( trackNumber-1 ).get( KCDDB::Title ).toString() ); d->infFileWriter->setTrackPerformer( d->cddbInfo.track( trackNumber-1 ).get( KCDDB::Artist ).toString() ); d->infFileWriter->setTrackMessage( d->cddbInfo.track( trackNumber-1 ).get( KCDDB::Comment ).toString() ); d->infFileWriter->setAlbumTitle( d->cddbInfo.get( KCDDB::Title ).toString() ); d->infFileWriter->setAlbumPerformer( d->cddbInfo.get( KCDDB::Artist ).toString() ); } if( m_onTheFly ) { d->infFileWriter->setBigEndian( true ); // we let KTempFile choose a temp file but delete it on our own // the same way we delete them when writing with images // It is important that the files have the ending inf because // cdrecord only checks this KTemporaryFile tmp; tmp.setSuffix( ".inf" ); tmp.setAutoRemove( false ); tmp.open(); d->infNames.append( tmp.fileName() ); QTextStream stream( &tmp ); bool success = d->infFileWriter->save( stream ); tmp.close(); if( !success ) return false; } else { d->infFileWriter->setBigEndian( false ); if( !d->infFileWriter->save( d->infNames[trackNumber-1] ) ) return false; } ++trackNumber; } } // // the inf files are ready and named correctly when writing with images // K3b::WritingMode usedWritingMode = m_writingMode; if( usedWritingMode == K3b::WRITING_MODE_AUTO ) { // // there are a lot of writers out there which produce coasters // in dao mode if the CD contains pregaps of length 0 (or maybe already != 2 secs?) // bool zeroPregap = false; if( d->numSessions == 1 ) { for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) { const K3b::Device::Track& track = *it; if( track.index0() == 0 ) { ++it; if( it != d->toc.constEnd() ) zeroPregap = true; --it; } } } if( zeroPregap && m_writerDevice->supportsRawWriting() ) { if( d->numSessions == 1 ) usedWritingMode = K3b::WRITING_MODE_RAW; else usedWritingMode = K3b::WRITING_MODE_TAO; } else if( m_writerDevice->dao() ) usedWritingMode = K3b::WRITING_MODE_DAO; else if( m_writerDevice->supportsRawWriting() ) usedWritingMode = K3b::WRITING_MODE_RAW; else usedWritingMode = K3b::WRITING_MODE_TAO; } d->cdrecordWriter->setWritingMode( usedWritingMode ); if( d->numSessions > 1 ) d->cdrecordWriter->addArgument( "-multi" ); if( d->haveCddb || d->haveCdText ) { if( usedWritingMode == K3b::WRITING_MODE_TAO ) { emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode."), WARNING ); } else if( d->haveCdText ) { // use the raw CDTEXT data d->cdrecordWriter->setRawCdText( d->cdTextRaw ); } else { // make sure the writer job does not create raw cdtext d->cdrecordWriter->setRawCdText( QByteArray() ); // cdrecord will use the cdtext data in the inf files d->cdrecordWriter->addArgument( "-text" ); } } d->cdrecordWriter->addArgument( "-useinfo" ); // // add all the audio tracks // d->cdrecordWriter->addArgument( "-audio" )->addArgument( "-shorttrack" ); for( int i = 0; i < d->infNames.count(); ++i ) { if( m_onTheFly ) d->cdrecordWriter->addArgument( d->infNames[i] ); else d->cdrecordWriter->addArgument( d->imageNames[i] ); } } else { // // Data Session // K3b::Device::Track* track = 0; int dataTrackIndex = 0; if( d->toc.contentType() == K3b::Device::MIXED ) { track = &d->toc[d->toc.count()-1]; dataTrackIndex = 0; } else { track = &d->toc[d->currentWrittenSession-1]; dataTrackIndex = d->currentWrittenSession-1; } bool multi = d->doNotCloseLastSession || (d->numSessions > 1 && d->currentWrittenSession < d->toc.count()); K3b::WritingMode usedWritingMode = m_writingMode; if( usedWritingMode == K3b::WRITING_MODE_AUTO ) { // at least the NEC3540a does write 2056 byte sectors only in tao mode. Same for LG4040b // since writing data tracks in TAO mode is no loss let's default to TAO in the case of 2056 byte // sectors (which is when writing xa form1 sectors here) if( m_writerDevice->dao() && d->toc.count() == 1 && !multi && track->mode() == K3b::Device::Track::MODE1 ) usedWritingMode = K3b::WRITING_MODE_DAO; else usedWritingMode = K3b::WRITING_MODE_TAO; } d->cdrecordWriter->setWritingMode( usedWritingMode ); // // all but the last session of a multisession disk are written in multi mode // and every data track has it's own session which we forced above // if( multi ) d->cdrecordWriter->addArgument( "-multi" ); // just to let the reader init if( m_onTheFly ) d->cdrecordWriter->addArgument( "-waiti" ); if( track->mode() == K3b::Device::Track::MODE1 ) d->cdrecordWriter->addArgument( "-data" ); else if( track->mode() == K3b::Device::Track::XA_FORM1 ) d->cdrecordWriter->addArgument( "-xa1" ); else d->cdrecordWriter->addArgument( "-xamix" ); if( m_onTheFly ) { // HACK: if the track is TAO recorded cut the two run-out sectors unsigned long trackLen = track->length().lba(); if( d->dataSessionProbablyTAORecorded.count() > dataTrackIndex && d->dataSessionProbablyTAORecorded[dataTrackIndex] ) trackLen -= 2; if( track->mode() == K3b::Device::Track::MODE1 ) trackLen = trackLen * 2048; else if( track->mode() == K3b::Device::Track::XA_FORM1 ) trackLen = trackLen * 2056; // see k3bdatatrackreader.h else trackLen = trackLen * 2332; // see k3bdatatrackreader.h d->cdrecordWriter->addArgument( QString("-tsize=%1").arg(trackLen) )->addArgument("-"); } else if( d->toc.contentType() == K3b::Device::MIXED ) d->cdrecordWriter->addArgument( d->imageNames[d->toc.count()-1] ); else d->cdrecordWriter->addArgument( d->imageNames[d->currentWrittenSession-1] ); // clear cd text from previous sessions d->cdrecordWriter->setRawCdText( QByteArray() ); } // // Finally start the writer // emit burning(true); d->writerRunning = true; d->cdrecordWriter->start(); return true; } // both the readcdreader and the audiosessionreader are connected to this slot void K3b::CdCopyJob::slotSessionReaderFinished( bool success ) { d->audioReaderRunning = d->dataReaderRunning = false; if( success ) { if( d->numSessions > 1 ) emit infoMessage( i18n("Successfully read session %1.",d->currentReadSession), SUCCESS ); else emit infoMessage( i18n("Successfully read source disk."), SUCCESS ); if( !m_onTheFly ) { if( d->numSessions > d->currentReadSession ) { d->currentReadSession++; readNextSession(); } else { d->readingSuccessful = true; if( !m_onlyCreateImages ) { if( m_readerDevice == m_writerDevice ) { // eject the media (we do this blocking to know if it worked // becasue if it did not it might happen that k3b overwrites a CD-RW // source) if( !m_readerDevice->eject() ) { blockingInformation( i18n("K3b was unable to eject the source disk. Please do so manually.") ); } } if( !writeNextSession() ) { // nothing is running here... finishJob( d->canceled, d->error ); } } else { finishJob( false, false ); } } } } else { if( !d->canceled ) { emit infoMessage( i18n("Error while reading session %1.",d->currentReadSession), ERROR ); if( m_onTheFly ) d->cdrecordWriter->setSourceUnreadable(true); } finishJob( d->canceled, !d->canceled ); } } void K3b::CdCopyJob::slotWriterFinished( bool success ) { emit burning(false); d->writerRunning = false; if( success ) { // // if this was the last written session we need to reset d->currentWrittenSession // and start a new writing if more copies are wanted // if( d->currentWrittenSession < d->numSessions ) { d->currentWrittenSession++; d->currentReadSession++; // many drives need to reload the medium to return to a proper state if ( m_writerDevice->diskInfo().numSessions() < ( int )d->currentWrittenSession ) { emit infoMessage( i18n( "Need to reload medium to return to proper state." ), INFO ); emit newSubTask( i18n("Reloading the medium") ); connect( K3b::Device::reload( m_writerDevice ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotMediaReloadedForNextSession(K3b::Device::DeviceHandler*)) ); } if( !writeNextSession() ) { // nothing is running here... finishJob( d->canceled, d->error ); } else if( m_onTheFly ) readNextSession(); } else { d->doneCopies++; if( !m_simulate && d->doneCopies < m_copies ) { // start next copy if( !m_writerDevice->eject() ) { blockingInformation( i18n("K3b was unable to eject the written disk. Please do so manually.") ); } d->currentWrittenSession = 1; d->currentReadSession = 1; if( writeNextSession() ) { if( m_onTheFly ) readNextSession(); } else { // nothing running here... finishJob( d->canceled, d->error ); } } else { if ( k3bcore->globalSettings()->ejectMedia() ) { K3b::Device::eject( m_writerDevice ); } finishJob( false, false ); } } } else { // // If we are writing on the fly the reader will also stop when it is not able to write anymore // The error handling will be done only here in that case // // the K3b::CdrecordWriter emitted an error message finishJob( d->canceled, !d->canceled ); } } void K3b::CdCopyJob::slotMediaReloadedForNextSession( K3b::Device::DeviceHandler* dh ) { if( !dh->success() ) blockingInformation( i18n("Please reload the medium and press 'ok'"), i18n("Unable to close the tray") ); if( !writeNextSession() ) { // nothing is running here... finishJob( d->canceled, d->error ); } else if( m_onTheFly ) readNextSession(); } void K3b::CdCopyJob::cleanup() { if( m_onTheFly || !m_keepImage || ((d->canceled || d->error) && !d->readingSuccessful) ) { emit infoMessage( i18n("Removing temporary files."), INFO ); for( QStringList::iterator it = d->infNames.begin(); it != d->infNames.end(); ++it ) QFile::remove( *it ); } if( !m_onTheFly && (!m_keepImage || ((d->canceled || d->error) && !d->readingSuccessful)) ) { emit infoMessage( i18n("Removing image files."), INFO ); for( QStringList::iterator it = d->imageNames.begin(); it != d->imageNames.end(); ++it ) QFile::remove( *it ); // remove the tempdir created in prepareImageFiles() if( d->deleteTempDir ) { KIO::NetAccess::del( m_tempPath, 0 ); d->deleteTempDir = false; } } } void K3b::CdCopyJob::slotReaderProgress( int p ) { if( !m_onTheFly || m_onlyCreateImages ) { int bigParts = ( m_onlyCreateImages ? 1 : (m_simulate ? 2 : m_copies + 1 ) ); double done = (double)p * (double)d->sessionSizes[d->currentReadSession-1] / 100.0; for( int i = 0; i < d->currentReadSession-1; ++i ) done += (double)d->sessionSizes[i]; emit percent( (int)(100.0*done/(double)d->overallSize/(double)bigParts) ); if( d->dataReaderRunning ) emit subPercent(p); } } void K3b::CdCopyJob::slotReaderSubProgress( int p ) { // only if reading an audiosession if( !m_onTheFly || m_onlyCreateImages ) { emit subPercent( p ); } } void K3b::CdCopyJob::slotReaderProcessedSize( int p, int pp ) { if( !m_onTheFly ) emit processedSubSize( p, pp ); } void K3b::CdCopyJob::slotWriterProgress( int p ) { int bigParts = ( m_simulate ? 1 : m_copies ) + ( m_onTheFly ? 0 : 1 ); long done = ( m_onTheFly ? d->doneCopies : d->doneCopies+1 ) * d->overallSize + (p * d->sessionSizes[d->currentWrittenSession-1] / 100); for( int i = 0; i < d->currentWrittenSession-1; ++i ) done += d->sessionSizes[i]; emit percent( 100*done/d->overallSize/bigParts ); } void K3b::CdCopyJob::slotWritingNextTrack( int t, int tt ) { if( d->toc.contentType() == K3b::Device::MIXED ) { if( d->currentWrittenSession == 1 ) emit newSubTask( i18n("Writing track %1 of %2",t,d->toc.count()) ); else emit newSubTask( i18n("Writing track %1 of %2",d->toc.count(),d->toc.count()) ); } else if( d->numSessions > 1 ) emit newSubTask( i18n("Writing track %1 of %2",d->currentWrittenSession,d->toc.count()) ); else emit newSubTask( i18n("Writing track %1 of %2",t,tt) ); } void K3b::CdCopyJob::slotReadingNextTrack( int t, int ) { if( !m_onTheFly || m_onlyCreateImages ) { int track = t; if( d->audioReaderRunning ) track = t; else if( d->toc.contentType() == K3b::Device::MIXED ) track = d->toc.count(); else track = d->currentReadSession; emit newSubTask( i18n("Reading track %1 of %2",track,d->toc.count()) ); } } QString K3b::CdCopyJob::jobDescription() const { if( m_onlyCreateImages ) { return i18n("Creating CD Image"); } else if( m_simulate ) { if( m_onTheFly ) return i18n("Simulating CD Copy On-The-Fly"); else return i18n("Simulating CD Copy"); } else { if( m_onTheFly ) return i18n("Copying CD On-The-Fly"); else return i18n("Copying CD"); } } QString K3b::CdCopyJob::jobDetails() const { return i18np("Creating 1 copy", "Creating %1 copies", (m_simulate||m_onlyCreateImages) ? 1 : m_copies ); } void K3b::CdCopyJob::finishJob( bool c, bool e ) { if( d->running ) { if( c ) { d->canceled = true; emit canceled(); } if( e ) d->error = true; cleanup(); d->running = false; jobFinished( !(c||e) ); } } #include "k3bcdcopyjob.moc" diff --git a/libk3b/jobs/k3bdatatrackreader.cpp b/libk3b/jobs/k3bdatatrackreader.cpp index de01feb77..e97ce1e0e 100644 --- a/libk3b/jobs/k3bdatatrackreader.cpp +++ b/libk3b/jobs/k3bdatatrackreader.cpp @@ -1,482 +1,482 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bdatatrackreader.h" #include #include #include #include #include #include #include #include #include #include // FIXME: determine max DMA buffer size static int s_bufferSizeSectors = 10; class K3b::DataTrackReader::Private { public: Private(); ~Private(); bool ignoreReadErrors; bool noCorrection; int retries; K3b::Device::Device* device; K3b::Msf firstSector; K3b::Msf lastSector; K3b::Msf nextReadSector; - int fd; + QIODevice* ioDevice; QString imagePath; int sectorSize; bool useLibdvdcss; K3b::LibDvdCss* libcss; int oldErrorRecoveryMode; int errorSectorCount; int usedSectorSize; }; K3b::DataTrackReader::Private::Private() : ignoreReadErrors(false), noCorrection(false), retries(10), device(0), - fd(-1), + ioDevice(0), libcss(0) { } K3b::DataTrackReader::Private::~Private() { delete libcss; } K3b::DataTrackReader::DataTrackReader( K3b::JobHandler* jh, QObject* parent ) : K3b::ThreadJob( jh, parent ), d( new Private() ) { } K3b::DataTrackReader::~DataTrackReader() { delete d; } void K3b::DataTrackReader::setDevice( K3b::Device::Device* dev ) { d->device = dev; } void K3b::DataTrackReader::setSectorRange( const K3b::Msf& start, const K3b::Msf& end ) { d->firstSector = start; d->lastSector = end; } void K3b::DataTrackReader::setRetries( int r ) { d->retries = r; } void K3b::DataTrackReader::setIgnoreErrors( bool b ) { d->ignoreReadErrors = b; } void K3b::DataTrackReader::setNoCorrection( bool b ) { d->noCorrection = b; } -void K3b::DataTrackReader::writeToFd( int fd ) +void K3b::DataTrackReader::writeTo( QIODevice* ioDev ) { - d->fd = fd; + d->ioDevice = ioDev; } void K3b::DataTrackReader::setImagePath( const QString& p ) { d->imagePath = p; - d->fd = -1; + d->ioDevice = 0; } void K3b::DataTrackReader::setSectorSize( SectorSize size ) { d->sectorSize = size; } bool K3b::DataTrackReader::run() { if( !d->device->open() ) { emit infoMessage( i18n("Could not open device %1",d->device->blockDeviceName()), K3b::Job::ERROR ); return false; } // 1. determine sector size by checking the first sectors mode // if impossible or MODE2 (mode2 formless) finish(false) d->useLibdvdcss = false; d->usedSectorSize = d->sectorSize; Device::MediaType mediaType = d->device->mediaType(); if( K3b::Device::isDvdMedia( mediaType ) ) { d->usedSectorSize = MODE1; // // In case of an encrypted VideoDVD we read with libdvdcss which takes care of decrypting the vobs // if( d->device->copyrightProtectionSystemType() == K3b::Device::COPYRIGHT_PROTECTION_CSS ) { // close the device for libdvdcss d->device->close(); kDebug() << "(K3b::DataTrackReader::WorkThread) found encrypted dvd. using libdvdcss."; // open the libdvdcss stuff if( !d->libcss ) d->libcss = K3b::LibDvdCss::create(); if( !d->libcss ) { emit infoMessage( i18n("Unable to open libdvdcss."), K3b::Job::ERROR ); return false; } if( !d->libcss->open(d->device) ) { emit infoMessage( i18n("Could not open device %1",d->device->blockDeviceName()), K3b::Job::ERROR ); return false; } emit infoMessage( i18n("Retrieving all CSS keys. This might take a while."), K3b::Job::INFO ); if( !d->libcss->crackAllKeys() ) { d->libcss->close(); emit infoMessage( i18n("Failed to retrieve all CSS keys."), K3b::Job::ERROR ); emit infoMessage( i18n("Video DVD decryption failed."), K3b::Job::ERROR ); return false; } d->useLibdvdcss = true; } } else if ( K3b::Device::isBdMedia( mediaType ) ) { d->usedSectorSize = MODE1; } else { if( d->usedSectorSize == AUTO ) { switch( d->device->getDataMode( d->firstSector ) ) { case K3b::Device::Track::MODE1: case K3b::Device::Track::DVD: d->usedSectorSize = MODE1; break; case K3b::Device::Track::XA_FORM1: d->usedSectorSize = MODE2FORM1; break; case K3b::Device::Track::XA_FORM2: d->usedSectorSize = MODE2FORM2; break; case K3b::Device::Track::MODE2: emit infoMessage( i18n("No support for reading formless Mode2 sectors."), K3b::Job::ERROR ); default: emit infoMessage( i18n("Unsupported sector type."), K3b::Job::ERROR ); d->device->close(); return false; } } } emit infoMessage( i18n("Reading with sector size %1.",d->usedSectorSize), K3b::Job::INFO ); emit debuggingOutput( "K3b::DataTrackReader", QString("reading sectors %1 to %2 with sector size %3. Length: %4 sectors, %5 bytes.") .arg( d->firstSector.lba() ) .arg( d->lastSector.lba() ) .arg( d->usedSectorSize ) .arg( d->lastSector.lba() - d->firstSector.lba() + 1 ) .arg( quint64(d->usedSectorSize) * (quint64)(d->lastSector.lba() - d->firstSector.lba() + 1) ) ); QFile file; - if( d->fd == -1 ) { + if( !d->ioDevice ) { file.setFileName( d->imagePath ); if( !file.open( QIODevice::WriteOnly ) ) { d->device->close(); if( d->useLibdvdcss ) d->libcss->close(); emit infoMessage( i18n("Unable to open '%1' for writing.",d->imagePath), K3b::Job::ERROR ); return false; } } k3bcore->blockDevice( d->device ); d->device->block( true ); // // set the error recovery mode to 0x21 or 0x20 depending on d->ignoreReadErrors // TODO: should we also set RC=1 in d->ignoreReadErrors mode (0x11 because TB is ignored) // setErrorRecovery( d->device, d->noCorrection ? 0x21 : 0x20 ); // // Let the drive determine the optimal reading speed // d->device->setSpeed( 0xffff, 0xffff ); s_bufferSizeSectors = 128; unsigned char* buffer = new unsigned char[d->usedSectorSize*s_bufferSizeSectors]; while( s_bufferSizeSectors > 0 && read( buffer, d->firstSector.lba(), s_bufferSizeSectors ) < 0 ) { kDebug() << "(K3b::DataTrackReader) determine max read sectors: " << s_bufferSizeSectors << " too high." << endl; s_bufferSizeSectors--; } kDebug() << "(K3b::DataTrackReader) determine max read sectors: " << s_bufferSizeSectors << " is max." << endl; // s_bufferSizeSectors = K3b::Device::determineMaxReadingBufferSize( d->device, d->firstSector ); if( s_bufferSizeSectors <= 0 ) { emit infoMessage( i18n("Error while reading sector %1.",d->firstSector.lba()), K3b::Job::ERROR ); d->device->block( false ); k3bcore->unblockDevice( d->device ); return false; } kDebug() << "(K3b::DataTrackReader) using buffer size of " << s_bufferSizeSectors << " blocks."; emit debuggingOutput( "K3b::DataTrackReader", QString("using buffer size of %1 blocks.").arg( s_bufferSizeSectors ) ); // 2. get it on K3b::Msf currentSector = d->firstSector; K3b::Msf totalReadSectors; d->nextReadSector = 0; d->errorSectorCount = 0; bool writeError = false; bool readError = false; int lastPercent = 0; unsigned long lastReadMb = 0; int bufferLen = s_bufferSizeSectors*d->usedSectorSize; while( !canceled() && currentSector <= d->lastSector ) { int maxReadSectors = qMin( bufferLen/d->usedSectorSize, d->lastSector.lba()-currentSector.lba()+1 ); int readSectors = read( buffer, currentSector.lba(), maxReadSectors ); if( readSectors < 0 ) { if( !retryRead( buffer, currentSector.lba(), maxReadSectors ) ) { readError = true; break; } else readSectors = maxReadSectors; } totalReadSectors += readSectors; int readBytes = readSectors * d->usedSectorSize; - if( d->fd != -1 ) { - if( ::write( d->fd, reinterpret_cast(buffer), readBytes ) != readBytes ) { - kDebug() << "(K3b::DataTrackReader::WorkThread) error while writing to fd " << d->fd + if( d->ioDevice ) { + if( d->ioDevice->write( reinterpret_cast(buffer ), readBytes ) != readBytes ) { + kDebug() << "(K3b::DataTrackReader::WorkThread) error while writing to dev " << d->ioDevice << " current sector: " << (currentSector.lba()-d->firstSector.lba()) << endl; emit debuggingOutput( "K3b::DataTrackReader", - QString("Error while writing to fd %1. Current sector is %2.") - .arg(d->fd).arg(currentSector.lba()-d->firstSector.lba()) ); + QString("Error while writing to IO device. Current sector is %2.") + .arg(currentSector.lba()-d->firstSector.lba()) ); writeError = true; break; } } else { if( file.write( reinterpret_cast(buffer), readBytes ) != readBytes ) { kDebug() << "(K3b::DataTrackReader::WorkThread) error while writing to file " << d->imagePath << " current sector: " << (currentSector.lba()-d->firstSector.lba()) << endl; emit debuggingOutput( "K3b::DataTrackReader", QString("Error while writing to file %1. Current sector is %2.") .arg(d->imagePath).arg(currentSector.lba()-d->firstSector.lba()) ); writeError = true; break; } } currentSector += readSectors; int currentPercent = 100 * (currentSector.lba() - d->firstSector.lba() + 1 ) / (d->lastSector.lba() - d->firstSector.lba() + 1 ); if( currentPercent > lastPercent ) { lastPercent = currentPercent; emit percent( currentPercent ); } unsigned long readMb = (currentSector.lba() - d->firstSector.lba() + 1) / 512; if( readMb > lastReadMb ) { lastReadMb = readMb; emit processedSize( readMb, ( d->lastSector.lba() - d->firstSector.lba() + 1 ) / 512 ); } } if( d->errorSectorCount > 0 ) emit infoMessage( i18np("Ignored %1 erroneous sector.", "Ignored a total of %1 erroneous sectors.", d->errorSectorCount ), K3b::Job::ERROR ); // reset the error recovery mode setErrorRecovery( d->device, d->oldErrorRecoveryMode ); d->device->block( false ); k3bcore->unblockDevice( d->device ); // cleanup if( d->useLibdvdcss ) d->libcss->close(); d->device->close(); delete [] buffer; emit debuggingOutput( "K3b::DataTrackReader", QString("Read a total of %1 sectors (%2 bytes)") .arg(totalReadSectors.lba()) .arg((quint64)totalReadSectors.lba()*(quint64)d->usedSectorSize) ); return( !canceled() && !writeError && !readError ); } int K3b::DataTrackReader::read( unsigned char* buffer, unsigned long sector, unsigned int len ) { // // Encrypted DVD reading with libdvdcss // if( d->useLibdvdcss ) { return d->libcss->readWrapped( reinterpret_cast(buffer), sector, len ); } // // Standard reading // else { bool success = false; // setErrorRecovery( d->device, d->ignoreReadErrors ? 0x21 : 0x20 ); if( d->usedSectorSize == 2048 ) success = d->device->read10( buffer, len*2048, sector, len ); else success = d->device->readCd( buffer, len*d->usedSectorSize, 0, // all sector types false, // no dap sector, len, false, // no sync false, // no header d->usedSectorSize != MODE1, // subheader true, // user data false, // no edc/ecc 0, // no c2 error info... FIXME: should we check this?? 0 // no subchannel data ); if( success ) return len; else return -1; } } // here we read every single sector for itself to find the troubleing ones bool K3b::DataTrackReader::retryRead( unsigned char* buffer, unsigned long startSector, unsigned int len ) { emit debuggingOutput( "K3b::DataTrackReader", QString( "Problem while reading. Retrying from sector %1.").arg(startSector) ); emit infoMessage( i18n("Problem while reading. Retrying from sector %1.",startSector), K3b::Job::WARNING ); int sectorsRead = -1; bool success = true; for( unsigned long sector = startSector; sector < startSector+len; ++sector ) { int retry = d->retries; while( !canceled() && retry && (sectorsRead = read( &buffer[( sector - startSector ) * d->usedSectorSize], sector, 1 )) < 0 ) --retry; success = ( sectorsRead > 0 ); if( canceled() ) return false; if( !success ) { if( d->ignoreReadErrors ) { emit infoMessage( i18n("Ignoring read error in sector %1.",sector), K3b::Job::ERROR ); emit debuggingOutput( "K3b::DataTrackReader", QString( "Ignoring read error in sector %1.").arg(sector) ); ++d->errorSectorCount; // ::memset( &buffer[i], 0, 1 ); success = true; } else { emit infoMessage( i18n("Error while reading sector %1.",sector), K3b::Job::ERROR ); emit debuggingOutput( "K3b::DataTrackReader", QString( "Read error in sector %1.").arg(sector) ); break; } } } return success; } bool K3b::DataTrackReader::setErrorRecovery( K3b::Device::Device* dev, int code ) { unsigned char* data = 0; unsigned int dataLen = 0; if( !dev->modeSense( &data, dataLen, 0x01 ) ) return false; // in MMC1 the page has 8 bytes (12 in MMC4 but we only need the first 3 anyway) if( dataLen < 8+8 ) { kDebug() << "(K3b::DataTrackReader) modepage 0x01 data too small: " << dataLen; delete [] data; return false; } d->oldErrorRecoveryMode = data[8+2]; data[8+2] = code; if( d->oldErrorRecoveryMode != code ) kDebug() << "(K3b::DataTrackReader) changing data recovery mode from " << d->oldErrorRecoveryMode << " to " << code; bool success = dev->modeSelect( data, dataLen, true, false ); delete [] data; return success; } diff --git a/libk3b/jobs/k3bdatatrackreader.h b/libk3b/jobs/k3bdatatrackreader.h index c409466ed..1481cd085 100644 --- a/libk3b/jobs/k3bdatatrackreader.h +++ b/libk3b/jobs/k3bdatatrackreader.h @@ -1,96 +1,94 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_DATATRACK_READER_H_ #define _K3B_DATATRACK_READER_H_ -#include -#include -#include +#include "k3bthreadjob.h" + +#include "k3bmsf.h" +#include "k3bglobals.h" + +class QIODevice; namespace K3b { namespace Device { class Device; } /** * This is a replacement for readcd. We need this since * it is not possible to influence the sector size used * by readcd and readcd is not very good to handle anyway. * * The sector size read is the following: * @li Mode1: 2048 bytes (only user data) * @li Mode2 Form1: 2056 bytes containing the subheader and the user data * @li Mode2 Form2: 2332 bytes containing the subheader and the user data * * Formless Mode2 sectors will not be read. */ class DataTrackReader : public ThreadJob { Q_OBJECT public: DataTrackReader( JobHandler*, QObject* parent = 0 ); ~DataTrackReader(); enum SectorSize { AUTO = 0, MODE1 = SECTORSIZE_DATA_2048, MODE2FORM1 = SECTORSIZE_DATA_2048_SUBHEADER, MODE2FORM2 = SECTORSIZE_DATA_2324_SUBHEADER }; void setSectorSize( SectorSize size ); void setDevice( Device::Device* ); + void setImagePath( const QString& p ); + /** * @param start the first sector to be read * @end the last sector to be read */ void setSectorRange( const Msf& start, const Msf& end ); void setRetries( int ); /** * If true unreadable sectors will be replaced by zero data to always * maintain the track length. */ void setIgnoreErrors( bool b ); void setNoCorrection( bool b ); - /** - * the data gets written directly into fd instead of the imagefile. - * Be aware that this only makes sense before starting the job. - * To disable just set fd to -1 - */ - void writeToFd( int fd ); - - void setImagePath( const QString& p ); + void writeTo( QIODevice* ioDev ); private: bool run(); int read( unsigned char* buffer, unsigned long sector, unsigned int len ); bool retryRead( unsigned char* buffer, unsigned long startSector, unsigned int len ); bool setErrorRecovery( Device::Device* dev, int code ); class Private; Private* const d; }; } #endif diff --git a/libk3b/jobs/k3bdvdbooktypejob.cpp b/libk3b/jobs/k3bdvdbooktypejob.cpp index 74136edaa..c36af7159 100644 --- a/libk3b/jobs/k3bdvdbooktypejob.cpp +++ b/libk3b/jobs/k3bdvdbooktypejob.cpp @@ -1,346 +1,344 @@ /* * - * Copyright (C) 2003-2008 Sebastian Trueg + * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bdvdbooktypejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K3b::DvdBooktypeJob::Private { public: Private() : device(0), process(0), dvdBooktypeBin(0), running(false), forceNoEject(false) { } K3b::Device::Device* device; K3b::Process* process; const K3b::ExternalBin* dvdBooktypeBin; bool success; bool canceled; bool running; bool forceNoEject; int foundMediaType; }; K3b::DvdBooktypeJob::DvdBooktypeJob( K3b::JobHandler* jh, QObject* parent ) : K3b::Job( jh, parent ), m_action(0) { d = new Private; } K3b::DvdBooktypeJob::~DvdBooktypeJob() { delete d->process; delete d; } void K3b::DvdBooktypeJob::setForceNoEject( bool b ) { d->forceNoEject = b; } QString K3b::DvdBooktypeJob::jobDescription() const { return i18n("Changing DVD Booktype"); // Changing DVD±R(W) Booktype } QString K3b::DvdBooktypeJob::jobDetails() const { return QString(); } void K3b::DvdBooktypeJob::start() { d->canceled = false; d->running = true; jobStarted(); if( !d->device ) { emit infoMessage( i18n("No device set"), ERROR ); jobFinished(false); d->running = false; return; } // // In case we want to change the writers default we do not need to wait for a media // if( m_action == SET_MEDIA_DVD_ROM || m_action == SET_MEDIA_DVD_R_W ) { emit newSubTask( i18n("Waiting for media") ); if( waitForMedia( d->device, K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE|K3b::Device::STATE_EMPTY, K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_PLUS_R, i18n("Please insert an empty DVD+R or a DVD+RW medium into drive

%1 %2 (%3).", d->device->vendor(), d->device->description(), d->device->blockDeviceName()) ) == -1 ) { emit canceled(); jobFinished(false); d->running = false; return; } emit infoMessage( i18n("Checking media..."), INFO ); emit newTask( i18n("Checking media") ); connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::NG_DISKINFO, d->device ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotDeviceHandlerFinished(K3b::Device::DeviceHandler*)) ); } else { // change writer defaults startBooktypeChange(); } } void K3b::DvdBooktypeJob::start( K3b::Device::DeviceHandler* dh ) { d->canceled = false; d->running = true; jobStarted(); slotDeviceHandlerFinished( dh ); } void K3b::DvdBooktypeJob::cancel() { if( d->running ) { d->canceled = true; if( d->process ) - d->process->kill(); + d->process->terminate(); } else { kDebug() << "(K3b::DvdBooktypeJob) not running."; } } void K3b::DvdBooktypeJob::setDevice( K3b::Device::Device* dev ) { d->device = dev; } void K3b::DvdBooktypeJob::slotStderrLine( const QString& line ) { emit debuggingOutput( "dvd+rw-booktype", line ); // FIXME } void K3b::DvdBooktypeJob::slotProcessFinished( int exitCode, QProcess::ExitStatus exitStatus ) { if( d->canceled ) { emit canceled(); d->success = false; } else if( exitStatus == QProcess::NormalExit ) { if( exitCode == 0 ) { emit infoMessage( i18n("Booktype successfully changed"), K3b::Job::SUCCESS ); d->success = true; } else { emit infoMessage( i18n("%1 returned an unknown error (code %2).",d->dvdBooktypeBin->name(), exitCode), K3b::Job::ERROR ); emit infoMessage( i18n("Please send me an email with the last output."), K3b::Job::ERROR ); d->success = false; } } else { emit infoMessage( i18n("%1 did not exit cleanly.",d->dvdBooktypeBin->name()), ERROR ); d->success = false; } // // No need to eject the media if we changed the writer's default // if( m_action == SET_MEDIA_DVD_ROM || m_action == SET_MEDIA_DVD_R_W ) { if( d->forceNoEject || !k3bcore->globalSettings()->ejectMedia() ) { d->running = false; jobFinished(d->success); } else { emit infoMessage( i18n("Ejecting DVD..."), INFO ); connect( K3b::Device::eject( d->device ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotEjectingFinished(K3b::Device::DeviceHandler*)) ); } } else { d->running = false; jobFinished(d->success); } } void K3b::DvdBooktypeJob::slotEjectingFinished( K3b::Device::DeviceHandler* dh ) { if( !dh->success() ) emit infoMessage( i18n("Unable to eject media."), ERROR ); d->running = false; jobFinished(d->success); } void K3b::DvdBooktypeJob::slotDeviceHandlerFinished( K3b::Device::DeviceHandler* dh ) { if( d->canceled ) { emit canceled(); d->running = false; jobFinished(false); } if( dh->success() ) { d->foundMediaType = dh->diskInfo().mediaType(); if( d->foundMediaType == K3b::Device::MEDIA_DVD_PLUS_R ) { // the media needs to be empty if( dh->diskInfo().empty() ) startBooktypeChange(); else { emit infoMessage( i18n("Cannot change booktype on non-empty DVD+R media."), ERROR ); jobFinished(false); } } else if( d->foundMediaType == K3b::Device::MEDIA_DVD_PLUS_RW ) { startBooktypeChange(); } else { emit infoMessage( i18n("No DVD+R(W) media found."), ERROR ); jobFinished(false); } } else { emit infoMessage( i18n("Unable to determine media state."), ERROR ); d->running = false; jobFinished(false); } } void K3b::DvdBooktypeJob::startBooktypeChange() { delete d->process; d->process = new K3b::Process(); - d->process->setRunPrivileged(true); d->process->setSuppressEmptyLines(true); connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStderrLine(const QString&)) ); connect( d->process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessFinished(int, QProcess::ExitStatus)) ); d->dvdBooktypeBin = k3bcore->externalBinManager()->binObject( "dvd+rw-booktype" ); if( !d->dvdBooktypeBin ) { emit infoMessage( i18n("Could not find %1 executable.",QString("dvd+rw-booktype")), ERROR ); d->running = false; jobFinished(false); return; } *d->process << d->dvdBooktypeBin; switch( m_action ) { case SET_MEDIA_DVD_ROM: *d->process << "-dvd-rom-spec" << "-media"; break; case SET_MEDIA_DVD_R_W: if( d->foundMediaType == K3b::Device::MEDIA_DVD_PLUS_RW ) *d->process << "-dvd+rw-spec"; else *d->process << "-dvd+r-spec"; *d->process << "-media"; break; case SET_UNIT_DVD_ROM_ON_NEW_DVD_R: *d->process << "-dvd-rom-spec" << "-unit+r"; break; case SET_UNIT_DVD_ROM_ON_NEW_DVD_RW: *d->process << "-dvd-rom-spec" << "-unit+rw"; break; case SET_UNIT_DVD_R_ON_NEW_DVD_R: *d->process << "-dvd+r-spec" << "-unit+r"; break; case SET_UNIT_DVD_RW_ON_NEW_DVD_RW: *d->process << "-dvd+rw-spec" << "-unit+rw"; break; } *d->process << d->device->blockDeviceName(); kDebug() << "***** dvd+rw-booktype parameters:\n"; QString s = d->process->joinedArgs(); kDebug() << s << endl << flush; emit debuggingOutput( "dvd+rw-booktype command:", s ); - - if( !d->process->start( K3Process::All ) ) { + if( !d->process->start( KProcess::OnlyStderrChannel ) ) { // something went wrong when starting the program // it "should" be the executable emit infoMessage( i18n("Could not start %1.",d->dvdBooktypeBin->name()), K3b::Job::ERROR ); d->running = false; jobFinished(false); } else { emit newTask( i18n("Changing Booktype") ); } } #include "k3bdvdbooktypejob.moc" diff --git a/libk3b/jobs/k3bdvdcopyjob.cpp b/libk3b/jobs/k3bdvdcopyjob.cpp index d71d2e258..4b00e84f3 100644 --- a/libk3b/jobs/k3bdvdcopyjob.cpp +++ b/libk3b/jobs/k3bdvdcopyjob.cpp @@ -1,953 +1,953 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bdvdcopyjob.h" #include "k3blibdvdcss.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K3b::DvdCopyJob::Private { public: Private() : doneCopies(0), running(false), canceled(false), writerJob(0), readcdReader(0), dataTrackReader(0), verificationJob(0), usedWritingMode(K3b::WRITING_MODE_AUTO), verifyData(false) { - outPipe.readFromIODevice( &imageFile ); + outPipe.readFrom( &imageFile ); } int doneCopies; bool running; bool readerRunning; bool writerRunning; bool canceled; K3b::AbstractWriter* writerJob; K3b::ReadcdReader* readcdReader; K3b::DataTrackReader* dataTrackReader; K3b::VerificationJob* verificationJob; K3b::Device::DiskInfo sourceDiskInfo; K3b::Msf lastSector; K3b::WritingMode usedWritingMode; K3b::FileSplitter imageFile; K3b::ChecksumPipe inPipe; K3b::ActivePipe outPipe; bool verifyData; }; K3b::DvdCopyJob::DvdCopyJob( K3b::JobHandler* hdl, QObject* parent ) : K3b::BurnJob( hdl, parent ), m_writerDevice(0), m_readerDevice(0), m_onTheFly(false), m_removeImageFiles(false), m_simulate(false), m_speed(1), m_copies(1), m_onlyCreateImage(false), m_ignoreReadErrors(false), m_readRetries(128), m_writingMode( K3b::WRITING_MODE_AUTO ) { d = new Private(); } K3b::DvdCopyJob::~DvdCopyJob() { delete d; } void K3b::DvdCopyJob::start() { jobStarted(); emit burning(false); d->canceled = false; d->running = true; d->readerRunning = d->writerRunning = false; emit newTask( i18n("Checking Source Medium") ); if( m_onTheFly && k3bcore->externalBinManager()->binObject( "growisofs" )->version < K3b::Version( 5, 12 ) ) { m_onTheFly = false; emit infoMessage( i18n("K3b does not support writing on-the-fly with growisofs %1.", k3bcore->externalBinManager()->binObject( "growisofs" )->version), ERROR ); emit infoMessage( i18n("Disabling on-the-fly writing."), INFO ); } emit newSubTask( i18n("Waiting for source medium") ); // wait for a source disk if( waitForMedia( m_readerDevice, K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE, K3b::Device::MEDIA_WRITABLE_DVD|K3b::Device::MEDIA_DVD_ROM|K3b::Device::MEDIA_BD_ALL ) < 0 ) { emit canceled(); d->running = false; jobFinished( false ); return; } emit newSubTask( i18n("Checking source medium") ); connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::DISKINFO, m_readerDevice ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) ); } void K3b::DvdCopyJob::slotDiskInfoReady( K3b::Device::DeviceHandler* dh ) { if( d->canceled ) { emit canceled(); jobFinished(false); d->running = false; } d->sourceDiskInfo = dh->diskInfo(); if( dh->diskInfo().empty() || dh->diskInfo().diskState() == K3b::Device::STATE_NO_MEDIA ) { emit infoMessage( i18n("No source medium found."), ERROR ); jobFinished(false); d->running = false; } else { if( m_readerDevice->copyrightProtectionSystemType() == K3b::Device::COPYRIGHT_PROTECTION_CSS ) { // CSS is the the only one we support ATM emit infoMessage( i18n("Found encrypted DVD."), WARNING ); // check for libdvdcss bool haveLibdvdcss = false; kDebug() << "(K3b::DvdCopyJob) trying to open libdvdcss."; if( K3b::LibDvdCss* libcss = K3b::LibDvdCss::create() ) { kDebug() << "(K3b::DvdCopyJob) succeeded."; kDebug() << "(K3b::DvdCopyJob) dvdcss_open(" << m_readerDevice->blockDeviceName() << ") = " << libcss->open(m_readerDevice) << endl; haveLibdvdcss = true; delete libcss; } else kDebug() << "(K3b::DvdCopyJob) failed."; if( !haveLibdvdcss ) { emit infoMessage( i18n("Cannot copy encrypted DVDs."), ERROR ); d->running = false; jobFinished( false ); return; } } // // We cannot rely on the kernel to determine the size of the DVD for some reason // On the other hand it is not always a good idea to rely on the size from the ISO9660 // header since that may be wrong due to some buggy encoder or some boot code appended // after creating the image. // That is why we try our best to determine the size of the DVD. For DVD-ROM this is very // easy since it has only one track. The same goes for single session DVD-R(W) and DVD+R. // Multisession DVDs we will simply not copy. ;) // For DVD+RW and DVD-RW in restricted overwrite mode we are left with no other choice but // to use the ISO9660 header. // // On the other hand: in on-the-fly mode growisofs determines the size of the data to be written // by looking at the ISO9660 header when writing in DAO mode. So in this case // it would be best for us to do the same.... // // With growisofs 5.15 we have the option to specify the size of the image to be written in DAO mode. // switch( dh->diskInfo().mediaType() ) { case K3b::Device::MEDIA_DVD_ROM: case K3b::Device::MEDIA_DVD_PLUS_R_DL: case K3b::Device::MEDIA_DVD_R_DL: case K3b::Device::MEDIA_DVD_R_DL_SEQ: case K3b::Device::MEDIA_DVD_R_DL_JUMP: if( !m_onlyCreateImage ) { if( dh->diskInfo().numLayers() > 1 && dh->diskInfo().size().mode1Bytes() > 4700372992LL ) { if( !(m_writerDevice->type() & (K3b::Device::DEVICE_DVD_R_DL|K3b::Device::DEVICE_DVD_PLUS_R_DL)) ) { emit infoMessage( i18n("The writer does not support writing Double Layer DVD."), ERROR ); d->running = false; jobFinished(false); return; } // FIXME: check for growisofs 5.22 (or whatever version is needed) for DVD-R DL else if( k3bcore->externalBinManager()->binObject( "growisofs" ) && k3bcore->externalBinManager()->binObject( "growisofs" )->hasFeature( "dual-layer" ) ) { emit infoMessage( i18n("Growisofs >= 5.20 is needed to write Double Layer DVD+R."), ERROR ); d->running = false; jobFinished(false); return; } } } case K3b::Device::MEDIA_DVD_R: case K3b::Device::MEDIA_DVD_R_SEQ: case K3b::Device::MEDIA_DVD_RW: case K3b::Device::MEDIA_DVD_RW_SEQ: case K3b::Device::MEDIA_DVD_PLUS_R: case K3b::Device::MEDIA_BD_ROM: case K3b::Device::MEDIA_BD_R: case K3b::Device::MEDIA_BD_R_SRM: if( dh->diskInfo().numSessions() > 1 ) { emit infoMessage( i18n("K3b does not support copying multi-session DVD or Blu-ray disks."), ERROR ); d->running = false; jobFinished(false); return; } // growisofs only uses the size from the PVD for reserving // writable space in DAO mode // with version >= 5.15 growisofs supports specifying the size of the track if( m_writingMode != K3b::WRITING_MODE_DAO || !m_onTheFly || m_onlyCreateImage || ( k3bcore->externalBinManager()->binObject( "growisofs" ) && k3bcore->externalBinManager()->binObject( "growisofs" )->hasFeature( "daosize" ) ) ) { d->lastSector = dh->toc().lastSector(); break; } // fallthrough case K3b::Device::MEDIA_DVD_PLUS_RW: case K3b::Device::MEDIA_DVD_RW_OVWR: case K3b::Device::MEDIA_BD_RE: { emit infoMessage( i18n("K3b relies on the size saved in the ISO9660 header."), WARNING ); emit infoMessage( i18n("This might result in a corrupt copy if the source was mastered with buggy software."), WARNING ); K3b::Iso9660 isoF( m_readerDevice, 0 ); if( isoF.open() ) { d->lastSector = ((long long)isoF.primaryDescriptor().logicalBlockSize*isoF.primaryDescriptor().volumeSpaceSize)/2048LL - 1; } else { emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR ); jobFinished(false); d->running = false; return; } } break; case K3b::Device::MEDIA_DVD_RAM: emit infoMessage( i18n("K3b does not support copying DVD-RAM."), ERROR ); jobFinished(false); d->running = false; return; default: emit infoMessage( i18n("Unsupported media type."), ERROR ); jobFinished(false); d->running = false; return; } if( !m_onTheFly ) { // // Check the image path // QFileInfo fi( m_imagePath ); if( !fi.isFile() || questionYesNo( i18n("Do you want to overwrite %1?",m_imagePath), i18n("File Exists") ) ) { if( fi.isDir() ) m_imagePath = K3b::findTempFile( "iso", m_imagePath ); else if( !QFileInfo( m_imagePath.section( '/', 0, -2 ) ).isDir() ) { emit infoMessage( i18n("Specified an unusable temporary path. Using default."), WARNING ); m_imagePath = K3b::findTempFile( "iso" ); } // else the user specified a file in an existing dir emit infoMessage( i18n("Writing image file to %1.",m_imagePath), INFO ); emit newSubTask( i18n("Reading source medium.") ); } // // check free temp space // KIO::filesize_t imageSpaceNeeded = (KIO::filesize_t)(d->lastSector.lba()+1)*2048; unsigned long avail, size; QString pathToTest = m_imagePath.left( m_imagePath.lastIndexOf( '/' ) ); if( !K3b::kbFreeOnFs( pathToTest, size, avail ) ) { emit infoMessage( i18n("Unable to determine free space in temporary directory '%1'.",pathToTest), ERROR ); jobFinished(false); d->running = false; return; } else { if( avail < imageSpaceNeeded/1024 ) { emit infoMessage( i18n("Not enough space left in temporary directory."), ERROR ); jobFinished(false); d->running = false; return; } } d->imageFile.setName( m_imagePath ); if( !d->imageFile.open( QIODevice::WriteOnly ) ) { emit infoMessage( i18n("Unable to open '%1' for writing.",m_imagePath), ERROR ); jobFinished( false ); d->running = false; return; } } if( K3b::isMounted( m_readerDevice ) ) { emit infoMessage( i18n("Unmounting source medium"), INFO ); K3b::unmount( m_readerDevice ); } if( m_onlyCreateImage || !m_onTheFly ) { emit newTask( i18n("Creating image") ); } else if( m_onTheFly && !m_onlyCreateImage ) { if( waitForDvd() ) { prepareWriter(); if( m_simulate ) emit newTask( i18n("Simulating copy") ); else if( m_copies > 1 ) emit newTask( i18n("Writing copy %1",d->doneCopies+1) ); else emit newTask( i18n("Writing copy") ); emit burning(true); d->writerRunning = true; d->writerJob->start(); } else { if( d->canceled ) emit canceled(); jobFinished(false); d->running = false; return; } } prepareReader(); d->readerRunning = true; d->dataTrackReader->start(); } } void K3b::DvdCopyJob::cancel() { if( d->running ) { d->canceled = true; if( d->readerRunning ) d->dataTrackReader->cancel(); if( d->writerRunning ) d->writerJob->cancel(); d->inPipe.close(); d->outPipe.close(); d->imageFile.close(); } else { kDebug() << "(K3b::DvdCopyJob) not running."; } } void K3b::DvdCopyJob::prepareReader() { if( !d->dataTrackReader ) { d->dataTrackReader = new K3b::DataTrackReader( this ); connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) ); connect( d->dataTrackReader, SIGNAL(processedSize(int, int)), this, SLOT(slotReaderProcessedSize(int, int)) ); connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotReaderFinished(bool)) ); connect( d->dataTrackReader, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( d->dataTrackReader, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( d->dataTrackReader, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } d->dataTrackReader->setDevice( m_readerDevice ); d->dataTrackReader->setIgnoreErrors( m_ignoreReadErrors ); d->dataTrackReader->setRetries( m_readRetries ); d->dataTrackReader->setSectorRange( 0, d->lastSector ); if( m_onTheFly && !m_onlyCreateImage ) - d->inPipe.writeToFd( d->writerJob->fd(), true ); + d->inPipe.writeTo( d->writerJob->ioDevice(), true ); else - d->inPipe.writeToIODevice( &d->imageFile ); + d->inPipe.writeTo( &d->imageFile ); d->inPipe.open( true ); - d->dataTrackReader->writeToFd( d->inPipe.in() ); + d->dataTrackReader->writeTo( &d->inPipe ); } // ALWAYS CALL WAITFORDVD BEFORE PREPAREWRITER! void K3b::DvdCopyJob::prepareWriter() { delete d->writerJob; // first let's determine which application to use int usedApp = writingApp(); if ( usedApp == K3b::WRITING_APP_DEFAULT ) { // let's default to cdrecord for the time being // FIXME: use growisofs for non-dao and non-auto mode if ( K3b::Device::isBdMedia( d->sourceDiskInfo.mediaType() ) ) { if ( k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "blu-ray" ) ) usedApp = K3b::WRITING_APP_CDRECORD; else usedApp = K3b::WRITING_APP_GROWISOFS; } } if ( usedApp == K3b::WRITING_APP_GROWISOFS ) { K3b::GrowisofsWriter* job = new K3b::GrowisofsWriter( m_writerDevice, this, this ); // these do only make sense with DVD-R(W) job->setSimulate( m_simulate ); job->setBurnSpeed( m_speed ); job->setWritingMode( d->usedWritingMode ); job->setCloseDvd( true ); // // In case the first layer size is not known let the // split be determined by growisofs // if( d->sourceDiskInfo.numLayers() > 1 && d->sourceDiskInfo.firstLayerSize() > 0 ) { job->setLayerBreak( d->sourceDiskInfo.firstLayerSize().lba() ); } else { // this is only used in DAO mode with growisofs >= 5.15 job->setTrackSize( d->lastSector.lba()+1 ); } job->setImageToWrite( QString() ); // write to stdin d->writerJob = job; } else { K3b::CdrecordWriter* writer = new K3b::CdrecordWriter( m_writerDevice, this, this ); writer->setWritingMode( d->usedWritingMode ); writer->setSimulate( m_simulate ); writer->setBurnSpeed( m_speed ); writer->addArgument( "-data" ); writer->addArgument( QString("-tsize=%1s").arg( d->lastSector.lba()+1 ) )->addArgument("-"); d->writerJob = writer; } connect( d->writerJob, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( d->writerJob, SIGNAL(percent(int)), this, SLOT(slotWriterProgress(int)) ); connect( d->writerJob, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); connect( d->writerJob, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); connect( d->writerJob, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); connect( d->writerJob, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); connect( d->writerJob, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)), this, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)) ); connect( d->writerJob, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) ); // connect( d->writerJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); connect( d->writerJob, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( d->writerJob, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } void K3b::DvdCopyJob::slotReaderProgress( int p ) { if( !m_onTheFly || m_onlyCreateImage ) { emit subPercent( p ); int bigParts = ( m_onlyCreateImage ? 1 : (m_simulate ? 2 : ( d->verifyData ? m_copies*2 : m_copies ) + 1 ) ); emit percent( p/bigParts ); } } void K3b::DvdCopyJob::slotReaderProcessedSize( int p, int c ) { if( !m_onTheFly || m_onlyCreateImage ) emit processedSubSize( p, c ); if( m_onlyCreateImage ) emit processedSize( p, c ); } void K3b::DvdCopyJob::slotWriterProgress( int p ) { int bigParts = ( m_simulate ? 1 : ( d->verifyData ? m_copies*2 : m_copies ) ) + ( m_onTheFly ? 0 : 1 ); int doneParts = ( m_simulate ? 0 : ( d->verifyData ? d->doneCopies*2 : d->doneCopies ) ) + ( m_onTheFly ? 0 : 1 ); emit percent( 100*doneParts/bigParts + p/bigParts ); emit subPercent( p ); } void K3b::DvdCopyJob::slotVerificationProgress( int p ) { int bigParts = ( m_simulate ? 1 : ( d->verifyData ? m_copies*2 : m_copies ) ) + ( m_onTheFly ? 0 : 1 ); int doneParts = ( m_simulate ? 0 : ( d->verifyData ? d->doneCopies*2 : d->doneCopies ) ) + ( m_onTheFly ? 0 : 1 ) + 1; emit percent( 100*doneParts/bigParts + p/bigParts ); } void K3b::DvdCopyJob::slotReaderFinished( bool success ) { d->readerRunning = false; d->inPipe.close(); // close the socket // otherwise growisofs will never quit. // FIXME: is it posiible to do this in a generic manner? if( d->writerJob ) d->writerJob->closeFd(); // already finished? if( !d->running ) return; if( d->canceled ) { removeImageFiles(); emit canceled(); jobFinished(false); d->running = false; } if( success ) { emit infoMessage( i18n("Successfully read source medium."), SUCCESS ); if( m_onlyCreateImage ) { jobFinished(true); d->running = false; } else { if( m_writerDevice == m_readerDevice ) { // eject the media (we do this blocking to know if it worked // because if it did not it might happen that k3b overwrites a CD-RW // source) if( !m_readerDevice->eject() ) { blockingInformation( i18n("K3b was unable to eject the source medium. Please do so manually.") ); } } if( !m_onTheFly ) { if( waitForDvd() ) { prepareWriter(); if( m_copies > 1 ) emit newTask( i18n("Writing copy %1",d->doneCopies+1) ); else emit newTask( i18n("Writing copy") ); emit burning(true); d->writerRunning = true; d->writerJob->start(); - d->outPipe.writeToFd( d->writerJob->fd(), true ); + d->outPipe.writeTo( d->writerJob->ioDevice(), true ); d->outPipe.open( true ); } else { if( m_removeImageFiles ) removeImageFiles(); if( d->canceled ) emit canceled(); jobFinished(false); d->running = false; } } } } else { removeImageFiles(); jobFinished(false); d->running = false; } } void K3b::DvdCopyJob::slotWriterFinished( bool success ) { d->writerRunning = false; d->outPipe.close(); // already finished? if( !d->running ) return; if( d->canceled ) { if( m_removeImageFiles ) removeImageFiles(); emit canceled(); jobFinished(false); d->running = false; } if( success ) { emit infoMessage( i18n("Successfully written copy %1.",d->doneCopies+1), INFO ); if( d->verifyData && !m_simulate ) { if( !d->verificationJob ) { d->verificationJob = new K3b::VerificationJob( this, this ); connect( d->verificationJob, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( d->verificationJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( d->verificationJob, SIGNAL(percent(int)), this, SLOT(slotVerificationProgress(int)) ); connect( d->verificationJob, SIGNAL(percent(int)), this, SIGNAL(subPercent(int)) ); connect( d->verificationJob, SIGNAL(finished(bool)), this, SLOT(slotVerificationFinished(bool)) ); connect( d->verificationJob, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } d->verificationJob->setDevice( m_writerDevice ); d->verificationJob->addTrack( 1, d->inPipe.checksum(), d->lastSector+1 ); if( m_copies > 1 ) emit newTask( i18n("Verifying copy %1",d->doneCopies+1) ); else emit newTask( i18n("Verifying copy") ); emit burning( false ); d->verificationJob->start(); } else if( ++d->doneCopies < m_copies ) { if( !m_writerDevice->eject() ) { blockingInformation( i18n("K3b was unable to eject the written medium. Please do so manually.") ); } if( waitForDvd() ) { prepareWriter(); emit newTask( i18n("Writing copy %1",d->doneCopies+1) ); emit burning(true); d->writerRunning = true; d->writerJob->start(); } else { if( d->canceled ) emit canceled(); jobFinished(false); d->running = false; return; } if( m_onTheFly ) { prepareReader(); d->readerRunning = true; d->dataTrackReader->start(); } else { - d->outPipe.writeToFd( d->writerJob->fd(), true ); + d->outPipe.writeTo( d->writerJob->ioDevice(), true ); d->outPipe.open( true ); } } else { if ( k3bcore->globalSettings()->ejectMedia() ) { K3b::Device::eject( m_writerDevice ); } if( m_removeImageFiles ) removeImageFiles(); d->running = false; jobFinished(true); } } else { if( m_removeImageFiles ) removeImageFiles(); d->running = false; jobFinished(false); } } void K3b::DvdCopyJob::slotVerificationFinished( bool success ) { // we simply ignore the results from the verification, the verification // job already emits a message if( ++d->doneCopies < m_copies ) { if( waitForDvd() ) { prepareWriter(); emit newTask( i18n("Writing copy %1",d->doneCopies+1) ); emit burning(true); d->writerRunning = true; d->writerJob->start(); } else { if( d->canceled ) emit canceled(); jobFinished(false); d->running = false; return; } if( m_onTheFly ) { prepareReader(); d->readerRunning = true; d->dataTrackReader->start(); } else { - d->outPipe.writeToFd( d->writerJob->fd(), true ); + d->outPipe.writeTo( d->writerJob->ioDevice(), true ); d->outPipe.open( true ); } } else { if( m_removeImageFiles ) removeImageFiles(); d->running = false; jobFinished( success ); } } // this is basically the same code as in K3b::DvdJob... :( // perhaps this should be moved to some K3b::GrowisofsHandler which also parses the growisofs output? bool K3b::DvdCopyJob::waitForDvd() { int mt = 0; if ( K3b::Device::isDvdMedia( d->sourceDiskInfo.mediaType() ) ) { if( m_writingMode == K3b::WRITING_MODE_RES_OVWR ) // we treat DVD+R(W) as restricted overwrite media mt = K3b::Device::MEDIA_DVD_RW_OVWR|K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_PLUS_R; else mt = K3b::Device::MEDIA_WRITABLE_DVD_SL; // // in case the source is a double layer DVD we made sure above that the writer // is capable of writing DVD+R-DL or DVD-R DL and here we wait for a DL DVD // if( d->sourceDiskInfo.numLayers() > 1 && d->sourceDiskInfo.size().mode1Bytes() > 4700372992LL ) { mt = K3b::Device::MEDIA_WRITABLE_DVD_DL; } } else if ( K3b::Device::isBdMedia( d->sourceDiskInfo.mediaType() ) ) { // FIXME: what about the size? layers and stuff? mt = K3b::Device::MEDIA_WRITABLE_BD; } else { // this should NEVER happen emit infoMessage( i18n( "Unsupported media type: %1" , K3b::Device::mediaTypeString( d->sourceDiskInfo.mediaType() ) ), ERROR ); return false; } int m = waitForMedia( m_writerDevice, K3b::Device::STATE_EMPTY, mt ); if( m < 0 ) { cancel(); return false; } if( m == 0 ) { emit infoMessage( i18n("Forced by user. Growisofs will be called without further tests."), INFO ); } else { // ------------------------------- // DVD Plus // ------------------------------- if( m & K3b::Device::MEDIA_DVD_PLUS_ALL ) { d->usedWritingMode = K3b::WRITING_MODE_RES_OVWR; if( m_simulate ) { if( !questionYesNo( i18n("K3b does not support simulation with DVD+R(W) media. " "Do you really want to continue? The media will actually be " "written to."), i18n("No Simulation with DVD+R(W)") ) ) { cancel(); return false; } // m_simulate = false; emit newTask( i18n("Writing DVD copy") ); } if( m_writingMode != K3b::WRITING_MODE_AUTO && m_writingMode != K3b::WRITING_MODE_RES_OVWR ) emit infoMessage( i18n("Writing mode ignored when writing DVD+R(W) media."), INFO ); if( m & K3b::Device::MEDIA_DVD_PLUS_RW ) emit infoMessage( i18n("Writing DVD+RW."), INFO ); else if( m & K3b::Device::MEDIA_DVD_PLUS_R_DL ) emit infoMessage( i18n("Writing Double Layer DVD+R."), INFO ); else emit infoMessage( i18n("Writing DVD+R."), INFO ); } // ------------------------------- // DVD Minus // ------------------------------- else if ( m & K3b::Device::MEDIA_DVD_MINUS_ALL ) { if( m_simulate && !m_writerDevice->dvdMinusTestwrite() ) { if( !questionYesNo( i18n("Your writer (%1 %2) does not support simulation with DVD-R(W) media. " "Do you really want to continue? The media will be written " "for real.", m_writerDevice->vendor() ,m_writerDevice->description()), i18n("No Simulation with DVD-R(W)") ) ) { cancel(); return false; } // m_simulate = false; } // // We do not default to DAO in onthefly mode since otherwise growisofs would // use the size from the PVD to reserve space on the DVD and that can be bad // if this size is wrong // With growisofs 5.15 we have the option to specify the size of the image to be written in DAO mode. // // bool sizeWithDao = ( k3bcore->externalBinManager()->binObject( "growisofs" ) && // k3bcore->externalBinManager()->binObject( "growisofs" )->version >= K3b::Version( 5, 15, -1 ) ); // TODO: check for feature 0x21 if( m & K3b::Device::MEDIA_DVD_RW_OVWR ) { emit infoMessage( i18n("Writing DVD-RW in restricted overwrite mode."), INFO ); d->usedWritingMode = K3b::WRITING_MODE_RES_OVWR; } else if( m & (K3b::Device::MEDIA_DVD_RW_SEQ| K3b::Device::MEDIA_DVD_RW) ) { if( m_writingMode == K3b::WRITING_MODE_DAO ) { // ( m_writingMode == K3b::WRITING_MODE_AUTO && // ( sizeWithDao || !m_onTheFly ) ) ) { emit infoMessage( i18n("Writing DVD-RW in DAO mode."), INFO ); d->usedWritingMode = K3b::WRITING_MODE_DAO; } else { emit infoMessage( i18n("Writing DVD-RW in incremental mode."), INFO ); d->usedWritingMode = K3b::WRITING_MODE_INCR_SEQ; } } else { // FIXME: DVD-R DL jump and stuff if( m_writingMode == K3b::WRITING_MODE_RES_OVWR ) emit infoMessage( i18n("Restricted Overwrite is not possible with DVD-R media."), INFO ); if( m_writingMode == K3b::WRITING_MODE_DAO ) { // ( m_writingMode == K3b::WRITING_MODE_AUTO && // ( sizeWithDao || !m_onTheFly ) ) ) { emit infoMessage( i18n("Writing %1 in DAO mode.",K3b::Device::mediaTypeString(m, true) ), INFO ); d->usedWritingMode = K3b::WRITING_MODE_DAO; } else { emit infoMessage( i18n("Writing %1 in incremental mode.",K3b::Device::mediaTypeString(m, true) ), INFO ); d->usedWritingMode = K3b::WRITING_MODE_INCR_SEQ; } } } // ------------------------------- // Blue-Ray // ------------------------------- else { emit infoMessage( i18n("Writing %1.", K3b::Device::mediaTypeString(m, true) ), INFO ); d->usedWritingMode = K3b::WRITING_MODE_INCR_SEQ; // just to set some mode for now } } return true; } void K3b::DvdCopyJob::removeImageFiles() { if( QFile::exists( m_imagePath ) ) { d->imageFile.remove(); emit infoMessage( i18n("Removed image file %1",m_imagePath), K3b::Job::SUCCESS ); } } QString K3b::DvdCopyJob::jobDescription() const { if( m_onlyCreateImage ) { return i18n("Creating Image"); } else { if( m_onTheFly ) return i18n("Copying DVD/BD On-The-Fly"); else return i18n("Copying DVD/BD"); } } QString K3b::DvdCopyJob::jobDetails() const { return i18np("Creating 1 copy", "Creating %1 copies", (m_simulate||m_onlyCreateImage) ? 1 : m_copies ); } void K3b::DvdCopyJob::setVerifyData( bool b ) { d->verifyData = b; } #include "k3bdvdcopyjob.moc" diff --git a/libk3b/jobs/k3bdvdformattingjob.cpp b/libk3b/jobs/k3bdvdformattingjob.cpp index 689fbcdcf..3289d9fd9 100644 --- a/libk3b/jobs/k3bdvdformattingjob.cpp +++ b/libk3b/jobs/k3bdvdformattingjob.cpp @@ -1,531 +1,529 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3bdvdformattingjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K3b::DvdFormattingJob::Private { public: Private() : quick(false), force(false), mode(K3b::WRITING_MODE_AUTO), device(0), process(0), dvdFormatBin(0), lastProgressValue(0), running(false), forceNoEject(false) { } bool quick; bool force; int mode; K3b::Device::Device* device; K3b::Process* process; const K3b::ExternalBin* dvdFormatBin; int lastProgressValue; bool success; bool canceled; bool running; bool forceNoEject; bool error; }; K3b::DvdFormattingJob::DvdFormattingJob( K3b::JobHandler* jh, QObject* parent ) : K3b::BurnJob( jh, parent ) { d = new Private; } K3b::DvdFormattingJob::~DvdFormattingJob() { delete d->process; delete d; } K3b::Device::Device* K3b::DvdFormattingJob::writer() const { return d->device; } void K3b::DvdFormattingJob::setForceNoEject( bool b ) { d->forceNoEject = b; } QString K3b::DvdFormattingJob::jobDescription() const { return i18n("Formatting DVD±RW"); } QString K3b::DvdFormattingJob::jobDetails() const { if( d->quick ) return i18n("Quick Format"); else return QString(); } void K3b::DvdFormattingJob::start() { d->canceled = false; d->running = true; d->error = false; jobStarted(); if( !d->device ) { emit infoMessage( i18n("No device set"), ERROR ); d->running = false; jobFinished(false); return; } // FIXME: check the return value if( K3b::isMounted( d->device ) ) { emit infoMessage( i18n("Unmounting medium"), INFO ); K3b::unmount( d->device ); } // // first wait for a dvd+rw or dvd-rw // Be aware that an empty DVD-RW might be reformatted to another writing mode // so we also wait for empty dvds // if( waitForMedia( d->device, K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE|K3b::Device::STATE_EMPTY, K3b::Device::MEDIA_WRITABLE_DVD, i18n("Please insert a rewritable DVD medium into drive

%1 %2 (%3).", d->device->vendor(), d->device->description(), d->device->blockDeviceName()) ) == -1 ) { emit canceled(); d->running = false; jobFinished(false); return; } emit infoMessage( i18n("Checking media..."), INFO ); emit newTask( i18n("Checking media") ); connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::NG_DISKINFO, d->device ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotDeviceHandlerFinished(K3b::Device::DeviceHandler*)) ); } void K3b::DvdFormattingJob::start( const K3b::Device::DiskInfo& di ) { d->canceled = false; d->running = true; jobStarted(); startFormatting( di ); } void K3b::DvdFormattingJob::cancel() { if( d->running ) { d->canceled = true; if( d->process ) - d->process->kill(); + d->process->terminate(); } else { kDebug() << "(K3b::DvdFormattingJob) not running."; } } void K3b::DvdFormattingJob::setDevice( K3b::Device::Device* dev ) { d->device = dev; } void K3b::DvdFormattingJob::setMode( int m ) { d->mode = m; } void K3b::DvdFormattingJob::setQuickFormat( bool b ) { d->quick = b; } void K3b::DvdFormattingJob::setForce( bool b ) { d->force = b; } void K3b::DvdFormattingJob::slotStderrLine( const QString& line ) { // * DVD±RW format utility by , version 4.4. // * 4.7GB DVD-RW media in Sequential mode detected. // * blanking 100.0| // * formatting 100.0| emit debuggingOutput( "dvd+rw-format", line ); // parsing for the -gui mode (since dvd+rw-format 4.6) int pos = line.indexOf( "blanking" ); if( pos < 0 ) pos = line.indexOf( "formatting" ); if( pos >= 0 ) { pos = line.indexOf( QRegExp( "\\d" ), pos ); } // parsing for \b\b... stuff else if( !line.startsWith("*") ) { pos = line.indexOf( QRegExp( "\\d" ) ); } else if( line.startsWith( ":-(" ) ) { if( line.startsWith( ":-( unable to proceed with format" ) ) { d->error = true; } } if( pos >= 0 ) { int endPos = line.indexOf( QRegExp("[^\\d\\.]"), pos ) - 1; bool ok; int progress = (int)(line.mid( pos, endPos - pos ).toDouble(&ok)); if( ok ) { d->lastProgressValue = progress; emit percent( progress ); } else { kDebug() << "(K3b::DvdFormattingJob) parsing error: '" << line.mid( pos, endPos - pos ) << "'"; } } } void K3b::DvdFormattingJob::slotProcessFinished( int exitCode, QProcess::ExitStatus exitStatus ) { if( d->canceled ) { emit canceled(); d->success = false; } else if( exitStatus == QProcess::NormalExit ) { if( !d->error && (exitCode == 0) ) { emit infoMessage( i18n("Formatting successfully completed"), K3b::Job::SUCCESS ); if( d->lastProgressValue < 100 ) { emit infoMessage( i18n("Do not be concerned with the progress stopping before 100%."), INFO ); emit infoMessage( i18n("The formatting will continue in the background during writing."), INFO ); } d->success = true; } else { emit infoMessage( i18n("%1 returned an unknown error (code %2).",d->dvdFormatBin->name(), exitCode), K3b::Job::ERROR ); emit infoMessage( i18n("Please send me an email with the last output."), K3b::Job::ERROR ); d->success = false; } } else { emit infoMessage( i18n("%1 did not exit cleanly.",d->dvdFormatBin->name()), ERROR ); d->success = false; } if( d->forceNoEject || !k3bcore->globalSettings()->ejectMedia() ) { d->running = false; jobFinished(d->success); } else { emit infoMessage( i18n("Ejecting medium..."), INFO ); connect( K3b::Device::eject( d->device ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotEjectingFinished(K3b::Device::DeviceHandler*)) ); } } void K3b::DvdFormattingJob::slotEjectingFinished( K3b::Device::DeviceHandler* dh ) { if( !dh->success() ) emit infoMessage( i18n("Unable to eject medium."), ERROR ); d->running = false; jobFinished(d->success); } void K3b::DvdFormattingJob::slotDeviceHandlerFinished( K3b::Device::DeviceHandler* dh ) { if( d->canceled ) { emit canceled(); jobFinished(false); d->running = false; } if( dh->success() ) { startFormatting( dh->diskInfo() ); } else { emit infoMessage( i18n("Unable to determine media state."), ERROR ); d->running = false; jobFinished(false); } } void K3b::DvdFormattingJob::startFormatting( const K3b::Device::DiskInfo& diskInfo ) { // // Now check the media type: // if DVD-RW: use d->mode // emit warning if formatting is full and stuff // // in overwrite mode: emit info that progress might stop before 100% since formatting will continue // in the background once the media gets rewritten (only DVD+RW?) // // emit info about what kind of media has been found if( !(diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_RW| K3b::Device::MEDIA_DVD_RW_SEQ| K3b::Device::MEDIA_DVD_RW_OVWR| K3b::Device::MEDIA_DVD_PLUS_RW)) ) { emit infoMessage( i18n("No rewritable DVD media found. Unable to format."), ERROR ); d->running = false; jobFinished(false); return; } bool format = true; // do we need to format bool blank = false; // blank is for DVD-RW sequential incremental // DVD-RW restricted overwrite and DVD+RW uses the force option (or no option at all) // // DVD+RW is quite easy to handle. There is only one possible mode and it is always recommended to not // format it more than once but to overwrite it once it is formatted // Once the initial formatting has been done it's always "appendable" (or "complete"???) // if( diskInfo.mediaType() == K3b::Device::MEDIA_DVD_PLUS_RW ) { emit infoMessage( i18n("Found %1 media.",K3b::Device::mediaTypeString(K3b::Device::MEDIA_DVD_PLUS_RW)), INFO ); // mode is ignored if( diskInfo.empty() ) { // // The DVD+RW is blank and needs to be initially formatted // blank = false; } else { emit infoMessage( i18n("No need to format %1 media more than once.", K3b::Device::mediaTypeString(K3b::Device::MEDIA_DVD_PLUS_RW)), INFO ); emit infoMessage( i18n("It may simply be overwritten."), INFO ); if( d->force ) { emit infoMessage( i18n("Forcing formatting anyway."), INFO ); emit infoMessage( i18n("It is not recommended to force formatting of DVD+RW media."), INFO ); emit infoMessage( i18n("Already after 10-20 reformats the media might be unusable."), INFO ); blank = false; } else { format = false; } } if( format ) emit newSubTask( i18n("Formatting DVD+RW") ); } // // DVD-RW has two modes: incremental sequential (the default which is also needed for DAO writing) // and restricted overwrite which compares to the DVD+RW mode. // else { // MEDIA_DVD_RW emit infoMessage( i18n("Found %1 media.",K3b::Device::mediaTypeString(K3b::Device::MEDIA_DVD_RW)), INFO ); if( diskInfo.currentProfile() != K3b::Device::MEDIA_UNKNOWN ) { emit infoMessage( i18n("Formatted in %1 mode.",K3b::Device::mediaTypeString(diskInfo.currentProfile())), INFO ); // // Is it possible to have an empty DVD-RW in restricted overwrite mode???? I don't think so. // if( diskInfo.empty() && (d->mode == K3b::WRITING_MODE_AUTO || (d->mode == K3b::WRITING_MODE_INCR_SEQ && diskInfo.currentProfile() == K3b::Device::MEDIA_DVD_RW_SEQ) || (d->mode == K3b::WRITING_MODE_RES_OVWR && diskInfo.currentProfile() == K3b::Device::MEDIA_DVD_RW_OVWR) ) ) { emit infoMessage( i18n("Media is already empty."), INFO ); if( d->force ) emit infoMessage( i18n("Forcing formatting anyway."), INFO ); else format = false; } else if( diskInfo.currentProfile() == K3b::Device::MEDIA_DVD_RW_OVWR && d->mode != K3b::WRITING_MODE_INCR_SEQ ) { emit infoMessage( i18n("No need to format %1 media more than once.", K3b::Device::mediaTypeString(diskInfo.currentProfile())), INFO ); emit infoMessage( i18n("It may simply be overwritten."), INFO ); if( d->force ) emit infoMessage( i18n("Forcing formatting anyway."), INFO ); else format = false; } if( format ) { if( d->mode == K3b::WRITING_MODE_AUTO ) { // just format in the same mode as the media is currently formatted blank = (diskInfo.currentProfile() == K3b::Device::MEDIA_DVD_RW_SEQ); } else { blank = (d->mode == K3b::WRITING_MODE_INCR_SEQ); } emit newSubTask( i18n("Formatting" " DVD-RW in %1 mode.",K3b::Device::mediaTypeString( blank ? K3b::Device::MEDIA_DVD_RW_SEQ : K3b::Device::MEDIA_DVD_RW_OVWR )) ); } } else { emit infoMessage( i18n("Unable to determine the current formatting state of the DVD-RW media."), ERROR ); d->running = false; jobFinished(false); return; } } if( format ) { delete d->process; d->process = new K3b::Process(); - d->process->setRunPrivileged(true); - // d->process->setSuppressEmptyLines(false); connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStderrLine(const QString&)) ); connect( d->process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessFinished(int, QProcess::ExitStatus)) ); d->dvdFormatBin = k3bcore->externalBinManager()->binObject( "dvd+rw-format" ); if( !d->dvdFormatBin ) { emit infoMessage( i18n("Could not find %1 executable.",QString("dvd+rw-format")), ERROR ); d->running = false; jobFinished(false); return; } if( !d->dvdFormatBin->copyright.isEmpty() ) emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3",d->dvdFormatBin->name(),d->dvdFormatBin->version,d->dvdFormatBin->copyright), INFO ); *d->process << d->dvdFormatBin; if( d->dvdFormatBin->version >= K3b::Version( 4, 6 ) ) *d->process << "-gui"; QString p; if( blank ) p = "-blank"; else p = "-force"; if( !d->quick ) p += "=full"; *d->process << p; *d->process << d->device->blockDeviceName(); // additional user parameters from config const QStringList& params = d->dvdFormatBin->userParameters(); for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) *d->process << *it; kDebug() << "***** dvd+rw-format parameters:\n"; QString s = d->process->joinedArgs(); kDebug() << s << endl << flush; emit debuggingOutput( "dvd+rw-format command:", s ); - if( !d->process->start( K3Process::All ) ) { + if( !d->process->start( KProcess::OnlyStderrChannel ) ) { // something went wrong when starting the program // it "should" be the executable kDebug() << "(K3b::DvdFormattingJob) could not start " << d->dvdFormatBin->path; emit infoMessage( i18n("Could not start %1.",d->dvdFormatBin->name()), K3b::Job::ERROR ); d->running = false; jobFinished(false); } else { emit newTask( i18n("Formatting") ); } } else { // already formatted :) d->running = false; jobFinished(true); } } #include "k3bdvdformattingjob.moc" diff --git a/libk3b/jobs/k3biso9660imagewritingjob.cpp b/libk3b/jobs/k3biso9660imagewritingjob.cpp index f065bb01d..30d50a82c 100644 --- a/libk3b/jobs/k3biso9660imagewritingjob.cpp +++ b/libk3b/jobs/k3biso9660imagewritingjob.cpp @@ -1,439 +1,446 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3biso9660imagewritingjob.h" #include "k3bverificationjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K3b::Iso9660ImageWritingJob::Private { public: K3b::ChecksumPipe checksumPipe; K3b::FileSplitter imageFile; }; K3b::Iso9660ImageWritingJob::Iso9660ImageWritingJob( K3b::JobHandler* hdl ) : K3b::BurnJob( hdl ), m_writingMode(K3b::WRITING_MODE_AUTO), m_simulate(false), m_device(0), m_noFix(false), m_speed(2), m_dataMode(K3b::DATA_MODE_AUTO), m_writer(0), m_tocFile(0), m_copies(1), m_verifyJob(0) { d = new Private; } K3b::Iso9660ImageWritingJob::~Iso9660ImageWritingJob() { delete m_tocFile; delete d; } void K3b::Iso9660ImageWritingJob::start() { m_canceled = m_finished = false; m_currentCopy = 1; jobStarted(); if( m_simulate ) m_verifyData = false; emit newTask( i18n("Preparing data") ); if( !QFile::exists( m_imagePath ) ) { emit infoMessage( i18n("Could not find image %1",m_imagePath), K3b::Job::ERROR ); jobFinished( false ); return; } KIO::filesize_t mb = K3b::imageFilesize( m_imagePath )/1024ULL/1024ULL; // very rough test but since most dvd images are 4,x or 8,x GB it should be enough m_dvd = ( mb > 900ULL ); startWriting(); } void K3b::Iso9660ImageWritingJob::slotWriterJobFinished( bool success ) { if( m_canceled ) { m_finished = true; emit canceled(); jobFinished(false); return; } d->checksumPipe.close(); if( success ) { if( !m_simulate && m_verifyData ) { emit burning(false); // allright // the writerJob should have emitted the "simulation/writing successful" signal if( !m_verifyJob ) { m_verifyJob = new K3b::VerificationJob( this ); connectSubJob( m_verifyJob, SLOT(slotVerificationFinished(bool)), K3b::Job::DEFAULT_SIGNAL_CONNECTION, K3b::Job::DEFAULT_SIGNAL_CONNECTION, SLOT(slotVerificationProgress(int)), SIGNAL(subPercent(int)) ); } m_verifyJob->setDevice( m_device ); m_verifyJob->clear(); m_verifyJob->addTrack( 1, d->checksumPipe.checksum(), K3b::imageFilesize( m_imagePath )/2048 ); if( m_copies == 1 ) emit newTask( i18n("Verifying written data") ); else emit newTask( i18n("Verifying written copy %1 of %2",m_currentCopy,m_copies) ); m_verifyJob->start(); } else if( m_currentCopy >= m_copies ) { + if ( k3bcore->globalSettings()->ejectMedia() ) { + K3b::Device::eject( m_device ); + } m_finished = true; jobFinished(true); } else { m_currentCopy++; + m_device->eject(); startWriting(); } } else { + if ( k3bcore->globalSettings()->ejectMedia() ) { + K3b::Device::eject( m_device ); + } m_finished = true; jobFinished(false); } } void K3b::Iso9660ImageWritingJob::slotVerificationFinished( bool success ) { if( m_canceled ) { m_finished = true; emit canceled(); jobFinished(false); return; } if( success && m_currentCopy < m_copies ) { m_currentCopy++; connect( K3b::Device::eject( m_device ), SIGNAL(finished(bool)), this, SLOT(startWriting()) ); return; } if( k3bcore->globalSettings()->ejectMedia() ) K3b::Device::eject( m_device ); m_finished = true; jobFinished( success ); } void K3b::Iso9660ImageWritingJob::slotVerificationProgress( int p ) { emit percent( (int)(100.0 / (double)m_copies * ( (double)(m_currentCopy-1) + 0.5 + (double)p/200.0 )) ); } void K3b::Iso9660ImageWritingJob::slotWriterPercent( int p ) { emit subPercent( p ); if( m_verifyData ) emit percent( (int)(100.0 / (double)m_copies * ( (double)(m_currentCopy-1) + ((double)p/200.0) )) ); else emit percent( (int)(100.0 / (double)m_copies * ( (double)(m_currentCopy-1) + ((double)p/100.0) )) ); } void K3b::Iso9660ImageWritingJob::slotNextTrack( int, int ) { if( m_copies == 1 ) emit newSubTask( i18n("Writing image") ); else emit newSubTask( i18n("Writing copy %1 of %2",m_currentCopy,m_copies) ); } void K3b::Iso9660ImageWritingJob::cancel() { if( !m_finished ) { m_canceled = true; if( m_writer ) m_writer->cancel(); if( m_verifyData && m_verifyJob ) m_verifyJob->cancel(); } } void K3b::Iso9660ImageWritingJob::startWriting() { emit newSubTask( i18n("Waiting for medium") ); // we wait for the following: // 1. if writing mode auto and writing app auto: all writable media types // 2. if writing mode auto and writing app not growisofs: all writable cd types // 3. if writing mode auto and writing app growisofs: all writable dvd types // 4. if writing mode TAO or RAW: all writable cd types // 5. if writing mode DAO and writing app auto: all writable cd types and DVD-R(W) // 6. if writing mode DAO and writing app GROWISOFS: DVD-R(W) // 7. if writing mode DAO and writing app CDRDAO or CDRECORD: all writable cd types // 8. if writing mode WRITING_MODE_INCR_SEQ: DVD-R(W) // 9. if writing mode WRITING_MODE_RES_OVWR: DVD-RW or DVD+RW int mt = 0; if( m_writingMode == K3b::WRITING_MODE_AUTO || m_writingMode == K3b::WRITING_MODE_DAO ) { if( writingApp() == K3b::WRITING_APP_CDRDAO ) mt = K3b::Device::MEDIA_WRITABLE_CD; else if( m_dvd ) mt = K3b::Device::MEDIA_WRITABLE_DVD; else mt = K3b::Device::MEDIA_WRITABLE; } else if( m_writingMode == K3b::WRITING_MODE_TAO || m_writingMode == K3b::WRITING_MODE_RAW ) { mt = K3b::Device::MEDIA_WRITABLE_CD; } else if( m_writingMode == K3b::WRITING_MODE_RES_OVWR ) { mt = K3b::Device::MEDIA_DVD_PLUS_R|K3b::Device::MEDIA_DVD_PLUS_R_DL|K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR; } else { mt = K3b::Device::MEDIA_WRITABLE_DVD; } // wait for the media int media = waitForMedia( m_device, K3b::Device::STATE_EMPTY, mt ); if( media < 0 ) { m_finished = true; emit canceled(); jobFinished(false); return; } // we simply always calculate the checksum, thus making the code simpler d->imageFile.close(); d->imageFile.setName( m_imagePath ); d->imageFile.open( QIODevice::ReadOnly ); d->checksumPipe.close(); - d->checksumPipe.readFromIODevice( &d->imageFile ); + d->checksumPipe.readFrom( &d->imageFile, true ); if( prepareWriter( media ) ) { emit burning(true); m_writer->start(); - d->checksumPipe.writeToFd( m_writer->fd(), true ); + d->checksumPipe.writeTo( m_writer->ioDevice(), true ); d->checksumPipe.open( K3b::ChecksumPipe::MD5, true ); } else { m_finished = true; jobFinished(false); } } bool K3b::Iso9660ImageWritingJob::prepareWriter( int mediaType ) { if( mediaType == 0 ) { // media forced // just to get it going... if( writingApp() != K3b::WRITING_APP_GROWISOFS && !m_dvd ) mediaType = K3b::Device::MEDIA_CD_R; else mediaType = K3b::Device::MEDIA_DVD_R; } delete m_writer; if( mediaType == K3b::Device::MEDIA_CD_R || mediaType == K3b::Device::MEDIA_CD_RW ) { K3b::WritingMode usedWritingMode = m_writingMode; if( usedWritingMode == K3b::WRITING_MODE_AUTO ) { // cdrecord seems to have problems when writing in mode2 in dao mode // so with cdrecord we use TAO if( m_noFix || m_dataMode == K3b::DATA_MODE_2 || !m_device->dao() ) usedWritingMode = K3b::WRITING_MODE_TAO; else usedWritingMode = K3b::WRITING_MODE_DAO; } K3b::WritingApp usedApp = writingApp(); if( usedApp == K3b::WRITING_APP_DEFAULT ) { if( usedWritingMode == K3b::WRITING_MODE_DAO && ( m_dataMode == K3b::DATA_MODE_2 || m_noFix ) ) usedApp = K3b::WRITING_APP_CDRDAO; else usedApp = K3b::WRITING_APP_CDRECORD; } if( usedApp == K3b::WRITING_APP_CDRECORD ) { K3b::CdrecordWriter* writer = new K3b::CdrecordWriter( m_device, this ); writer->setWritingMode( usedWritingMode ); writer->setSimulate( m_simulate ); writer->setBurnSpeed( m_speed ); if( m_noFix ) { writer->addArgument("-multi"); } if( (m_dataMode == K3b::DATA_MODE_AUTO && m_noFix) || m_dataMode == K3b::DATA_MODE_2 ) { if( k3bcore->externalBinManager()->binObject("cdrecord") && k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) ) writer->addArgument( "-xa" ); else writer->addArgument( "-xa1" ); } else writer->addArgument("-data"); // read from stdin writer->addArgument( QString("-tsize=%1s").arg( K3b::imageFilesize( m_imagePath )/2048 ) )->addArgument( "-" ); m_writer = writer; } else { // create cdrdao job K3b::CdrdaoWriter* writer = new K3b::CdrdaoWriter( m_device, this ); writer->setCommand( K3b::CdrdaoWriter::WRITE ); writer->setSimulate( m_simulate ); writer->setBurnSpeed( m_speed ); // multisession writer->setMulti( m_noFix ); // now write the tocfile delete m_tocFile; m_tocFile = new KTemporaryFile(); m_tocFile->setSuffix( ".toc" ); m_tocFile->open(); QTextStream s( m_tocFile ); if( (m_dataMode == K3b::DATA_MODE_AUTO && m_noFix) || m_dataMode == K3b::DATA_MODE_2 ) { s << "CD_ROM_XA" << "\n"; s << "\n"; s << "TRACK MODE2_FORM1" << "\n"; } else { s << "CD_ROM" << "\n"; s << "\n"; s << "TRACK MODE1" << "\n"; } s << "DATAFILE \"-\" " << QString::number( K3b::imageFilesize( m_imagePath ) ) << "\n"; m_tocFile->close(); writer->setTocFile( m_tocFile->fileName() ); m_writer = writer; } } else { // DVD if( mediaType & K3b::Device::MEDIA_DVD_PLUS_ALL ) { if( m_simulate ) { if( questionYesNo( i18n("K3b does not support simulation with DVD+R(W) media. " "Do you really want to continue? The media will be written " "for real."), i18n("No Simulation with DVD+R(W)") ) ) { return false; } } m_simulate = false; } K3b::GrowisofsWriter* writer = new K3b::GrowisofsWriter( m_device, this ); writer->setSimulate( m_simulate ); writer->setBurnSpeed( m_speed ); writer->setWritingMode( m_writingMode == K3b::WRITING_MODE_DAO ? K3b::WRITING_MODE_DAO : K3b::WRITING_MODE_AUTO ); writer->setImageToWrite( QString() ); // read from stdin writer->setCloseDvd( !m_noFix ); writer->setTrackSize( K3b::imageFilesize( m_imagePath )/2048 ); m_writer = writer; } connect( m_writer, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_writer, SIGNAL(nextTrack(int, int)), this, SLOT(slotNextTrack(int, int)) ); connect( m_writer, SIGNAL(percent(int)), this, SLOT(slotWriterPercent(int)) ); connect( m_writer, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); connect( m_writer, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); connect( m_writer, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); connect( m_writer, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)), this, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)) ); connect( m_writer, SIGNAL(finished(bool)), this, SLOT(slotWriterJobFinished(bool)) ); connect( m_writer, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); connect( m_writer, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( m_writer, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); return true; } QString K3b::Iso9660ImageWritingJob::jobDescription() const { if( m_simulate ) return i18n("Simulating ISO9660 Image"); else return ( i18n("Burning ISO9660 Image") + ( m_copies > 1 ? i18np(" - %1 Copy", " - %1 Copies", m_copies) : QString() ) ); } QString K3b::Iso9660ImageWritingJob::jobDetails() const { return m_imagePath.section("/", -1) + QString( " (%1)" ).arg(KIO::convertSize(K3b::filesize(m_imagePath))); } #include "k3biso9660imagewritingjob.moc" diff --git a/libk3b/jobs/k3breadcdreader.cpp b/libk3b/jobs/k3breadcdreader.cpp index 5a8281be9..ecd5b03d5 100644 --- a/libk3b/jobs/k3breadcdreader.cpp +++ b/libk3b/jobs/k3breadcdreader.cpp @@ -1,330 +1,338 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3breadcdreader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include - +#include class K3b::ReadcdReader::Private { public: Private() : process(0), - fdToWriteTo(-1), + ioDevToWriteTo(0), canceled(false) { } K3b::Msf firstSector, lastSector; K3b::Process* process; const K3b::ExternalBin* readcdBinObject; - int fdToWriteTo; + QIODevice* ioDevToWriteTo; bool canceled; long blocksToRead; int unreadableBlocks; int lastProgress; int lastProcessedSize; }; K3b::ReadcdReader::ReadcdReader( K3b::JobHandler* jh, QObject* parent ) : K3b::Job( jh, parent ), m_noCorr(false), m_clone(false), m_noError(false), m_c2Scan(false), m_speed(0), m_retries(128) { d = new Private(); } K3b::ReadcdReader::~ReadcdReader() { delete d->process; delete d; } bool K3b::ReadcdReader::active() const { return (d->process ? d->process->isRunning() : false); } -void K3b::ReadcdReader::writeToFd( int fd ) +void K3b::ReadcdReader::writeTo( QIODevice* dev ) { - d->fdToWriteTo = fd; + d->ioDevToWriteTo = dev; } void K3b::ReadcdReader::start() { jobStarted(); d->blocksToRead = 1; d->unreadableBlocks = 0; d->lastProgress = 0; d->lastProcessedSize = 0; // the first thing to do is to check for readcd d->readcdBinObject = k3bcore->externalBinManager()->binObject( "readcd" ); if( !d->readcdBinObject ) { emit infoMessage( i18n("Could not find %1 executable.",QString("readcd")), ERROR ); jobFinished(false); return; } // check if we have clone support if we need it if( m_clone ) { bool foundCloneSupport = false; if( !d->readcdBinObject->hasFeature( "clone" ) ) { // search all readcd installations K3b::ExternalProgram* readcdProgram = k3bcore->externalBinManager()->program( "readcd" ); QList readcdBins = readcdProgram->bins(); for( QList::const_iterator it = readcdBins.constBegin(); it != readcdBins.constEnd(); ++it ) { const K3b::ExternalBin* bin = *it; if( bin->hasFeature( "clone" ) ) { d->readcdBinObject = bin; emit infoMessage( i18n("Using readcd %1 instead of default version for clone support.", d->readcdBinObject->version), INFO ); foundCloneSupport = true; break; } } if( !foundCloneSupport ) { emit infoMessage( i18n("Could not find readcd executable with cloning support."), ERROR ); jobFinished(false); return; } } } // create the commandline delete d->process; d->process = new K3b::Process(); - connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); + connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStderrLine(const QString&)) ); connect( d->process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessExited(int, QProcess::ExitStatus)) ); *d->process << d->readcdBinObject; // display progress *d->process << "-v"; // Again we assume the device to be set! *d->process << QString("dev=%1").arg(K3b::externalBinDeviceParameter(m_readDevice, d->readcdBinObject)); if( m_speed > 0 ) *d->process << QString("speed=%1").arg(m_speed); // output - if( d->fdToWriteTo != -1 ) { + if( d->ioDevToWriteTo ) { *d->process << "f=-"; - d->process->writeToFd( d->fdToWriteTo ); + connect( d->process, SIGNAL(readyReadStandardOutput()), this, SLOT(slotReadyRead()) ); } else { - emit newTask( i18n("Writing image to %1.",m_imagePath) ); - emit infoMessage( i18n("Writing image to %1.",m_imagePath), INFO ); + emit newTask( i18n("Writing image to %1.", m_imagePath) ); + emit infoMessage( i18n("Writing image to %1.", m_imagePath), INFO ); *d->process << "f=" + m_imagePath; } if( m_noError ) *d->process << "-noerror"; if( m_clone ) { *d->process << "-clone"; // noCorr can only be used with cloning if( m_noCorr ) *d->process << "-nocorr"; } if( m_c2Scan ) *d->process << "-c2scan"; *d->process << QString("retries=%1").arg(m_retries); // readcd does not read the last sector specified if( d->firstSector < d->lastSector ) *d->process << QString("sectors=%1-%2").arg(d->firstSector.lba()).arg(d->lastSector.lba()+1); // Joerg sais it is a Linux kernel bug, anyway, with the default value it does not work *d->process << "ts=128k"; // additional user parameters from config const QStringList& params = d->readcdBinObject->userParameters(); for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) *d->process << *it; kDebug() << "***** readcd parameters:\n"; QString s = d->process->joinedArgs(); kDebug() << s << endl << flush; emit debuggingOutput("readcd command:", s); d->canceled = false; - if( !d->process->start( K3Process::AllOutput ) ) { + if( !d->process->start( KProcess::SeparateChannels ) ) { // something went wrong when starting the program // it "should" be the executable kError() << "(K3b::ReadcdReader) could not start readcd" << endl; emit infoMessage( i18n("Could not start readcd."), K3b::Job::ERROR ); jobFinished( false ); } } void K3b::ReadcdReader::cancel() { if( d->process ) { if( d->process->isRunning() ) { d->canceled = true; d->process->kill(); } } } -void K3b::ReadcdReader::slotStdLine( const QString& line ) +void K3b::ReadcdReader::slotStderrLine( const QString& line ) { emit debuggingOutput( "readcd", line ); int pos = -1; if( line.startsWith( "end:" ) ) { bool ok; d->blocksToRead = line.mid(4).toInt(&ok); if( d->firstSector < d->lastSector ) d->blocksToRead -= d->firstSector.lba(); if( !ok ) kError() << "(K3b::ReadcdReader) blocksToRead parsing error in line: " << line.mid(4) << endl; } else if( line.startsWith( "addr:" ) ) { bool ok; long currentReadBlock = line.mid( 6, line.indexOf("cnt")-7 ).toInt(&ok); if( d->firstSector < d->lastSector ) currentReadBlock -= d->firstSector.lba(); if( ok ) { int p = (int)(100.0 * (double)currentReadBlock / (double)d->blocksToRead); if( p > d->lastProgress ) { emit percent( p ); d->lastProgress = p; } int ps = currentReadBlock*2/1024; if( ps > d->lastProcessedSize ) { emit processedSize( ps, d->blocksToRead*2/1024 ); d->lastProcessedSize = ps; } } else kError() << "(K3b::ReadcdReader) currentReadBlock parsing error in line: " << line.mid( 6, line.indexOf("cnt")-7 ) << endl; } else if( line.contains("Cannot read source disk") ) { emit infoMessage( i18n("Cannot read source disk."), ERROR ); } else if( (pos = line.indexOf("Retrying from sector")) >= 0 ) { // parse the sector pos += 21; bool ok; int problemSector = line.mid( pos, line.indexOf( QRegExp("\\D"), pos )-pos ).toInt(&ok); if( !ok ) { kError() << "(K3b::ReadcdReader) problemSector parsing error in line: " << line.mid( pos, line.indexOf( QRegExp("\\D"), pos )-pos ) << endl; } emit infoMessage( i18n("Retrying from sector %1.",problemSector), INFO ); } else if( (pos = line.indexOf("Error on sector")) >= 0 ) { d->unreadableBlocks++; pos += 16; bool ok; int problemSector = line.mid( pos, line.indexOf( QRegExp("\\D"), pos )-pos ).toInt(&ok); if( !ok ) { kError() << "(K3b::ReadcdReader) problemSector parsing error in line: " << line.mid( pos, line.indexOf( QRegExp("\\D"), pos )-pos ) << endl; } if( line.contains( "not corrected") ) { emit infoMessage( i18n("Uncorrected error in sector %1",problemSector), ERROR ); } else { emit infoMessage( i18n("Corrected error in sector %1",problemSector), ERROR ); } } else { kDebug() << "(readcd) " << line; } } void K3b::ReadcdReader::slotProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { if( d->canceled ) { emit canceled(); jobFinished(false); } else if( exitStatus == QProcess::NormalExit ) { if( exitCode == 0 ) { jobFinished( true ); } else { emit infoMessage( i18n("%1 returned error: %2",QString("Readcd"), exitCode ), ERROR ); jobFinished( false ); } } else { emit infoMessage( i18n("Readcd exited abnormally."), ERROR ); jobFinished( false ); } } void K3b::ReadcdReader::setSectorRange( const K3b::Msf& first, const K3b::Msf& last ) { d->firstSector = first; d->lastSector = last; } -#include "k3breadcdreader.moc" +void K3b::ReadcdReader::slotReadyRead() +{ + // FIXME: error handling! + if ( d->ioDevToWriteTo ) { + d->ioDevToWriteTo->write( d->process->readAllStandardOutput() ); + } +} + +#include "k3breadcdreader.moc" diff --git a/libk3b/jobs/k3breadcdreader.h b/libk3b/jobs/k3breadcdreader.h index e462452a1..bc79eb0ad 100644 --- a/libk3b/jobs/k3breadcdreader.h +++ b/libk3b/jobs/k3breadcdreader.h @@ -1,89 +1,90 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_READCD_READER_H_ #define _K3B_READCD_READER_H_ #include #include namespace K3b { namespace Device { class Device; } class Msf; class ReadcdReader : public Job { Q_OBJECT public: ReadcdReader( JobHandler*, QObject* parent = 0 ); ~ReadcdReader(); bool active() const; public Q_SLOTS: void start(); void cancel(); void setReadDevice( K3b::Device::Device* dev ) { m_readDevice = dev; } /** 0 means MAX */ void setReadSpeed( int s ) { m_speed = s; } void setDisableCorrection( bool b ) { m_noCorr = b; } /** default: true */ void setAbortOnError( bool b ) { m_noError = !b; } void setC2Scan( bool b ) { m_c2Scan = b; } void setClone( bool b ) { m_clone = b; } void setRetries( int i ) { m_retries = i; } void setSectorRange( const Msf&, const Msf& ); void setImagePath( const QString& p ) { m_imagePath = p; } /** - * the data gets written directly into fd instead of the imagefile. + * the data gets written directly into device instead of the imagefile. * Be aware that this only makes sense before starting the job. - * To disable just set fd to -1 + * To disable just set ioDev to 0 */ - void writeToFd( int fd ); + void writeTo( QIODevice* ioDev ); private Q_SLOTS: - void slotStdLine( const QString& line ); + void slotStderrLine( const QString& line ); void slotProcessExited( int exitCode, QProcess::ExitStatus exitStatus ); + void slotReadyRead(); private: bool m_noCorr; bool m_clone; bool m_noError; bool m_c2Scan; int m_speed; int m_retries; Device::Device* m_readDevice; QString m_imagePath; class Private; Private* d; }; } #endif diff --git a/libk3b/jobs/k3bverificationjob.cpp b/libk3b/jobs/k3bverificationjob.cpp index 4601c8f5d..e1a44e990 100644 --- a/libk3b/jobs/k3bverificationjob.cpp +++ b/libk3b/jobs/k3bverificationjob.cpp @@ -1,381 +1,351 @@ /* * - * Copyright (C) 2003-2007 Sebastian Trueg + * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2007 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bverificationjob.h" #include #include -#include #include #include -#include +#include #include #include #include #include #include #include #include #include #include namespace { class VerificationJobTrackEntry { public: VerificationJobTrackEntry() : trackNumber(0) { } VerificationJobTrackEntry( int tn, const QByteArray& cs, const K3b::Msf& msf ) : trackNumber(tn), checksum(cs), length(msf) { } int trackNumber; QByteArray checksum; K3b::Msf length; }; } class K3b::VerificationJob::Private { public: - Private() - : md5Job(0), - device(0), - dataTrackReader(0) { - } + Private() + : device(0), + dataTrackReader(0) { + } - bool canceled; - K3b::Md5Job* md5Job; - K3b::Device::Device* device; + bool canceled; + K3b::Device::Device* device; - K3b::Msf grownSessionSize; + K3b::Msf grownSessionSize; - QList tracks; - int currentTrackIndex; + QList tracks; + int currentTrackIndex; - K3b::Device::DiskInfo diskInfo; - K3b::Device::Toc toc; + K3b::Device::DiskInfo diskInfo; + K3b::Device::Toc toc; - K3b::DataTrackReader* dataTrackReader; + K3b::DataTrackReader* dataTrackReader; - K3b::Msf currentTrackSize; - K3b::Msf totalSectors; - K3b::Msf alreadyReadSectors; + K3b::Msf currentTrackSize; + K3b::Msf totalSectors; + K3b::Msf alreadyReadSectors; - K3b::Pipe pipe; + K3b::ChecksumPipe pipe; - bool readSuccessful; + bool readSuccessful; - bool mediumHasBeenReloaded; + bool mediumHasBeenReloaded; }; K3b::VerificationJob::VerificationJob( K3b::JobHandler* hdl, QObject* parent ) - : K3b::Job( hdl, parent ) + : K3b::Job( hdl, parent ) { - d = new Private(); - - d->md5Job = new K3b::Md5Job( this ); - connect( d->md5Job, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); - connect( d->md5Job, SIGNAL(finished(bool)), this, SLOT(slotMd5JobFinished(bool)) ); - connect( d->md5Job, SIGNAL(debuggingOutput(const QString&, const QString&)), - this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + d = new Private(); } K3b::VerificationJob::~VerificationJob() { - delete d; + delete d; } void K3b::VerificationJob::cancel() { - d->canceled = true; - if( d->md5Job && d->md5Job->active() ) - d->md5Job->cancel(); - if( d->dataTrackReader && d->dataTrackReader->active() ) - d->dataTrackReader->cancel(); + d->canceled = true; + if( d->dataTrackReader && d->dataTrackReader->active() ) + d->dataTrackReader->cancel(); } void K3b::VerificationJob::addTrack( int trackNum, const QByteArray& checksum, const K3b::Msf& length ) { - d->tracks.append( VerificationJobTrackEntry( trackNum, checksum, length ) ); + d->tracks.append( VerificationJobTrackEntry( trackNum, checksum, length ) ); } void K3b::VerificationJob::clear() { - d->tracks.clear(); - d->grownSessionSize = 0; + d->tracks.clear(); + d->grownSessionSize = 0; } void K3b::VerificationJob::setDevice( K3b::Device::Device* dev ) { - d->device = dev; + d->device = dev; } void K3b::VerificationJob::setGrownSessionSize( const K3b::Msf& s ) { - d->grownSessionSize = s; + d->grownSessionSize = s; } void K3b::VerificationJob::start() { - jobStarted(); - - d->canceled = false; - d->currentTrackIndex = 0; - d->alreadyReadSectors = 0; - - waitForMedia( d->device, - K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE, - K3b::Device::MEDIA_WRITABLE ); - - // make sure the job is initialized - if ( d->tracks.isEmpty() ) { - emit infoMessage( i18n( "Internal Error: Verification job improperly initialized (%1)", - i18n("no tracks added") ), ERROR ); - jobFinished( false ); - return; - } - - emit newTask( i18n("Checking medium") ); - - d->mediumHasBeenReloaded = false; - connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::DISKINFO, d->device ), - SIGNAL(finished(K3b::Device::DeviceHandler*)), - this, - SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) ); + jobStarted(); + + d->canceled = false; + d->currentTrackIndex = 0; + d->alreadyReadSectors = 0; + + waitForMedia( d->device, + K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE, + K3b::Device::MEDIA_WRITABLE ); + + // make sure the job is initialized + if ( d->tracks.isEmpty() ) { + emit infoMessage( i18n( "Internal Error: Verification job improperly initialized (%1)", + i18n("no tracks added") ), ERROR ); + jobFinished( false ); + return; + } + + emit newTask( i18n("Checking medium") ); + + d->mediumHasBeenReloaded = false; + connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::DISKINFO, d->device ), + SIGNAL(finished(K3b::Device::DeviceHandler*)), + this, + SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) ); } void K3b::VerificationJob::slotDiskInfoReady( K3b::Device::DeviceHandler* dh ) { - if( d->canceled ) { - emit canceled(); - jobFinished(false); - } - - d->diskInfo = dh->diskInfo(); - d->toc = dh->toc(); - d->totalSectors = 0; - - // just to be sure check if we actually have all the tracks - int i = 0; - for( QList::iterator it = d->tracks.begin(); - it != d->tracks.end(); ++i, ++it ) { - - // 0 means "last track" - if( (*it).trackNumber == 0 ) - (*it).trackNumber = d->toc.count(); - - if( (int)d->toc.count() < (*it).trackNumber ) { - if ( d->mediumHasBeenReloaded ) { - emit infoMessage( i18n("Internal Error: Verification job improperly initialized (%1)", - i18n("specified track number not found on medium") ), ERROR ); - jobFinished( false ); - return; - } - else { - // many drives need to reload the medium to return to a proper state - d->mediumHasBeenReloaded = true; - emit infoMessage( i18n( "Need to reload medium to return to proper state." ), INFO ); - connect( K3b::Device::reload( d->device ), - SIGNAL(finished(K3b::Device::DeviceHandler*)), - this, - SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) ); - return; - } + if( d->canceled ) { + emit canceled(); + jobFinished(false); } - d->totalSectors += trackLength( i ); - } + d->diskInfo = dh->diskInfo(); + d->toc = dh->toc(); + d->totalSectors = 0; + + // just to be sure check if we actually have all the tracks + int i = 0; + for( QList::iterator it = d->tracks.begin(); + it != d->tracks.end(); ++i, ++it ) { + + // 0 means "last track" + if( (*it).trackNumber == 0 ) + (*it).trackNumber = d->toc.count(); + + if( (int)d->toc.count() < (*it).trackNumber ) { + if ( d->mediumHasBeenReloaded ) { + emit infoMessage( i18n("Internal Error: Verification job improperly initialized (%1)", + i18n("specified track number not found on medium") ), ERROR ); + jobFinished( false ); + return; + } + else { + // many drives need to reload the medium to return to a proper state + d->mediumHasBeenReloaded = true; + emit infoMessage( i18n( "Need to reload medium to return to proper state." ), INFO ); + connect( K3b::Device::reload( d->device ), + SIGNAL(finished(K3b::Device::DeviceHandler*)), + this, + SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) ); + return; + } + } - readTrack( 0 ); + d->totalSectors += trackLength( i ); + } + + readTrack( 0 ); } void K3b::VerificationJob::readTrack( int trackIndex ) { - d->currentTrackIndex = trackIndex; - d->readSuccessful = true; - - d->currentTrackSize = trackLength( trackIndex ); - if( d->currentTrackSize == 0 ) { - jobFinished(false); - return; - } - - emit newTask( i18n("Verifying track %1", d->tracks[trackIndex].trackNumber ) ); - - K3b::Device::Track& track = d->toc[d->tracks[trackIndex].trackNumber-1]; - - d->pipe.open(); - - if( track.type() == K3b::Device::Track::TYPE_DATA ) { - if( !d->dataTrackReader ) { - d->dataTrackReader = new K3b::DataTrackReader( this ); - connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) ); - // connect( d->dataTrackReader, SIGNAL(processedSize(int, int)), this, SLOT(slotReaderProcessedSize(int, int)) ); - connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotReaderFinished(bool)) ); - connect( d->dataTrackReader, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); - connect( d->dataTrackReader, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); - connect( d->dataTrackReader, SIGNAL(debuggingOutput(const QString&, const QString&)), - this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); - } + d->currentTrackIndex = trackIndex; + d->readSuccessful = true; - d->dataTrackReader->setDevice( d->device ); - d->dataTrackReader->setIgnoreErrors( false ); - d->dataTrackReader->setSectorSize( K3b::DataTrackReader::MODE1 ); - - // in case a session was grown the track size does not say anything about the verification data size - if( d->diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) && - d->grownSessionSize > 0 ) { - K3b::Iso9660 isoF( d->device ); - if( isoF.open() ) { - int firstSector = isoF.primaryDescriptor().volumeSpaceSize - d->grownSessionSize.lba(); - d->dataTrackReader->setSectorRange( firstSector, - isoF.primaryDescriptor().volumeSpaceSize -1 ); - } - else { - emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR ); - jobFinished( false ); - return; - } + d->currentTrackSize = trackLength( trackIndex ); + if( d->currentTrackSize == 0 ) { + jobFinished(false); + return; } - else - d->dataTrackReader->setSectorRange( track.firstSector(), - track.firstSector() + d->currentTrackSize -1 ); - d->md5Job->setMaxReadSize( d->currentTrackSize.mode1Bytes() ); + emit newTask( i18n("Verifying track %1", d->tracks[trackIndex].trackNumber ) ); - d->dataTrackReader->writeToFd( d->pipe.in() ); - d->dataTrackReader->start(); - } - else { - // FIXME: handle audio tracks - } + K3b::Device::Track& track = d->toc[d->tracks[trackIndex].trackNumber-1]; - d->md5Job->setFd( d->pipe.out() ); - d->md5Job->start(); -} + d->pipe.open(); + if( track.type() == K3b::Device::Track::TYPE_DATA ) { + if( !d->dataTrackReader ) { + d->dataTrackReader = new K3b::DataTrackReader( this ); + connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) ); + // connect( d->dataTrackReader, SIGNAL(processedSize(int, int)), this, SLOT(slotReaderProcessedSize(int, int)) ); + connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotReaderFinished(bool)) ); + connect( d->dataTrackReader, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( d->dataTrackReader, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( d->dataTrackReader, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + } -void K3b::VerificationJob::slotReaderProgress( int p ) -{ - emit subPercent( p ); + d->dataTrackReader->setDevice( d->device ); + d->dataTrackReader->setIgnoreErrors( false ); + d->dataTrackReader->setSectorSize( K3b::DataTrackReader::MODE1 ); + d->dataTrackReader->writeTo( &d->pipe ); + + // in case a session was grown the track size does not say anything about the verification data size + if( d->diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) && + d->grownSessionSize > 0 ) { + K3b::Iso9660 isoF( d->device ); + if( isoF.open() ) { + int firstSector = isoF.primaryDescriptor().volumeSpaceSize - d->grownSessionSize.lba(); + d->dataTrackReader->setSectorRange( firstSector, + isoF.primaryDescriptor().volumeSpaceSize -1 ); + } + else { + emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR ); + jobFinished( false ); + return; + } + } + else + d->dataTrackReader->setSectorRange( track.firstSector(), + track.firstSector() + d->currentTrackSize -1 ); - emit percent( 100 * ( d->alreadyReadSectors.lba() + ( p*d->currentTrackSize.lba()/100 ) ) / d->totalSectors.lba() ); + d->dataTrackReader->writeTo( &d->pipe ); + d->pipe.open(); + d->dataTrackReader->start(); + } + else { + // FIXME: handle audio tracks + } } -void K3b::VerificationJob::slotMd5JobFinished( bool success ) +void K3b::VerificationJob::slotReaderProgress( int p ) { - d->pipe.close(); + emit subPercent( p ); - if( success && !d->canceled && d->readSuccessful ) { - // compare the two sums - if( d->tracks[d->currentTrackIndex].checksum != d->md5Job->hexDigest() ) { - emit infoMessage( i18n("Written data in track %1 differs from original.",d->tracks[d->currentTrackIndex].trackNumber), ERROR ); - jobFinished(false); - } - else { - emit infoMessage( i18n("Written data verified."), SUCCESS ); - ++d->currentTrackIndex; - if( d->currentTrackIndex < (int)d->tracks.count() ) - readTrack( d->currentTrackIndex ); - else - jobFinished(true); - } - } - else { - // The md5job emitted an error message. So there is no need to do this again - jobFinished(false); - } + emit percent( 100 * ( d->alreadyReadSectors.lba() + ( p*d->currentTrackSize.lba()/100 ) ) / d->totalSectors.lba() ); } void K3b::VerificationJob::slotReaderFinished( bool success ) { - d->readSuccessful = success; - if( !d->readSuccessful ) - d->md5Job->cancel(); - else { - d->alreadyReadSectors += trackLength( d->currentTrackIndex ); - - // close the pipe and let the md5 job finish gracefully - d->pipe.closeIn(); - // d->md5Job->stop(); - } + d->readSuccessful = success; + if( d->readSuccessful && !d->canceled ) { + d->alreadyReadSectors += trackLength( d->currentTrackIndex ); + + d->pipe.close(); + + // compare the two sums + if( d->tracks[d->currentTrackIndex].checksum != d->pipe.checksum() ) { + emit infoMessage( i18n("Written data in track %1 differs from original.", d->tracks[d->currentTrackIndex].trackNumber), ERROR ); + jobFinished(false); + } + else { + emit infoMessage( i18n("Written data verified."), SUCCESS ); + ++d->currentTrackIndex; + if( d->currentTrackIndex < (int)d->tracks.count() ) + readTrack( d->currentTrackIndex ); + else + jobFinished(true); + } + } } K3b::Msf K3b::VerificationJob::trackLength( int trackIndex ) { - K3b::Msf& trackSize = d->tracks[trackIndex].length; - const int& trackNum = d->tracks[trackIndex].trackNumber; - - K3b::Device::Track& track = d->toc[trackNum-1]; - - if( trackSize == 0 ) { - trackSize = track.length(); - - if( d->diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) ) { - K3b::Iso9660 isoF( d->device, track.firstSector().lba() ); - if( isoF.open() ) { - trackSize = isoF.primaryDescriptor().volumeSpaceSize; - } - else { - emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR ); - return 0; - } - } + K3b::Msf& trackSize = d->tracks[trackIndex].length; + const int& trackNum = d->tracks[trackIndex].trackNumber; + + K3b::Device::Track& track = d->toc[trackNum-1]; + + if( trackSize == 0 ) { + trackSize = track.length(); + + if( d->diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) ) { + K3b::Iso9660 isoF( d->device, track.firstSector().lba() ); + if( isoF.open() ) { + trackSize = isoF.primaryDescriptor().volumeSpaceSize; + } + else { + emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR ); + return 0; + } + } - // - // A data track recorded in TAO mode has two run-out blocks which cannot be read and contain - // zero data anyway. The problem is that I do not know of a valid method to determine if a track - // was written in TAO (the control nibble does definitely not work, I never saw one which did not - // equal 4). - // So the solution for now is to simply try to read the last sector of a data track. If this is not - // possible we assume it was written in TAO mode and reduce the length by 2 sectors - // - if( track.type() == K3b::Device::Track::TYPE_DATA && - d->diskInfo.mediaType() & K3b::Device::MEDIA_CD_ALL ) { - // we try twice just to be sure - unsigned char buffer[2048]; - if( !d->device->read10( buffer, 2048, track.lastSector().lba(), 1 ) && - !d->device->read10( buffer, 2048, track.lastSector().lba(), 1 ) ) { - trackSize -= 2; - kDebug() << "(K3b::CdCopyJob) track " << trackNum << " probably TAO recorded."; - } + // + // A data track recorded in TAO mode has two run-out blocks which cannot be read and contain + // zero data anyway. The problem is that I do not know of a valid method to determine if a track + // was written in TAO (the control nibble does definitely not work, I never saw one which did not + // equal 4). + // So the solution for now is to simply try to read the last sector of a data track. If this is not + // possible we assume it was written in TAO mode and reduce the length by 2 sectors + // + if( track.type() == K3b::Device::Track::TYPE_DATA && + d->diskInfo.mediaType() & K3b::Device::MEDIA_CD_ALL ) { + // we try twice just to be sure + unsigned char buffer[2048]; + if( !d->device->read10( buffer, 2048, track.lastSector().lba(), 1 ) && + !d->device->read10( buffer, 2048, track.lastSector().lba(), 1 ) ) { + trackSize -= 2; + kDebug() << "(K3b::CdCopyJob) track " << trackNum << " probably TAO recorded."; + } + } } - } - return trackSize; + return trackSize; } #include "k3bverificationjob.moc" diff --git a/libk3b/jobs/k3bverificationjob.h b/libk3b/jobs/k3bverificationjob.h index 126c747ac..ad2b28d10 100644 --- a/libk3b/jobs/k3bverificationjob.h +++ b/libk3b/jobs/k3bverificationjob.h @@ -1,93 +1,92 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_VERIFICATION_JOB_H_ #define _K3B_VERIFICATION_JOB_H_ #include #include namespace K3b { namespace Device { class Device; class DeviceHandler; } /** * Generic verification job. Add tracks to be verified via addTrack. * The job will then verifiy the tracks set against the set checksums. * * The different track types are handled as follows: * \li Data/DVD tracks: Read the track with a 2048 bytes sector size. * Tracks length on DVD+RW media will be read from the iso9660 * descriptor. * \li Audio tracks: Rip the track with a 2352 bytes sector size. * In the case of audio tracks the job will not fail if the checksums * differ becasue audio CD tracks do not contain error correction data. * In this case only a warning will be emitted. * * Other sector sizes than 2048 bytes for data tracks are not supported yet, * i.e. Video CDs cannot be verified. * * TAO written tracks have two run-out sectors that are not read. */ class VerificationJob : public Job { Q_OBJECT public: VerificationJob( JobHandler*, QObject* parent = 0 ); ~VerificationJob(); public Q_SLOTS: void start(); void cancel(); void setDevice( Device::Device* dev ); void clear(); /** * Add a track to be verified. * \param tracknum The number of the track. If \a tracknum is 0 * the last track will be verified. * \param length Set to override the track length from the TOC. This may be * useful when writing to DVD+RW media and the iso descriptor does not * contain the exact image size (as true for many commercial Video DVDs) */ void addTrack( int tracknum, const QByteArray& checksum, const Msf& length = Msf() ); /** * Handle the special case of iso session growing */ void setGrownSessionSize( const Msf& ); private Q_SLOTS: // void slotMediaReloaded( bool success ); void slotDiskInfoReady( K3b::Device::DeviceHandler* dh ); void readTrack( int trackIndex ); - void slotMd5JobFinished( bool success ); void slotReaderProgress( int p ); void slotReaderFinished( bool success ); private: Msf trackLength( int trackNum ); class Private; Private* d; }; } #endif diff --git a/libk3b/jobs/k3bvideodvdtitledetectclippingjob.cpp b/libk3b/jobs/k3bvideodvdtitledetectclippingjob.cpp index 11fec2eb3..ea6548227 100644 --- a/libk3b/jobs/k3bvideodvdtitledetectclippingjob.cpp +++ b/libk3b/jobs/k3bvideodvdtitledetectclippingjob.cpp @@ -1,286 +1,285 @@ /* * - * Copyright (C) 2006 Sebastian Trueg + * Copyright (C) 2006-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2007 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bvideodvdtitledetectclippingjob.h" #include #include #include #include #include #include static const int s_unrealisticHighClippingValue = 100000; class K3b::VideoDVDTitleDetectClippingJob::Private { public: const K3b::ExternalBin* usedTranscodeBin; K3b::Process* process; bool canceled; unsigned int currentChapter; unsigned int currentFrames; unsigned int totalChapters; int lastProgress; int lastSubProgress; }; K3b::VideoDVDTitleDetectClippingJob::VideoDVDTitleDetectClippingJob( K3b::JobHandler* hdl, QObject* parent ) : K3b::Job( hdl, parent ), m_clippingTop( 0 ), m_clippingBottom( 0 ), m_clippingLeft( 0 ), m_clippingRight( 0 ), m_lowPriority( true ) { d = new Private; d->process = 0; } K3b::VideoDVDTitleDetectClippingJob::~VideoDVDTitleDetectClippingJob() { delete d->process; delete d; } void K3b::VideoDVDTitleDetectClippingJob::start() { jobStarted(); d->canceled = false; d->lastProgress = 0; // // It seems as if the last chapter is often way too short // d->totalChapters = m_dvd[m_titleNumber-1].numPTTs(); if( d->totalChapters > 1 && m_dvd[m_titleNumber-1][d->totalChapters-1].playbackTime().totalFrames() < 200 ) d->totalChapters--; // initial values (some way to big value) m_clippingTop = s_unrealisticHighClippingValue; m_clippingBottom = s_unrealisticHighClippingValue; m_clippingLeft = s_unrealisticHighClippingValue; m_clippingRight = s_unrealisticHighClippingValue; d->usedTranscodeBin = k3bcore->externalBinManager()->binObject("transcode"); if( !d->usedTranscodeBin ) { emit infoMessage( i18n("%1 executable could not be found.",QString("transcode")), ERROR ); jobFinished( false ); return; } if( d->usedTranscodeBin->version < K3b::Version( 1, 0, 0 ) ){ emit infoMessage( i18n("%1 version %2 is too old.", QString("transcode") ,d->usedTranscodeBin->version), ERROR ); jobFinished( false ); return; } emit debuggingOutput( QLatin1String( "Used versions" ), QLatin1String( "transcode: " ) + d->usedTranscodeBin->version ); if( !d->usedTranscodeBin->copyright.isEmpty() ) emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3" ,d->usedTranscodeBin->name() ,d->usedTranscodeBin->version ,d->usedTranscodeBin->copyright), INFO ); emit newTask( i18n("Analysing Title %1 of Video DVD %2",m_titleNumber,m_dvd.volumeIdentifier()) ); startTranscode( 1 ); } void K3b::VideoDVDTitleDetectClippingJob::startTranscode( int chapter ) { d->currentChapter = chapter; d->lastSubProgress = 0; // // If we have only one chapter and it is not longer than 2 minutes (value guessed based on some test DVD) // use the whole chapter // if( d->totalChapters == 1 ) d->currentFrames = qMin( 3000, qMax( 1, ( int )m_dvd[m_titleNumber-1][d->currentChapter-1].playbackTime().totalFrames() ) ); else d->currentFrames = qMin( 200, qMax( 1, ( int )m_dvd[m_titleNumber-1][d->currentChapter-1].playbackTime().totalFrames() ) ); // // prepare the process // delete d->process; d->process = new K3b::Process(); d->process->setSuppressEmptyLines(true); d->process->setSplitStdout(true); - // connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotTranscodeStderr(const QString&)) ); connect( d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotTranscodeStderr(const QString&)) ); connect( d->process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotTranscodeExited(int, QProcess::ExitStatus)) ); // the executable *d->process << d->usedTranscodeBin; // low priority if( m_lowPriority ) *d->process << "--nice" << "19"; // the input *d->process << "-i" << m_dvd.device()->blockDeviceName(); // select the title number and chapter *d->process << "-T" << QString("%1,%2").arg(m_titleNumber).arg(chapter); // null output *d->process << "-y" << "null,null" << "-o" << "/dev/null"; // analyze the first 200 frames *d->process << "-J" << QString("detectclipping=range=0-%1/5").arg(d->currentFrames); // also only decode the first 200 frames *d->process << "-c" << QString("0-%1").arg(d->currentFrames+1); // additional user parameters from config const QStringList& params = d->usedTranscodeBin->userParameters(); for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) *d->process << *it; // produce some debugging output kDebug() << "***** transcode parameters:\n"; QString s = d->process->joinedArgs(); kDebug() << s << flush; emit debuggingOutput( d->usedTranscodeBin->name() + " command:", s); // start the process - if( !d->process->start( K3Process::All ) ) { + if( !d->process->start( KProcess::MergedChannels ) ) { // something went wrong when starting the program // it "should" be the executable emit infoMessage( i18n("Could not start %1.",d->usedTranscodeBin->name()), K3b::Job::ERROR ); jobFinished(false); } else { emit newSubTask( i18n("Analysing Chapter %1 of %2",chapter,m_dvd[m_titleNumber-1].numPTTs()) ); emit subPercent( 0 ); } } void K3b::VideoDVDTitleDetectClippingJob::cancel() { d->canceled = true; if( d->process && d->process->isRunning() ) d->process->kill(); } void K3b::VideoDVDTitleDetectClippingJob::slotTranscodeStderr( const QString& line ) { emit debuggingOutput( "transcode", line ); // parse progress // encoding frame [185], 24.02 fps, 93.0%, ETA: 0:00:00, ( 0| 0| 0) if( line.startsWith( "encoding frame" ) ) { int pos1 = line.indexOf( '[', 15 ); int pos2 = line.indexOf( ']', pos1+1 ); if( pos1 > 0 && pos2 > 0 ) { bool ok; int encodedFrames = line.mid( pos1+1, pos2-pos1-1 ).toInt( &ok ); if( ok ) { int progress = 100 * encodedFrames / d->currentFrames; if( progress > d->lastSubProgress ) { d->lastSubProgress = progress; emit subPercent( progress ); } double part = 100.0 / (double)d->totalChapters; progress = (int)( ( (double)(d->currentChapter-1) * part ) + ( (double)progress / (double)d->totalChapters ) + 0.5 ); if( progress > d->lastProgress ) { d->lastProgress = progress; emit percent( progress ); } } } } // [detectclipping#0] valid area: X: 5..719 Y: 72..507 -> -j 72,6,68,0 else if( line.startsWith( "[detectclipping" ) ) { int pos = line.indexOf( "-j" ); if( pos > 0 ) { QStringList values = line.mid( pos+3 ).split( ',' ); m_clippingTop = qMin( m_clippingTop, values[0].toInt() ); m_clippingLeft = qMin( m_clippingLeft, values[1].toInt() ); m_clippingBottom = qMin( m_clippingBottom, values[2].toInt() ); m_clippingRight = qMin( m_clippingRight, values[3].toInt() ); } else kDebug() << "(K3b::VideoDVDTitleDetectClippingJob) failed to parse line: " << line; } } void K3b::VideoDVDTitleDetectClippingJob::slotTranscodeExited( int exitCode, QProcess::ExitStatus ) { switch( exitCode ) { case 0: d->currentChapter++; if( d->currentChapter > d->totalChapters ) { // // check if we did set any values at all // if( m_clippingTop == s_unrealisticHighClippingValue ) m_clippingTop = m_clippingLeft = m_clippingBottom = m_clippingRight = 0; if( d->totalChapters < m_dvd[m_titleNumber-1].numPTTs() ) emit infoMessage( i18n("Ignoring last chapter due to its short playback time."), INFO ); jobFinished( true ); } else { startTranscode( d->currentChapter ); } break; default: // FIXME: error handling if( d->canceled ) { emit canceled(); } else { emit infoMessage( i18n("%1 returned an unknown error (code %2).", d->usedTranscodeBin->name(), exitCode ), K3b::Job::ERROR ); emit infoMessage( i18n("Please send me an email with the last output."), K3b::Job::ERROR ); } jobFinished( false ); } } #include "k3bvideodvdtitledetectclippingjob.moc" diff --git a/libk3b/jobs/k3bvideodvdtitletranscodingjob.cpp b/libk3b/jobs/k3bvideodvdtitletranscodingjob.cpp index 3d397f9b8..f8267ac3c 100644 --- a/libk3b/jobs/k3bvideodvdtitletranscodingjob.cpp +++ b/libk3b/jobs/k3bvideodvdtitletranscodingjob.cpp @@ -1,578 +1,577 @@ /* * - * Copyright (C) 2006 Sebastian Trueg + * Copyright (C) 2006-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2007 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bvideodvdtitletranscodingjob.h" #include #include #include #include #include #include #include #include #include class K3b::VideoDVDTitleTranscodingJob::Private { public: - const K3b::ExternalBin* usedTranscodeBin; + const K3b::ExternalBin* usedTranscodeBin; - K3b::Process* process; + K3b::Process* process; - QString twoPassEncodingLogFile; + QString twoPassEncodingLogFile; - int currentEncodingPass; + int currentEncodingPass; - bool canceled; + bool canceled; - int lastProgress; - int lastSubProgress; + int lastProgress; + int lastSubProgress; }; K3b::VideoDVDTitleTranscodingJob::VideoDVDTitleTranscodingJob( K3b::JobHandler* hdl, QObject* parent ) - : K3b::Job( hdl, parent ), - m_clippingTop( 0 ), - m_clippingBottom( 0 ), - m_clippingLeft( 0 ), - m_clippingRight( 0 ), - m_width( 0 ), - m_height( 0 ), - m_titleNumber( 1 ), - m_audioStreamIndex( 0 ), - m_videoCodec( VIDEO_CODEC_FFMPEG_MPEG4 ), - m_audioCodec( AUDIO_CODEC_MP3 ), - m_videoBitrate( 1800 ), - m_audioBitrate( 128 ), - m_audioVBR( false ), - m_resampleAudio( false ), - m_twoPassEncoding( false ), - m_lowPriority( true ) + : K3b::Job( hdl, parent ), + m_clippingTop( 0 ), + m_clippingBottom( 0 ), + m_clippingLeft( 0 ), + m_clippingRight( 0 ), + m_width( 0 ), + m_height( 0 ), + m_titleNumber( 1 ), + m_audioStreamIndex( 0 ), + m_videoCodec( VIDEO_CODEC_FFMPEG_MPEG4 ), + m_audioCodec( AUDIO_CODEC_MP3 ), + m_videoBitrate( 1800 ), + m_audioBitrate( 128 ), + m_audioVBR( false ), + m_resampleAudio( false ), + m_twoPassEncoding( false ), + m_lowPriority( true ) { - d = new Private; - d->process = 0; + d = new Private; + d->process = 0; } K3b::VideoDVDTitleTranscodingJob::~VideoDVDTitleTranscodingJob() { - delete d->process; - delete d; + delete d->process; + delete d; } void K3b::VideoDVDTitleTranscodingJob::start() { - jobStarted(); - - d->canceled = false; - d->lastProgress = 0; - - d->usedTranscodeBin = k3bcore->externalBinManager()->binObject("transcode"); - if( !d->usedTranscodeBin ) { - emit infoMessage( i18n("%1 executable could not be found.",QString("transcode")), ERROR ); - jobFinished( false ); - return; - } - - if( d->usedTranscodeBin->version < K3b::Version( 1, 0, 0 ) ){ - emit infoMessage( i18n("%1 version %2 is too old." - ,QString("transcode") - ,d->usedTranscodeBin->version), ERROR ); - jobFinished( false ); - return; - } - - emit debuggingOutput( QLatin1String( "Used versions" ), QLatin1String( "transcode: " ) + d->usedTranscodeBin->version ); - - if( !d->usedTranscodeBin->copyright.isEmpty() ) - emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3" - ,d->usedTranscodeBin->name() - ,d->usedTranscodeBin->version - ,d->usedTranscodeBin->copyright), INFO ); - - // - // Let's take a look at the filename - // - if( m_filename.isEmpty() ) { - m_filename = K3b::findTempFile( "avi" ); - } - else { - // let's see if the directory exists and we can write to it - QFileInfo fileInfo( m_filename ); - QFileInfo dirInfo( fileInfo.path() ); - if( !dirInfo.exists() && !KStandardDirs::makeDir( dirInfo.absoluteFilePath() ) ) { - emit infoMessage( i18n("Unable to create folder '%1'",dirInfo.filePath()), ERROR ); - return; + jobStarted(); + + d->canceled = false; + d->lastProgress = 0; + + d->usedTranscodeBin = k3bcore->externalBinManager()->binObject("transcode"); + if( !d->usedTranscodeBin ) { + emit infoMessage( i18n("%1 executable could not be found.",QString("transcode")), ERROR ); + jobFinished( false ); + return; } - else if( !dirInfo.isDir() || !dirInfo.isWritable() ) { - emit infoMessage( i18n("Invalid filename: '%1'",m_filename), ERROR ); - jobFinished( false ); - return; + + if( d->usedTranscodeBin->version < K3b::Version( 1, 0, 0 ) ){ + emit infoMessage( i18n("%1 version %2 is too old." + ,QString("transcode") + ,d->usedTranscodeBin->version), ERROR ); + jobFinished( false ); + return; } - } - // - // Determine a log file for two-pass encoding - // - d->twoPassEncodingLogFile = K3b::findTempFile( "log" ); + emit debuggingOutput( QLatin1String( "Used versions" ), QLatin1String( "transcode: " ) + d->usedTranscodeBin->version ); - emit newTask( i18n("Transcoding title %1 from Video DVD %2",m_titleNumber,m_dvd.volumeIdentifier()) ); + if( !d->usedTranscodeBin->copyright.isEmpty() ) + emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3" + ,d->usedTranscodeBin->name() + ,d->usedTranscodeBin->version + ,d->usedTranscodeBin->copyright), INFO ); - // - // Ok then, let's begin - // - startTranscode( m_twoPassEncoding ? 1 : 0 ); + // + // Let's take a look at the filename + // + if( m_filename.isEmpty() ) { + m_filename = K3b::findTempFile( "avi" ); + } + else { + // let's see if the directory exists and we can write to it + QFileInfo fileInfo( m_filename ); + QFileInfo dirInfo( fileInfo.path() ); + if( !dirInfo.exists() && !KStandardDirs::makeDir( dirInfo.absoluteFilePath() ) ) { + emit infoMessage( i18n("Unable to create folder '%1'",dirInfo.filePath()), ERROR ); + return; + } + else if( !dirInfo.isDir() || !dirInfo.isWritable() ) { + emit infoMessage( i18n("Invalid filename: '%1'",m_filename), ERROR ); + jobFinished( false ); + return; + } + } + + // + // Determine a log file for two-pass encoding + // + d->twoPassEncodingLogFile = K3b::findTempFile( "log" ); + + emit newTask( i18n("Transcoding title %1 from Video DVD %2",m_titleNumber,m_dvd.volumeIdentifier()) ); + + // + // Ok then, let's begin + // + startTranscode( m_twoPassEncoding ? 1 : 0 ); } void K3b::VideoDVDTitleTranscodingJob::startTranscode( int pass ) { - d->currentEncodingPass = pass; - d->lastSubProgress = 0; - - QString videoCodecString; - switch( m_videoCodec ) { - case VIDEO_CODEC_XVID: - videoCodecString = "xvid"; - break; - - case VIDEO_CODEC_FFMPEG_MPEG4: - videoCodecString = "ffmpeg"; - break; - - default: - emit infoMessage( i18n("Invalid Video codec set: %1",m_videoCodec), ERROR ); - jobFinished( false ); - return; - } - - QString audioCodecString; - switch( m_audioCodec ) { - case AUDIO_CODEC_MP3: - audioCodecString = "0x55"; - break; - - // ogg only works (as in: transcode does something) with .y ,ogg - // but then the video is garbage (at least to xine and mplayer on my system) - // case AUDIO_CODEC_OGG_VORBIS: - // audioCodecString = "0xfffe"; - // break; - - case AUDIO_CODEC_AC3_STEREO: - case AUDIO_CODEC_AC3_PASSTHROUGH: - audioCodecString = "0x2000"; - break; - - default: - emit infoMessage( i18n("Invalid Audio codec set: %1",m_audioCodec), ERROR ); - jobFinished( false ); - return; - } - - // - // prepare the process - // - delete d->process; - d->process = new K3b::Process(); - d->process->setSuppressEmptyLines(true); - d->process->setSplitStdout(true); - connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotTranscodeStderr(const QString&)) ); - connect( d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotTranscodeStderr(const QString&)) ); - connect( d->process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotTranscodeExited(int, QProcess::ExitStatus)) ); - - // the executable - *d->process << d->usedTranscodeBin; - - // low priority - if( m_lowPriority ) - *d->process << "--nice" << "19"; - - // we only need 100 steps, but to make sure we use 150 - if ( d->usedTranscodeBin->version.simplify() >= K3b::Version( 1, 1, 0 ) ) - *d->process << "--progress_meter" << "2" << "--progress_rate" << QString::number(m_dvd[m_titleNumber-1].playbackTime().totalFrames()/150); - else - *d->process << "--print_status" << QString::number(m_dvd[m_titleNumber-1].playbackTime().totalFrames()/150); - - // the input - *d->process << "-i" << m_dvd.device()->blockDeviceName(); - - // just to make sure - *d->process << "-x" << "dvd"; - - // select the title number - *d->process << "-T" << QString("%1,-1,1").arg( m_titleNumber ); - - // select the audio stream to extract - if ( m_dvd[m_titleNumber-1].numAudioStreams() > 0 ) - *d->process << "-a" << QString::number( m_audioStreamIndex ); - - // clipping - *d->process << "-j" << QString("%1,%2,%3,%4") - .arg(m_clippingTop) - .arg(m_clippingLeft) - .arg(m_clippingBottom) - .arg(m_clippingRight); - - // select the encoding type (single pass or two-pass) and the log file for two-pass encoding - // the latter is unused for pass = 0 - *d->process << "-R" << QString("%1,%2").arg( pass ).arg( d->twoPassEncodingLogFile ); - - // depending on the pass we use different options - if( pass != 1 ) { - // select video codec - *d->process << "-y" << videoCodecString; - - // select the audio codec to use - *d->process << "-N" << audioCodecString; - - if( m_audioCodec == AUDIO_CODEC_AC3_PASSTHROUGH ) { - // keep 5.1 sound - *d->process << "-A"; + d->currentEncodingPass = pass; + d->lastSubProgress = 0; + + QString videoCodecString; + switch( m_videoCodec ) { + case VIDEO_CODEC_XVID: + videoCodecString = "xvid"; + break; + + case VIDEO_CODEC_FFMPEG_MPEG4: + videoCodecString = "ffmpeg"; + break; + + default: + emit infoMessage( i18n("Invalid Video codec set: %1",m_videoCodec), ERROR ); + jobFinished( false ); + return; } - else { - // audio quality settings - *d->process << "-b" << QString("%1,%2").arg(m_audioBitrate).arg(m_audioVBR ? 1 : 0); - // resample audio stream to 44.1 khz - if( m_resampleAudio ) - *d->process << "-E" << "44100"; + QString audioCodecString; + switch( m_audioCodec ) { + case AUDIO_CODEC_MP3: + audioCodecString = "0x55"; + break; + + // ogg only works (as in: transcode does something) with .y ,ogg + // but then the video is garbage (at least to xine and mplayer on my system) + // case AUDIO_CODEC_OGG_VORBIS: + // audioCodecString = "0xfffe"; + // break; + + case AUDIO_CODEC_AC3_STEREO: + case AUDIO_CODEC_AC3_PASSTHROUGH: + audioCodecString = "0x2000"; + break; + + default: + emit infoMessage( i18n("Invalid Audio codec set: %1",m_audioCodec), ERROR ); + jobFinished( false ); + return; } - // the output filename - *d->process << "-o" << m_filename; - } - else { - // gather information about the video stream, ignore audio - *d->process << "-y" << QString("%1,null").arg( videoCodecString ); - - // we ignore the output from the first pass - *d->process << "-o" << "/dev/null"; - } - - // choose the ffmpeg codec - if( m_videoCodec == VIDEO_CODEC_FFMPEG_MPEG4 ) { - *d->process << "-F" << "mpeg4"; - } - - // video bitrate - *d->process << "-w" << QString::number( m_videoBitrate ); - - // video resizing - int usedWidth = m_width; - int usedHeight = m_height; - if( m_width == 0 || m_height == 0 ) { // - // The "real" size of the video, considering anamorph encoding + // prepare the process // - int realHeight = m_dvd[m_titleNumber-1].videoStream().realPictureHeight(); - int readWidth = m_dvd[m_titleNumber-1].videoStream().realPictureWidth(); + delete d->process; + d->process = new K3b::Process(); + d->process->setSuppressEmptyLines(true); + d->process->setSplitStdout(true); + connect( d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotTranscodeStderr(const QString&)) ); + connect( d->process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotTranscodeExited(int, QProcess::ExitStatus)) ); + + // the executable + *d->process << d->usedTranscodeBin; + + // low priority + if( m_lowPriority ) + *d->process << "--nice" << "19"; + + // we only need 100 steps, but to make sure we use 150 + if ( d->usedTranscodeBin->version.simplify() >= K3b::Version( 1, 1, 0 ) ) + *d->process << "--progress_meter" << "2" << "--progress_rate" << QString::number(m_dvd[m_titleNumber-1].playbackTime().totalFrames()/150); + else + *d->process << "--print_status" << QString::number(m_dvd[m_titleNumber-1].playbackTime().totalFrames()/150); + + // the input + *d->process << "-i" << m_dvd.device()->blockDeviceName(); + + // just to make sure + *d->process << "-x" << "dvd"; + + // select the title number + *d->process << "-T" << QString("%1,-1,1").arg( m_titleNumber ); + + // select the audio stream to extract + if ( m_dvd[m_titleNumber-1].numAudioStreams() > 0 ) + *d->process << "-a" << QString::number( m_audioStreamIndex ); + + // clipping + *d->process << "-j" << QString("%1,%2,%3,%4") + .arg(m_clippingTop) + .arg(m_clippingLeft) + .arg(m_clippingBottom) + .arg(m_clippingRight); + + // select the encoding type (single pass or two-pass) and the log file for two-pass encoding + // the latter is unused for pass = 0 + *d->process << "-R" << QString("%1,%2").arg( pass ).arg( d->twoPassEncodingLogFile ); + + // depending on the pass we use different options + if( pass != 1 ) { + // select video codec + *d->process << "-y" << videoCodecString; + + // select the audio codec to use + *d->process << "-N" << audioCodecString; + + if( m_audioCodec == AUDIO_CODEC_AC3_PASSTHROUGH ) { + // keep 5.1 sound + *d->process << "-A"; + } + else { + // audio quality settings + *d->process << "-b" << QString("%1,%2").arg(m_audioBitrate).arg(m_audioVBR ? 1 : 0); + + // resample audio stream to 44.1 khz + if( m_resampleAudio ) + *d->process << "-E" << "44100"; + } + + // the output filename + *d->process << "-o" << m_filename; + } + else { + // gather information about the video stream, ignore audio + *d->process << "-y" << QString("%1,null").arg( videoCodecString ); - // - // The clipped size with the correct aspect ratio - // - int clippedHeight = realHeight - m_clippingTop - m_clippingBottom; - int clippedWidth = readWidth - m_clippingLeft - m_clippingRight; + // we ignore the output from the first pass + *d->process << "-o" << "/dev/null"; + } + + // choose the ffmpeg codec + if( m_videoCodec == VIDEO_CODEC_FFMPEG_MPEG4 ) { + *d->process << "-F" << "mpeg4"; + } + + // video bitrate + *d->process << "-w" << QString::number( m_videoBitrate ); + + // video resizing + int usedWidth = m_width; + int usedHeight = m_height; + if( m_width == 0 || m_height == 0 ) { + // + // The "real" size of the video, considering anamorph encoding + // + int realHeight = m_dvd[m_titleNumber-1].videoStream().realPictureHeight(); + int readWidth = m_dvd[m_titleNumber-1].videoStream().realPictureWidth(); + + // + // The clipped size with the correct aspect ratio + // + int clippedHeight = realHeight - m_clippingTop - m_clippingBottom; + int clippedWidth = readWidth - m_clippingLeft - m_clippingRight; + + // + // Now simply resize the clipped video to the wanted size + // + if( usedWidth > 0 ) { + usedHeight = clippedHeight * usedWidth / clippedWidth; + } + else { + if( usedHeight == 0 ) { + // + // This is the default case in which both m_width and m_height are 0. + // The result will be a size of clippedWidth x clippedHeight + // + usedHeight = clippedHeight; + } + usedWidth = clippedWidth * usedHeight / clippedHeight; + } + } // - // Now simply resize the clipped video to the wanted size + // Now make sure both width and height are multiple of 16 the simple way // - if( usedWidth > 0 ) { - usedHeight = clippedHeight * usedWidth / clippedWidth; + usedWidth -= usedWidth%16; + usedHeight -= usedHeight%16; + + // we only give information about the resizing of the video once + if( pass < 2 ) + emit infoMessage( i18n("Resizing picture of title %1 to %2x%3",m_titleNumber,usedWidth,usedHeight), INFO ); + *d->process << "-Z" << QString("%1x%2").arg(usedWidth).arg(usedHeight); + + // additional user parameters from config + const QStringList& params = d->usedTranscodeBin->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *d->process << *it; + + // produce some debugging output + kDebug() << "***** transcode parameters:\n"; + QString s = d->process->joinedArgs(); + kDebug() << s << flush; + emit debuggingOutput( d->usedTranscodeBin->name() + " command:", s); + + // start the process + if( !d->process->start( KProcess::MergedChannels ) ) { + // something went wrong when starting the program + // it "should" be the executable + emit infoMessage( i18n("Could not start %1.",d->usedTranscodeBin->name()), K3b::Job::ERROR ); + jobFinished(false); } else { - if( usedHeight == 0 ) { - // - // This is the default case in which both m_width and m_height are 0. - // The result will be a size of clippedWidth x clippedHeight - // - usedHeight = clippedHeight; - } - usedWidth = clippedWidth * usedHeight / clippedHeight; + if( pass == 0 ) + emit newSubTask( i18n("Single-pass Encoding") ); + else if( pass == 1 ) + emit newSubTask( i18n("Two-pass Encoding: First Pass") ); + else + emit newSubTask( i18n("Two-pass Encoding: Second Pass") ); + + emit subPercent( 0 ); } - } - - // - // Now make sure both width and height are multiple of 16 the simple way - // - usedWidth -= usedWidth%16; - usedHeight -= usedHeight%16; - - // we only give information about the resizing of the video once - if( pass < 2 ) - emit infoMessage( i18n("Resizing picture of title %1 to %2x%3",m_titleNumber,usedWidth,usedHeight), INFO ); - *d->process << "-Z" << QString("%1x%2").arg(usedWidth).arg(usedHeight); - - // additional user parameters from config - const QStringList& params = d->usedTranscodeBin->userParameters(); - for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) - *d->process << *it; - - // produce some debugging output - kDebug() << "***** transcode parameters:\n"; - QString s = d->process->joinedArgs(); - kDebug() << s << flush; - emit debuggingOutput( d->usedTranscodeBin->name() + " command:", s); - - // start the process - if( !d->process->start( K3Process::All ) ) { - // something went wrong when starting the program - // it "should" be the executable - emit infoMessage( i18n("Could not start %1.",d->usedTranscodeBin->name()), K3b::Job::ERROR ); - jobFinished(false); - } - else { - if( pass == 0 ) - emit newSubTask( i18n("Single-pass Encoding") ); - else if( pass == 1 ) - emit newSubTask( i18n("Two-pass Encoding: First Pass") ); - else - emit newSubTask( i18n("Two-pass Encoding: Second Pass") ); - - emit subPercent( 0 ); - } } void K3b::VideoDVDTitleTranscodingJob::cancel() { - // FIXME: do not cancel before one frame has been encoded. transcode seems to hang then - // find a way to determine all subprocess ids to kill all of them - d->canceled = true; - if( d->process && d->process->isRunning() ) - d->process->kill(); + // FIXME: do not cancel before one frame has been encoded. transcode seems to hang then + // find a way to determine all subprocess ids to kill all of them + d->canceled = true; + if( d->process && d->process->isRunning() ) + d->process->kill(); } void K3b::VideoDVDTitleTranscodingJob::cleanup( bool success ) { - if( QFile::exists( d->twoPassEncodingLogFile ) ) { - QFile::remove( d->twoPassEncodingLogFile ); - } - - if( !success && QFile::exists( m_filename ) ) { - emit infoMessage( i18n("Removing incomplete video file '%1'",m_filename), INFO ); - QFile::remove( m_filename ); - } + if( QFile::exists( d->twoPassEncodingLogFile ) ) { + QFile::remove( d->twoPassEncodingLogFile ); + } + + if( !success && QFile::exists( m_filename ) ) { + emit infoMessage( i18n("Removing incomplete video file '%1'",m_filename), INFO ); + QFile::remove( m_filename ); + } } void K3b::VideoDVDTitleTranscodingJob::slotTranscodeStderr( const QString& line ) { - emit debuggingOutput( "transcode", line ); - - // parse progress - // encoding frames [000000-000144], 27.58 fps, EMT: 0:00:05, ( 0| 0| 0) - if( line.startsWith( "encoding frame" ) ) { - int pos1 = line.indexOf( '-', 15 ); - int pos2 = line.indexOf( ']', pos1+1 ); - if( pos1 > 0 && pos2 > 0 ) { - bool ok; - int encodedFrames = line.mid( pos1+1, pos2-pos1-1 ).toInt( &ok ); - if( ok ) { - int progress = 100 * encodedFrames / m_dvd[m_titleNumber-1].playbackTime().totalFrames(); - - if( progress > d->lastSubProgress ) { - d->lastSubProgress = progress; - emit subPercent( progress ); - } - - if( m_twoPassEncoding ) { - progress /= 2; - if( d->currentEncodingPass == 2 ) - progress += 50; - } - - if( progress > d->lastProgress ) { - d->lastProgress = progress; - emit percent( progress ); - } - } + emit debuggingOutput( "transcode", line ); + + // parse progress + // encoding frames [000000-000144], 27.58 fps, EMT: 0:00:05, ( 0| 0| 0) + if( line.startsWith( "encoding frame" ) ) { + int pos1 = line.indexOf( '-', 15 ); + int pos2 = line.indexOf( ']', pos1+1 ); + if( pos1 > 0 && pos2 > 0 ) { + bool ok; + int encodedFrames = line.mid( pos1+1, pos2-pos1-1 ).toInt( &ok ); + if( ok ) { + int progress = 100 * encodedFrames / m_dvd[m_titleNumber-1].playbackTime().totalFrames(); + + if( progress > d->lastSubProgress ) { + d->lastSubProgress = progress; + emit subPercent( progress ); + } + + if( m_twoPassEncoding ) { + progress /= 2; + if( d->currentEncodingPass == 2 ) + progress += 50; + } + + if( progress > d->lastProgress ) { + d->lastProgress = progress; + emit percent( progress ); + } + } + } } - } } void K3b::VideoDVDTitleTranscodingJob::slotTranscodeExited( int exitCode, QProcess::ExitStatus exitStatus ) { - if( d->canceled ) { - emit canceled(); - cleanup( false ); - jobFinished( false ); - } - else if( exitStatus == QProcess::NormalExit ) { - switch( exitCode ) { - case 0: - if( d->currentEncodingPass == 1 ) { - emit percent( 50 ); - // start second encoding pass - startTranscode( 2 ); - } - else { - emit percent( 100 ); - cleanup( true ); - jobFinished( true ); - } - break; - - default: - // FIXME: error handling - - emit infoMessage( i18n("%1 returned an unknown error (code %2).", - d->usedTranscodeBin->name(), exitCode ), - K3b::Job::ERROR ); - emit infoMessage( i18n("Please send me an email with the last output."), K3b::Job::ERROR ); - - cleanup( false ); - jobFinished( false ); + if( d->canceled ) { + emit canceled(); + cleanup( false ); + jobFinished( false ); + } + else if( exitStatus == QProcess::NormalExit ) { + switch( exitCode ) { + case 0: + if( d->currentEncodingPass == 1 ) { + emit percent( 50 ); + // start second encoding pass + startTranscode( 2 ); + } + else { + emit percent( 100 ); + cleanup( true ); + jobFinished( true ); + } + break; + + default: + // FIXME: error handling + + emit infoMessage( i18n("%1 returned an unknown error (code %2).", + d->usedTranscodeBin->name(), exitCode ), + K3b::Job::ERROR ); + emit infoMessage( i18n("Please send me an email with the last output."), K3b::Job::ERROR ); + + cleanup( false ); + jobFinished( false ); + } + } + else { + cleanup( false ); + emit infoMessage( i18n("Execution of %1 failed.",QString("transcode")), ERROR ); + emit infoMessage( i18n("Please consult the debugging output for details."), ERROR ); + jobFinished( false ); } - } - else { - cleanup( false ); - emit infoMessage( i18n("Execution of %1 failed.",QString("transcode")), ERROR ); - emit infoMessage( i18n("Please consult the debugging output for details."), ERROR ); - jobFinished( false ); - } } void K3b::VideoDVDTitleTranscodingJob::setClipping( int top, int left, int bottom, int right ) { - m_clippingTop = top; - m_clippingLeft = left; - m_clippingBottom = bottom; - m_clippingRight = right; - - // - // transcode seems unable to handle different clipping values for left and right - // - m_clippingLeft = m_clippingRight = qMin( m_clippingRight, m_clippingLeft ); + m_clippingTop = top; + m_clippingLeft = left; + m_clippingBottom = bottom; + m_clippingRight = right; + + // + // transcode seems unable to handle different clipping values for left and right + // + m_clippingLeft = m_clippingRight = qMin( m_clippingRight, m_clippingLeft ); } void K3b::VideoDVDTitleTranscodingJob::setSize( int width, int height ) { - m_width = width; - m_height = height; + m_width = width; + m_height = height; } QString K3b::VideoDVDTitleTranscodingJob::audioCodecString( K3b::VideoDVDTitleTranscodingJob::AudioCodec codec ) { - switch( codec ) { - case AUDIO_CODEC_AC3_STEREO: - return i18n("AC3 (Stereo)"); - case AUDIO_CODEC_AC3_PASSTHROUGH: - return i18n("AC3 (Pass-through)"); - case AUDIO_CODEC_MP3: - return i18n("MPEG1 Layer III"); - default: - return "unknown audio codec"; - } + switch( codec ) { + case AUDIO_CODEC_AC3_STEREO: + return i18n("AC3 (Stereo)"); + case AUDIO_CODEC_AC3_PASSTHROUGH: + return i18n("AC3 (Pass-through)"); + case AUDIO_CODEC_MP3: + return i18n("MPEG1 Layer III"); + default: + return "unknown audio codec"; + } } QString K3b::VideoDVDTitleTranscodingJob::videoCodecString( K3b::VideoDVDTitleTranscodingJob::VideoCodec codec ) { - switch( codec ) { - case VIDEO_CODEC_FFMPEG_MPEG4: - return i18n("MPEG4 (FFMPEG)"); - case VIDEO_CODEC_XVID: - return i18n("XviD"); - default: - return "unknown video codec"; - } + switch( codec ) { + case VIDEO_CODEC_FFMPEG_MPEG4: + return i18n("MPEG4 (FFMPEG)"); + case VIDEO_CODEC_XVID: + return i18n("XviD"); + default: + return "unknown video codec"; + } } QString K3b::VideoDVDTitleTranscodingJob::videoCodecDescription( K3b::VideoDVDTitleTranscodingJob::VideoCodec codec ) { - switch( codec ) { - case VIDEO_CODEC_FFMPEG_MPEG4: - return i18n("FFmpeg is an open-source project trying to support most video and audio codecs used " - "these days. Its subproject libavcodec forms the basis for multimedia players such as " - "xine or mplayer.") - + "
" - + i18n("FFmpeg contains an implementation of the MPEG-4 video encoding standard which produces " - "high quality results."); - case VIDEO_CODEC_XVID: - return i18n("XviD is a free and open source MPEG-4 video codec. XviD was created by a group of " - "volunteer programmers after the OpenDivX source was closed in July 2001.") - + "
" - + i18n("XviD features MPEG-4 Advanced Profile settings such as b-frames, global " - "and quarter pixel motion compensation, lumi masking, trellis quantization, and " - "H.263, MPEG and custom quantization matrices.") - + "
" - + i18n("XviD is a primary competitor of DivX (XviD being DivX spelled backwards). " - "While DivX is closed source and may only run on Windows, Mac OS and Linux, " - "XviD is open source and can potentially run on any platform.") - + "
" - + i18n("(Description taken from the Wikipedia article)") - + ""; - default: - return "unknown video codec"; - } + switch( codec ) { + case VIDEO_CODEC_FFMPEG_MPEG4: + return i18n("FFmpeg is an open-source project trying to support most video and audio codecs used " + "these days. Its subproject libavcodec forms the basis for multimedia players such as " + "xine or mplayer.") + + "
" + + i18n("FFmpeg contains an implementation of the MPEG-4 video encoding standard which produces " + "high quality results."); + case VIDEO_CODEC_XVID: + return i18n("XviD is a free and open source MPEG-4 video codec. XviD was created by a group of " + "volunteer programmers after the OpenDivX source was closed in July 2001.") + + "
" + + i18n("XviD features MPEG-4 Advanced Profile settings such as b-frames, global " + "and quarter pixel motion compensation, lumi masking, trellis quantization, and " + "H.263, MPEG and custom quantization matrices.") + + "
" + + i18n("XviD is a primary competitor of DivX (XviD being DivX spelled backwards). " + "While DivX is closed source and may only run on Windows, Mac OS and Linux, " + "XviD is open source and can potentially run on any platform.") + + "
" + + i18n("(Description taken from the Wikipedia article)") + + ""; + default: + return "unknown video codec"; + } } QString K3b::VideoDVDTitleTranscodingJob::audioCodecDescription( K3b::VideoDVDTitleTranscodingJob::AudioCodec codec ) { - static QString s_ac3General = i18n("AC3, better known as Dolby Digital is standardized as ATSC A/52. " - "It contains up to 6 total channels of sound."); - switch( codec ) { - case AUDIO_CODEC_AC3_STEREO: - return s_ac3General - + "
" + i18n("With this setting K3b will create a two-channel stereo " - "Dolby Digital audio stream."); - case AUDIO_CODEC_AC3_PASSTHROUGH: - return s_ac3General - + "
" + i18n("With this setting K3b will use the Dolby Digital audio stream " - "from the source DVD without changing it.") - + "
" + i18n("Use this setting to preserve 5.1 channel sound from the DVD."); - case AUDIO_CODEC_MP3: - return i18n("MPEG1 Layer III is better known as MP3 and is the most used lossy audio format.") - + "
" + i18n("With this setting K3b will create a two-channel stereo MPEG1 Layer III audio stream."); - default: - return "unknown audio codec"; - } + static QString s_ac3General = i18n("AC3, better known as Dolby Digital is standardized as ATSC A/52. " + "It contains up to 6 total channels of sound."); + switch( codec ) { + case AUDIO_CODEC_AC3_STEREO: + return s_ac3General + + "
" + i18n("With this setting K3b will create a two-channel stereo " + "Dolby Digital audio stream."); + case AUDIO_CODEC_AC3_PASSTHROUGH: + return s_ac3General + + "
" + i18n("With this setting K3b will use the Dolby Digital audio stream " + "from the source DVD without changing it.") + + "
" + i18n("Use this setting to preserve 5.1 channel sound from the DVD."); + case AUDIO_CODEC_MP3: + return i18n("MPEG1 Layer III is better known as MP3 and is the most used lossy audio format.") + + "
" + i18n("With this setting K3b will create a two-channel stereo MPEG1 Layer III audio stream."); + default: + return "unknown audio codec"; + } } bool K3b::VideoDVDTitleTranscodingJob::transcodeBinaryHasSupportFor( K3b::VideoDVDTitleTranscodingJob::VideoCodec codec, const K3b::ExternalBin* bin ) { - static const char* s_codecFeatures[] = { "xvid", "ffmpeg" }; - if( !bin ) - bin = k3bcore->externalBinManager()->binObject("transcode"); - if( !bin ) - return false; - return bin->hasFeature( QString::fromLatin1( s_codecFeatures[(int)codec] ) ); + static const char* s_codecFeatures[] = { "xvid", "ffmpeg" }; + if( !bin ) + bin = k3bcore->externalBinManager()->binObject("transcode"); + if( !bin ) + return false; + return bin->hasFeature( QString::fromLatin1( s_codecFeatures[(int)codec] ) ); } bool K3b::VideoDVDTitleTranscodingJob::transcodeBinaryHasSupportFor( K3b::VideoDVDTitleTranscodingJob::AudioCodec codec, const K3b::ExternalBin* bin ) { - static const char* s_codecFeatures[] = { "lame", "ac3", "ac3" }; - if( !bin ) - bin = k3bcore->externalBinManager()->binObject("transcode"); - if( !bin ) - return false; - return bin->hasFeature( QString::fromLatin1( s_codecFeatures[(int)codec] ) ); + static const char* s_codecFeatures[] = { "lame", "ac3", "ac3" }; + if( !bin ) + bin = k3bcore->externalBinManager()->binObject("transcode"); + if( !bin ) + return false; + return bin->hasFeature( QString::fromLatin1( s_codecFeatures[(int)codec] ) ); } #include "k3bvideodvdtitletranscodingjob.moc" diff --git a/libk3b/projects/audiocd/k3baudiodoc.cpp b/libk3b/projects/audiocd/k3baudiodoc.cpp index e117570f4..1773a8c8d 100644 --- a/libk3b/projects/audiocd/k3baudiodoc.cpp +++ b/libk3b/projects/audiocd/k3baudiodoc.cpp @@ -1,1147 +1,1147 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * (C) 2009 Gustavo Pichorim Boiko * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 #include "k3baudiodoc.h" #include "k3baudiotrack.h" #include "k3baudiojob.h" #include "k3baudiofile.h" #include "k3baudiozerodata.h" #include "k3baudiocdtracksource.h" #include #include #include #include // QT-includes #include #include #include #include #include #include #include #include #include // KDE-includes #include #include #include #include #include #include #include #include #include #include class K3b::AudioDoc::Private { public: Private() { cdTextValidator = new K3b::CdTextValidator(); } ~Private() { delete cdTextValidator; } K3b::CdTextValidator* cdTextValidator; }; K3b::AudioDoc::AudioDoc( QObject* parent ) : K3b::Doc( parent ), m_firstTrack(0), m_lastTrack(0) { d = new Private; m_docType = AUDIO; } K3b::AudioDoc::~AudioDoc() { // delete all tracks int i = 1; int cnt = numOfTracks(); while( m_firstTrack ) { kDebug() << "(K3b::AudioDoc::AudioDoc) deleting track " << i << " of " << cnt; delete m_firstTrack->take(); kDebug() << "(K3b::AudioDoc::AudioDoc) deleted."; ++i; } delete d; } bool K3b::AudioDoc::newDocument() { clear(); m_normalize = false; m_hideFirstTrack = false; m_cdText = false; m_cdTextData.clear(); m_audioRippingParanoiaMode = 0; m_audioRippingRetries = 5; m_audioRippingIgnoreReadErrors = true; return K3b::Doc::newDocument(); } void K3b::AudioDoc::clear() { // delete all tracks while( m_firstTrack ) delete m_firstTrack->take(); } QString K3b::AudioDoc::name() const { if( !m_cdTextData.title().isEmpty() ) return m_cdTextData.title(); else return K3b::Doc::name(); } K3b::AudioTrack* K3b::AudioDoc::firstTrack() const { return m_firstTrack; } K3b::AudioTrack* K3b::AudioDoc::lastTrack() const { return m_lastTrack; } // this one is called by K3b::AudioTrack to update the list void K3b::AudioDoc::setFirstTrack( K3b::AudioTrack* track ) { m_firstTrack = track; } // this one is called by K3b::AudioTrack to update the list void K3b::AudioDoc::setLastTrack( K3b::AudioTrack* track ) { m_lastTrack = track; } KIO::filesize_t K3b::AudioDoc::size() const { // This is not really correct but what the user expects ;) return length().mode1Bytes(); } K3b::Msf K3b::AudioDoc::length() const { K3b::Msf length = 0; K3b::AudioTrack* track = m_firstTrack; while( track ) { length += track->length(); track = track->next(); } return length; } void K3b::AudioDoc::addUrls( const KUrl::List& urls ) { // make sure we add them at the end even if urls are in the queue addTracks( urls, 99 ); } void K3b::AudioDoc::addTracks( const KUrl::List& urls, int position ) { KUrl::List allUrls = extractUrlList( K3b::convertToLocalUrls(urls) ); KUrl::List::iterator end( allUrls.end()); for( KUrl::List::iterator it = allUrls.begin(); it != end; it++, position++ ) { KUrl& url = *it; if( url.path().right(3).toLower() == "cue" ) { // try adding a cue file if( K3b::AudioTrack* newAfter = importCueFile( url.path(), getTrack(position) ) ) { position = newAfter->trackNumber(); continue; } } if( K3b::AudioTrack* track = createTrack( url ) ) { addTrack( track, position ); K3b::AudioDecoder* dec = static_cast( track->firstSource() )->decoder(); track->setTitle( dec->metaInfo( K3b::AudioDecoder::META_TITLE ) ); track->setArtist( dec->metaInfo( K3b::AudioDecoder::META_ARTIST ) ); track->setSongwriter( dec->metaInfo( K3b::AudioDecoder::META_SONGWRITER ) ); track->setComposer( dec->metaInfo( K3b::AudioDecoder::META_COMPOSER ) ); track->setCdTextMessage( dec->metaInfo( K3b::AudioDecoder::META_COMMENT ) ); } } emit changed(); informAboutNotFoundFiles(); } KUrl::List K3b::AudioDoc::extractUrlList( const KUrl::List& urls ) { KUrl::List allUrls = urls; KUrl::List urlsFromPlaylist; KUrl::List::iterator it = allUrls.begin(); while( it != allUrls.end() ) { const KUrl& url = *it; QFileInfo fi( url.path() ); if( !url.isLocalFile() ) { kDebug() << url.path() << " no local file"; it = allUrls.erase( it ); m_notFoundFiles.append( url ); } else if( !fi.exists() ) { it = allUrls.erase( it ); kDebug() << url.path() << " not found"; m_notFoundFiles.append( url ); } else if( fi.isDir() ) { it = allUrls.erase( it ); // add all files in the dir QDir dir(fi.filePath()); QStringList entries = dir.entryList( QDir::Files ); KUrl::List::iterator oldIt = it; // add all files into the list after the current item for( QStringList::iterator dirIt = entries.begin(); dirIt != entries.end(); ++dirIt ) it = allUrls.insert( oldIt, KUrl( dir.absolutePath() + "/" + *dirIt ) ); } else if( readPlaylistFile( url, urlsFromPlaylist ) ) { it = allUrls.erase( it ); KUrl::List::iterator oldIt = it; // add all files into the list after the current item for( KUrl::List::iterator dirIt = urlsFromPlaylist.begin(); dirIt != urlsFromPlaylist.end(); ++dirIt ) it = allUrls.insert( oldIt, *dirIt ); } else ++it; } return allUrls; } bool K3b::AudioDoc::readPlaylistFile( const KUrl& url, KUrl::List& playlist ) { // check if the file is a m3u playlist // and if so add all listed files QFile f( url.path() ); if( !f.open( QIODevice::ReadOnly ) ) return false; QByteArray buf = f.read( 7 ); if( buf.size() != 7 || QString::fromLatin1( buf ) != "#EXTM3U" ) return false; f.seek( 0 ); QTextStream t( &f ); // skip the first line t.readLine(); // read the file while( !t.atEnd() ) { QString line = t.readLine(); if( line[0] != '#' ) { KUrl mp3url; // relative paths if( line[0] != '/' ) mp3url.setPath( url.directory(false) + line ); else mp3url.setPath( line ); playlist.append( mp3url ); } } return true; } void K3b::AudioDoc::addSources( K3b::AudioTrack* parent, const KUrl::List& urls, K3b::AudioDataSource* sourceAfter ) { kDebug() << "(K3b::AudioDoc::addSources( " << parent << ", " << urls.first().path() << ", " << sourceAfter << " )" << endl; KUrl::List allUrls = extractUrlList( urls ); KUrl::List::const_iterator end(allUrls.constEnd()); for( KUrl::List::const_iterator it = allUrls.constBegin(); it != end; ++it ) { if( K3b::AudioFile* file = createAudioFile( *it ) ) { if( sourceAfter ) file->moveAfter( sourceAfter ); else file->moveAhead( parent->firstSource() ); sourceAfter = file; } } informAboutNotFoundFiles(); kDebug() << "(K3b::AudioDoc::addSources) finished."; } K3b::AudioTrack* K3b::AudioDoc::importCueFile( const QString& cuefile, K3b::AudioTrack* after, K3b::AudioDecoder* decoder ) { if( !after ) after = m_lastTrack; kDebug() << "(K3b::AudioDoc::importCueFile( " << cuefile << ", " << after << ")"; K3b::CueFileParser parser( cuefile ); if( parser.isValid() && parser.toc().contentType() == K3b::Device::AUDIO ) { kDebug() << "(K3b::AudioDoc::importCueFile) parsed with image: " << parser.imageFilename(); // global cd-text if( !parser.cdText().title().isEmpty() ) setTitle( parser.cdText().title() ); if( !parser.cdText().performer().isEmpty() ) setPerformer( parser.cdText().performer() ); bool reused = true; if( !decoder ) decoder = getDecoderForUrl( KUrl(parser.imageFilename()), &reused ); if( decoder ) { if( !reused ) decoder->analyseFile(); K3b::AudioFile* newFile = 0; int i = 0; for( K3b::Device::Toc::const_iterator it = parser.toc().constBegin(); it != parser.toc().constEnd(); ++it ) { const K3b::Device::Track& track = *it; newFile = new K3b::AudioFile( decoder, this ); newFile->setStartOffset( track.firstSector() ); newFile->setEndOffset( track.lastSector()+1 ); K3b::AudioTrack* newTrack = new K3b::AudioTrack( this ); newTrack->addSource( newFile ); newTrack->moveAfter( after ); emit trackAdded(newTrack); // we do not know the length of the source yet so we have to force the index value if( track.index0() > 0 ) newTrack->m_index0Offset = track.length() - track.index0(); else newTrack->m_index0Offset = 0; // cd-text newTrack->setTitle( parser.cdText()[i].title() ); newTrack->setPerformer( parser.cdText()[i].performer() ); // add the next track after this one after = newTrack; ++i; } // let the last source use the data up to the end of the file if( newFile ) newFile->setEndOffset(0); return after; } } return 0; } K3b::AudioDecoder* K3b::AudioDoc::getDecoderForUrl( const KUrl& url, bool* reused ) { K3b::AudioDecoder* decoder = 0; // check if we already have a proper decoder if( m_decoderPresenceMap.contains( url.path() ) ) { decoder = m_decoderPresenceMap[url.path()]; *reused = true; } else if( (decoder = K3b::AudioDecoderFactory::createDecoder( url )) ) { kDebug() << "(K3b::AudioDoc) using " << decoder->metaObject()->className() << " for decoding of " << url.path() << endl; decoder->setFilename( url.path() ); *reused = false; } return decoder; } K3b::AudioFile* K3b::AudioDoc::createAudioFile( const KUrl& url ) { if( !QFile::exists( url.path() ) ) { m_notFoundFiles.append( url.path() ); kDebug() << "(K3b::AudioDoc) could not find file " << url.path(); return 0; } bool reused; K3b::AudioDecoder* decoder = getDecoderForUrl( url, &reused ); if( decoder ) { if( !reused ) decoder->analyseFile(); return new K3b::AudioFile( decoder, this ); } else { m_unknownFileFormatFiles.append( url.path() ); kDebug() << "(K3b::AudioDoc) unknown file type in file " << url.path(); return 0; } } K3b::AudioTrack* K3b::AudioDoc::createTrack( const KUrl& url ) { kDebug() << "(K3b::AudioDoc::createTrack( " << url.path() << " )"; if( K3b::AudioFile* file = createAudioFile( url ) ) { K3b::AudioTrack* newTrack = new K3b::AudioTrack( this ); newTrack->setFirstSource( file ); return newTrack; } else return 0; } void K3b::AudioDoc::addTrack( const KUrl& url, int position ) { addTracks( KUrl::List(url), position ); } K3b::AudioTrack* K3b::AudioDoc::getTrack( int trackNum ) { K3b::AudioTrack* track = m_firstTrack; int i = 1; while( track ) { if( i == trackNum ) return track; track = track->next(); ++i; } return 0; } void K3b::AudioDoc::addTrack( K3b::AudioTrack* track, int position ) { kDebug() << "(K3b::AudioDoc::addTrack( " << track << ", " << position << " )"; track->m_parent = this; if( !m_firstTrack ) m_firstTrack = m_lastTrack = track; else if( position == 0 ) track->moveAhead( m_firstTrack ); else { K3b::AudioTrack* after = getTrack( position ); if( after ) track->moveAfter( after ); else track->moveAfter( m_lastTrack ); // just to be sure it's anywhere... } emit trackAdded(track); emit changed(); } void K3b::AudioDoc::removeTrack( K3b::AudioTrack* track ) { delete track; } void K3b::AudioDoc::moveTrack( K3b::AudioTrack* track, K3b::AudioTrack* after ) { track->moveAfter( after ); } QString K3b::AudioDoc::typeString() const { return "audio"; } bool K3b::AudioDoc::loadDocumentData( QDomElement* root ) { newDocument(); // we will parse the dom-tree and create a K3b::AudioTrack for all entries immediately // this should not take long and so not block the gui QDomNodeList nodes = root->childNodes(); for( int i = 0; i < nodes.count(); i++ ) { QDomElement e = nodes.item(i).toElement(); if( e.isNull() ) return false; if( e.nodeName() == "general" ) { if( !readGeneralDocumentData( e ) ) return false; } else if( e.nodeName() == "normalize" ) setNormalize( e.text() == "yes" ); else if( e.nodeName() == "hide_first_track" ) setHideFirstTrack( e.text() == "yes" ); else if( e.nodeName() == "audio_ripping" ) { QDomNodeList ripNodes = e.childNodes(); for( uint j = 0; j < ripNodes.length(); j++ ) { if( ripNodes.item(j).nodeName() == "paranoia_mode" ) setAudioRippingParanoiaMode( ripNodes.item(j).toElement().text().toInt() ); else if( ripNodes.item(j).nodeName() == "read_retries" ) setAudioRippingRetries( ripNodes.item(j).toElement().text().toInt() ); else if( ripNodes.item(j).nodeName() == "ignore_read_errors" ) setAudioRippingIgnoreReadErrors( ripNodes.item(j).toElement().text() == "yes" ); } } // parse cd-text else if( e.nodeName() == "cd-text" ) { if( !e.hasAttribute( "activated" ) ) return false; writeCdText( e.attributeNode( "activated" ).value() == "yes" ); QDomNodeList cdTextNodes = e.childNodes(); for( uint j = 0; j < cdTextNodes.length(); j++ ) { if( cdTextNodes.item(j).nodeName() == "title" ) setTitle( cdTextNodes.item(j).toElement().text() ); else if( cdTextNodes.item(j).nodeName() == "artist" ) setArtist( cdTextNodes.item(j).toElement().text() ); else if( cdTextNodes.item(j).nodeName() == "arranger" ) setArranger( cdTextNodes.item(j).toElement().text() ); else if( cdTextNodes.item(j).nodeName() == "songwriter" ) setSongwriter( cdTextNodes.item(j).toElement().text() ); else if( cdTextNodes.item(j).nodeName() == "composer" ) setComposer( cdTextNodes.item(j).toElement().text() ); else if( cdTextNodes.item(j).nodeName() == "disc_id" ) setDisc_id( cdTextNodes.item(j).toElement().text() ); else if( cdTextNodes.item(j).nodeName() == "upc_ean" ) setUpc_ean( cdTextNodes.item(j).toElement().text() ); else if( cdTextNodes.item(j).nodeName() == "message" ) setCdTextMessage( cdTextNodes.item(j).toElement().text() ); } } else if( e.nodeName() == "contents" ) { QDomNodeList contentNodes = e.childNodes(); for( uint j = 0; j< contentNodes.length(); j++ ) { QDomElement trackElem = contentNodes.item(j).toElement(); // first of all we need a track K3b::AudioTrack* track = new K3b::AudioTrack(); // backwards compatibility // ----------------------------------------------------------------------------------------------------- QDomAttr oldUrlAttr = trackElem.attributeNode( "url" ); if( !oldUrlAttr.isNull() ) { if( K3b::AudioFile* file = createAudioFile( KUrl( oldUrlAttr.value() ) ) ) { track->addSource( file ); } } // ----------------------------------------------------------------------------------------------------- QDomNodeList trackNodes = trackElem.childNodes(); for( uint trackJ = 0; trackJ < trackNodes.length(); trackJ++ ) { if( trackNodes.item(trackJ).nodeName() == "sources" ) { QDomNodeList sourcesNodes = trackNodes.item(trackJ).childNodes(); for( unsigned int sourcesIndex = 0; sourcesIndex < sourcesNodes.length(); sourcesIndex++ ) { QDomElement sourceElem = sourcesNodes.item(sourcesIndex).toElement(); if( sourceElem.nodeName() == "file" ) { if( K3b::AudioFile* file = createAudioFile( KUrl( sourceElem.attributeNode( "url" ).value() ) ) ) { file->setStartOffset( K3b::Msf::fromString( sourceElem.attributeNode( "start_offset" ).value() ) ); file->setEndOffset( K3b::Msf::fromString( sourceElem.attributeNode( "end_offset" ).value() ) ); track->addSource( file ); } } else if( sourceElem.nodeName() == "silence" ) { K3b::AudioZeroData* zero = new K3b::AudioZeroData(); zero->setLength( K3b::Msf::fromString( sourceElem.attributeNode( "length" ).value() ) ); track->addSource( zero ); } else if( sourceElem.nodeName() == "cdtrack" ) { K3b::Msf length = K3b::Msf::fromString( sourceElem.attributeNode( "length" ).value() ); int titlenum = 0; int discid = 0; QString title, artist, cdTitle, cdArtist; QDomNodeList cdTrackSourceNodes = sourceElem.childNodes(); for( unsigned int cdTrackSourceIndex = 0; cdTrackSourceIndex < cdTrackSourceNodes.length(); ++cdTrackSourceIndex ) { QDomElement cdTrackSourceItemElem = cdTrackSourceNodes.item(cdTrackSourceIndex).toElement(); if( cdTrackSourceItemElem.nodeName() == "title_number" ) titlenum = cdTrackSourceItemElem.text().toInt(); else if( cdTrackSourceItemElem.nodeName() == "disc_id" ) discid = cdTrackSourceItemElem.text().toUInt( 0, 16 ); else if( cdTrackSourceItemElem.nodeName() == "title" ) title = cdTrackSourceItemElem.text().toInt(); else if( cdTrackSourceItemElem.nodeName() == "artist" ) artist = cdTrackSourceItemElem.text().toInt(); else if( cdTrackSourceItemElem.nodeName() == "cdtitle" ) cdTitle = cdTrackSourceItemElem.text().toInt(); else if( cdTrackSourceItemElem.nodeName() == "cdartist" ) cdArtist = cdTrackSourceItemElem.text().toInt(); } if( discid != 0 && titlenum > 0 ) { K3b::AudioCdTrackSource* cdtrack = new K3b::AudioCdTrackSource( discid, length, titlenum, artist, title, cdArtist, cdTitle ); cdtrack->setStartOffset( K3b::Msf::fromString( sourceElem.attributeNode( "start_offset" ).value() ) ); cdtrack->setEndOffset( K3b::Msf::fromString( sourceElem.attributeNode( "end_offset" ).value() ) ); track->addSource( cdtrack ); } else { kDebug() << "(K3b::AudioDoc) invalid cdtrack source."; return false; } } else { kDebug() << "(K3b::AudioDoc) unknown source type: " << sourceElem.nodeName(); return false; } } } // load cd-text else if( trackNodes.item(trackJ).nodeName() == "cd-text" ) { QDomNodeList cdTextNodes = trackNodes.item(trackJ).childNodes(); for( uint trackCdTextJ = 0; trackCdTextJ < cdTextNodes.length(); trackCdTextJ++ ) { if( cdTextNodes.item(trackCdTextJ).nodeName() == "title" ) track->setTitle( cdTextNodes.item(trackCdTextJ).toElement().text() ); else if( cdTextNodes.item(trackCdTextJ).nodeName() == "artist" ) track->setArtist( cdTextNodes.item(trackCdTextJ).toElement().text() ); else if( cdTextNodes.item(trackCdTextJ).nodeName() == "arranger" ) track->setArranger( cdTextNodes.item(trackCdTextJ).toElement().text() ); else if( cdTextNodes.item(trackCdTextJ).nodeName() == "songwriter" ) track->setSongwriter( cdTextNodes.item(trackCdTextJ).toElement().text() ); else if( cdTextNodes.item(trackCdTextJ).nodeName() == "composer" ) track->setComposer( cdTextNodes.item(trackCdTextJ).toElement().text() ); else if( cdTextNodes.item(trackCdTextJ).nodeName() == "isrc" ) track->setIsrc( cdTextNodes.item(trackCdTextJ).toElement().text() ); else if( cdTextNodes.item(trackCdTextJ).nodeName() == "message" ) track->setCdTextMessage( cdTextNodes.item(trackCdTextJ).toElement().text() ); } } else if( trackNodes.item(trackJ).nodeName() == "index0" ) track->setIndex0( K3b::Msf::fromString( trackNodes.item(trackJ).toElement().text() ) ); // TODO: load other indices // load options else if( trackNodes.item(trackJ).nodeName() == "copy_protection" ) track->setCopyProtection( trackNodes.item(trackJ).toElement().text() == "yes" ); else if( trackNodes.item(trackJ).nodeName() == "pre_emphasis" ) track->setPreEmp( trackNodes.item(trackJ).toElement().text() == "yes" ); } // add the track if( track->numberSources() > 0 ) addTrack( track, 99 ); // append to the end // TODO improve else { kDebug() << "(K3b::AudioDoc) no sources. deleting track " << track; delete track; } } } } informAboutNotFoundFiles(); setModified(false); return true; } bool K3b::AudioDoc::saveDocumentData( QDomElement* docElem ) { QDomDocument doc = docElem->ownerDocument(); saveGeneralDocumentData( docElem ); // add normalize QDomElement normalizeElem = doc.createElement( "normalize" ); normalizeElem.appendChild( doc.createTextNode( normalize() ? "yes" : "no" ) ); docElem->appendChild( normalizeElem ); // add hide track QDomElement hideFirstTrackElem = doc.createElement( "hide_first_track" ); hideFirstTrackElem.appendChild( doc.createTextNode( hideFirstTrack() ? "yes" : "no" ) ); docElem->appendChild( hideFirstTrackElem ); // save the audio cd ripping settings // paranoia mode, read retries, and ignore read errors // ------------------------------------------------------------ QDomElement ripMain = doc.createElement( "audio_ripping" ); docElem->appendChild( ripMain ); QDomElement ripElem = doc.createElement( "paranoia_mode" ); ripElem.appendChild( doc.createTextNode( QString::number( audioRippingParanoiaMode() ) ) ); ripMain.appendChild( ripElem ); ripElem = doc.createElement( "read_retries" ); ripElem.appendChild( doc.createTextNode( QString::number( audioRippingRetries() ) ) ); ripMain.appendChild( ripElem ); ripElem = doc.createElement( "ignore_read_errors" ); ripElem.appendChild( doc.createTextNode( audioRippingIgnoreReadErrors() ? "yes" : "no" ) ); ripMain.appendChild( ripElem ); // ------------------------------------------------------------ // save disc cd-text // ------------------------------------------------------------- QDomElement cdTextMain = doc.createElement( "cd-text" ); cdTextMain.setAttribute( "activated", cdText() ? "yes" : "no" ); QDomElement cdTextElem = doc.createElement( "title" ); cdTextElem.appendChild( doc.createTextNode( (title())) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "artist" ); cdTextElem.appendChild( doc.createTextNode( (artist())) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "arranger" ); cdTextElem.appendChild( doc.createTextNode( (arranger())) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "songwriter" ); cdTextElem.appendChild( doc.createTextNode( (songwriter())) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "composer" ); cdTextElem.appendChild( doc.createTextNode( composer()) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "disc_id" ); cdTextElem.appendChild( doc.createTextNode( (disc_id())) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "upc_ean" ); cdTextElem.appendChild( doc.createTextNode( (upc_ean())) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "message" ); cdTextElem.appendChild( doc.createTextNode( (cdTextMessage())) ); cdTextMain.appendChild( cdTextElem ); docElem->appendChild( cdTextMain ); // ------------------------------------------------------------- // save the tracks // ------------------------------------------------------------- QDomElement contentsElem = doc.createElement( "contents" ); for( K3b::AudioTrack* track = firstTrack(); track != 0; track = track->next() ) { QDomElement trackElem = doc.createElement( "track" ); // add sources QDomElement sourcesParent = doc.createElement( "sources" ); for( K3b::AudioDataSource* source = track->firstSource(); source; source = source->next() ) { // TODO: save a source element with a type attribute and start- and endoffset // then distict between the different source types. if( K3b::AudioFile* file = dynamic_cast(source) ) { QDomElement sourceElem = doc.createElement( "file" ); sourceElem.setAttribute( "url", file->filename() ); sourceElem.setAttribute( "start_offset", file->startOffset().toString() ); sourceElem.setAttribute( "end_offset", file->endOffset().toString() ); sourcesParent.appendChild( sourceElem ); } else if( K3b::AudioZeroData* zero = dynamic_cast(source) ) { QDomElement sourceElem = doc.createElement( "silence" ); sourceElem.setAttribute( "length", zero->length().toString() ); sourcesParent.appendChild( sourceElem ); } else if( K3b::AudioCdTrackSource* cdTrack = dynamic_cast(source) ) { QDomElement sourceElem = doc.createElement( "cdtrack" ); sourceElem.setAttribute( "length", cdTrack->originalLength().toString() ); sourceElem.setAttribute( "start_offset", cdTrack->startOffset().toString() ); sourceElem.setAttribute( "end_offset", cdTrack->endOffset().toString() ); QDomElement subElem = doc.createElement( "title_number" ); subElem.appendChild( doc.createTextNode( QString::number(cdTrack->cdTrackNumber()) ) ); sourceElem.appendChild( subElem ); subElem = doc.createElement( "disc_id" ); subElem.appendChild( doc.createTextNode( QString::number(cdTrack->discId(), 16) ) ); sourceElem.appendChild( subElem ); subElem = doc.createElement( "title" ); subElem.appendChild( doc.createTextNode( cdTrack->title() ) ); sourceElem.appendChild( subElem ); subElem = doc.createElement( "artist" ); subElem.appendChild( doc.createTextNode( cdTrack->artist() ) ); sourceElem.appendChild( subElem ); subElem = doc.createElement( "cdtitle" ); subElem.appendChild( doc.createTextNode( cdTrack->cdTitle() ) ); sourceElem.appendChild( subElem ); subElem = doc.createElement( "cdartist" ); subElem.appendChild( doc.createTextNode( cdTrack->cdArtist() ) ); sourceElem.appendChild( subElem ); sourcesParent.appendChild( sourceElem ); } else { kError() << "(K3b::AudioDoc) saving sources other than file or zero not supported yet." << endl; return false; } } trackElem.appendChild( sourcesParent ); // index 0 QDomElement index0Elem = doc.createElement( "index0" ); index0Elem.appendChild( doc.createTextNode( track->index0().toString() ) ); trackElem.appendChild( index0Elem ); // TODO: other indices // add cd-text cdTextMain = doc.createElement( "cd-text" ); cdTextElem = doc.createElement( "title" ); cdTextElem.appendChild( doc.createTextNode( (track->title())) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "artist" ); cdTextElem.appendChild( doc.createTextNode( (track->artist())) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "arranger" ); cdTextElem.appendChild( doc.createTextNode( (track->arranger()) ) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "songwriter" ); cdTextElem.appendChild( doc.createTextNode( (track->songwriter()) ) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "composer" ); cdTextElem.appendChild( doc.createTextNode( (track->composer()) ) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "isrc" ); cdTextElem.appendChild( doc.createTextNode( ( track->isrc()) ) ); cdTextMain.appendChild( cdTextElem ); cdTextElem = doc.createElement( "message" ); cdTextElem.appendChild( doc.createTextNode( (track->cdTextMessage()) ) ); cdTextMain.appendChild( cdTextElem ); trackElem.appendChild( cdTextMain ); // add copy protection QDomElement copyElem = doc.createElement( "copy_protection" ); copyElem.appendChild( doc.createTextNode( track->copyProtection() ? "yes" : "no" ) ); trackElem.appendChild( copyElem ); // add pre emphasis copyElem = doc.createElement( "pre_emphasis" ); copyElem.appendChild( doc.createTextNode( track->preEmp() ? "yes" : "no" ) ); trackElem.appendChild( copyElem ); contentsElem.appendChild( trackElem ); } // ------------------------------------------------------------- docElem->appendChild( contentsElem ); return true; } int K3b::AudioDoc::numOfTracks() const { return ( m_lastTrack ? m_lastTrack->trackNumber() : 0 ); } K3b::BurnJob* K3b::AudioDoc::newBurnJob( K3b::JobHandler* hdl, QObject* parent ) { return new K3b::AudioJob( this, hdl, parent ); } void K3b::AudioDoc::informAboutNotFoundFiles() { if( !m_notFoundFiles.isEmpty() ) { QStringList l; for( KUrl::List::const_iterator it = m_notFoundFiles.constBegin(); it != m_notFoundFiles.constEnd(); ++it ) l.append( (*it).path() ); KMessageBox::informationList( qApp->activeWindow(), i18n("Could not find the following files:"), l, i18n("Not Found") ); m_notFoundFiles.clear(); } if( !m_unknownFileFormatFiles.isEmpty() ) { QStringList l; for( KUrl::List::const_iterator it = m_unknownFileFormatFiles.constBegin(); it != m_unknownFileFormatFiles.constEnd(); ++it ) l.append( (*it).path() ); KMessageBox::informationList( qApp->activeWindow(), i18n("

Unable to handle the following files due to an unsupported format:" "

You may manually convert these audio files to wave using another " "application supporting the audio format and then add the wave files " "to the K3b project."), l, i18n("Unsupported Format") ); m_unknownFileFormatFiles.clear(); } } void K3b::AudioDoc::removeCorruptTracks() { // K3b::AudioTrack* track = m_tracks->first(); // while( track ) { // if( track->status() != 0 ) { // removeTrack(track); // track = m_tracks->current(); // } // else // track = m_tracks->next(); // } } void K3b::AudioDoc::slotTrackChanged( K3b::AudioTrack* track ) { kDebug() << "(K3b::AudioDoc::slotTrackChanged " << track; setModified( true ); // if the track is empty now we simply delete it if( track->firstSource() ) { emit trackChanged(track); emit changed(); } else { kDebug() << "(K3b::AudioDoc::slotTrackChanged) track " << track << " empty. Deleting."; delete track; // this will emit the proper signal } kDebug() << "(K3b::AudioDoc::slotTrackChanged done"; } void K3b::AudioDoc::slotTrackRemoved( K3b::AudioTrack* track ) { setModified( true ); emit trackRemoved(track); emit changed(); } void K3b::AudioDoc::slotAboutToRemoveTrack( K3b::AudioTrack* track ) { int index = track->trackNumber() - 1; if ( index >= 0 ) emit aboutToRemoveTrack( index ); } void K3b::AudioDoc::increaseDecoderUsage( K3b::AudioDecoder* decoder ) { kDebug() << "(K3b::AudioDoc::increaseDecoderUsage)"; if( !m_decoderUsageCounterMap.contains( decoder ) ) { m_decoderUsageCounterMap[decoder] = 1; m_decoderPresenceMap[decoder->filename()] = decoder; } else m_decoderUsageCounterMap[decoder]++; kDebug() << "(K3b::AudioDoc::increaseDecoderUsage) finished"; } void K3b::AudioDoc::decreaseDecoderUsage( K3b::AudioDecoder* decoder ) { m_decoderUsageCounterMap[decoder]--; if( m_decoderUsageCounterMap[decoder] <= 0 ) { m_decoderUsageCounterMap.remove(decoder); m_decoderPresenceMap.remove(decoder->filename()); delete decoder; } } K3b::Device::CdText K3b::AudioDoc::cdTextData() const { K3b::Device::CdText text( m_cdTextData ); K3b::AudioTrack* track = firstTrack(); int i = 0; while( track ) { text.track( i++ ) = track->cdText(); track = track->next(); } return text; } K3b::Device::Toc K3b::AudioDoc::toToc() const { K3b::Device::Toc toc; // FIXME: add MCN K3b::AudioTrack* track = firstTrack(); K3b::Msf pos = 0; while( track ) { toc.append( track->toCdTrack() ); track = track->next(); } return toc; } void K3b::AudioDoc::setTitle( const QString& v ) { m_cdTextData.setTitle( v ); emit changed(); } void K3b::AudioDoc::setArtist( const QString& v ) { setPerformer( v ); } void K3b::AudioDoc::setPerformer( const QString& v ) { QString s( v ); d->cdTextValidator->fixup( s ); m_cdTextData.setPerformer( s ); emit changed(); } void K3b::AudioDoc::setDisc_id( const QString& v ) { QString s( v ); d->cdTextValidator->fixup( s ); m_cdTextData.setDiscId( s ); emit changed(); } void K3b::AudioDoc::setArranger( const QString& v ) { QString s( v ); d->cdTextValidator->fixup( s ); m_cdTextData.setArranger( s ); emit changed(); } void K3b::AudioDoc::setSongwriter( const QString& v ) { QString s( v ); d->cdTextValidator->fixup( s ); m_cdTextData.setSongwriter( s ); emit changed(); } void K3b::AudioDoc::setComposer( const QString& v ) { QString s( v ); d->cdTextValidator->fixup( s ); m_cdTextData.setComposer( s ); emit changed(); } void K3b::AudioDoc::setUpc_ean( const QString& v ) { QString s( v ); d->cdTextValidator->fixup( s ); m_cdTextData.setUpcEan( s ); emit changed(); } void K3b::AudioDoc::setCdTextMessage( const QString& v ) { QString s( v ); d->cdTextValidator->fixup( s ); m_cdTextData.setMessage( s ); emit changed(); } -int K3b::AudioDoc::supportedMediaTypes() const +K3b::Device::MediaTypes K3b::AudioDoc::supportedMediaTypes() const { return K3b::Device::MEDIA_WRITABLE_CD; } #include "k3baudiodoc.moc" diff --git a/libk3b/projects/audiocd/k3baudiodoc.h b/libk3b/projects/audiocd/k3baudiodoc.h index 8ff8735fc..fd8bbb1a5 100644 --- a/libk3b/projects/audiocd/k3baudiodoc.h +++ b/libk3b/projects/audiocd/k3baudiodoc.h @@ -1,265 +1,265 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * (C) 2009 Gustavo Pichorim Boiko * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3BAUDIODOC_H #define K3BAUDIODOC_H #include #include #include #include #include #include #include #include "k3b_export.h" #include class QDomElement; namespace K3b { class AudioTrack; class AudioDataSource; class AudioDecoder; class AudioFile; /** * Document class for an audio project. * @author Sebastian Trueg */ class LIBK3B_EXPORT AudioDoc : public Doc { Q_OBJECT friend class MixedDoc; friend class AudioTrack; friend class AudioFile; public: AudioDoc( QObject* ); ~AudioDoc(); QString name() const; bool newDocument(); void clear(); - int supportedMediaTypes() const; + Device::MediaTypes supportedMediaTypes() const; bool hideFirstTrack() const { return m_hideFirstTrack; } int numOfTracks() const; bool normalize() const { return m_normalize; } AudioTrack* firstTrack() const; AudioTrack* lastTrack() const; /** * Slow. * \return the AudioTrack with track number trackNum (starting at 1) or 0 if trackNum > numOfTracks() */ AudioTrack* getTrack( int trackNum ); /** * Creates a new audiofile inside this doc which has no track yet. */ AudioFile* createAudioFile( const KUrl& url ); /** get the current size of the project */ KIO::filesize_t size() const; Msf length() const; // CD-Text bool cdText() const { return m_cdText; } QString title() const { return m_cdTextData.title(); } QString artist() const { return m_cdTextData.performer(); } QString disc_id() const { return m_cdTextData.discId(); } QString arranger() const { return m_cdTextData.arranger(); } QString songwriter() const { return m_cdTextData.songwriter(); } QString composer() const { return m_cdTextData.composer(); } QString upc_ean() const { return m_cdTextData.upcEan(); } QString cdTextMessage() const { return m_cdTextData.message(); } /** * Create complete CD-Text including the tracks' data. */ Device::CdText cdTextData() const; int audioRippingParanoiaMode() const { return m_audioRippingParanoiaMode; } int audioRippingRetries() const { return m_audioRippingRetries; } bool audioRippingIgnoreReadErrors() const { return m_audioRippingIgnoreReadErrors; } /** * Represent the structure of the doc as CD Table of Contents. */ Device::Toc toToc() const; BurnJob* newBurnJob( JobHandler*, QObject* parent = 0 ); /** * Shows dialogs. */ void informAboutNotFoundFiles(); /** * returns the new after track, ie. the the last added track or null if * the import failed. * * This is a blocking method. * * \param cuefile The Cuefile to be imported * \param after The track after which the new tracks should be inserted * \param decoder The decoder to be used for the new tracks. If 0 a new one will be created. * * BE AWARE THAT THE DECODER HAS TO FIT THE AUDIO FILE IN THE CUE. */ AudioTrack* importCueFile( const QString& cuefile, AudioTrack* after, AudioDecoder* decoder = 0 ); /** * Create a decoder for a specific url. If another AudioFileSource with this * url is already part of this project the associated decoder is returned. * * In the first case the decoder will not be initialized yet (AudioDecoder::analyseFile * is not called yet). * * \param url The url for which a decoder is requested. * \param reused If not null this variable is set to true if the decoder is already in * use and AudioDecoder::analyseFile() does not have to be called anymore. */ AudioDecoder* getDecoderForUrl( const KUrl& url, bool* reused = 0 ); static bool readPlaylistFile( const KUrl& url, KUrl::List& playlist ); public Q_SLOTS: void addUrls( const KUrl::List& ); void addTrack( const KUrl&, int ); void addTracks( const KUrl::List&, int ); /** * Adds a track without any testing * * Slow because it uses getTrack. */ void addTrack( AudioTrack* track, int position = 0 ); void addSources( AudioTrack* parent, const KUrl::List& urls, AudioDataSource* sourceAfter = 0 ); void removeTrack( AudioTrack* ); void moveTrack( AudioTrack* track, AudioTrack* after ); void setHideFirstTrack( bool b ) { m_hideFirstTrack = b; } void setNormalize( bool b ) { m_normalize = b; } // CD-Text void writeCdText( bool b ) { m_cdText = b; } void setTitle( const QString& v ); void setArtist( const QString& v ); void setPerformer( const QString& v ); void setDisc_id( const QString& v ); void setArranger( const QString& v ); void setSongwriter( const QString& v ); void setComposer( const QString& v ); void setUpc_ean( const QString& v ); void setCdTextMessage( const QString& v ); // Audio-CD Ripping void setAudioRippingParanoiaMode( int i ) { m_audioRippingParanoiaMode = i; } void setAudioRippingRetries( int r ) { m_audioRippingRetries = r; } void setAudioRippingIgnoreReadErrors( bool b ) { m_audioRippingIgnoreReadErrors = b; } void removeCorruptTracks(); private Q_SLOTS: void slotTrackChanged( K3b::AudioTrack* ); void slotTrackRemoved( K3b::AudioTrack* ); void slotAboutToRemoveTrack( K3b::AudioTrack* ); Q_SIGNALS: void trackAdded( K3b::AudioTrack* ); void trackChanged( K3b::AudioTrack* ); void trackRemoved( K3b::AudioTrack* ); // signal for the model void aboutToRemoveTrack( int position ); protected: /** reimplemented from Doc */ bool loadDocumentData( QDomElement* ); /** reimplemented from Doc */ bool saveDocumentData( QDomElement* ); QString typeString() const; private: // the stuff for adding files // --------------------------------------------------------- AudioTrack* createTrack( const KUrl& url ); /** * Handle directories and M3u files */ KUrl::List extractUrlList( const KUrl::List& urls ); // --------------------------------------------------------- /** * Used by AudioTrack to update the track list */ void setFirstTrack( AudioTrack* track ); /** * Used by AudioTrack to update the track list */ void setLastTrack( AudioTrack* track ); /** * Used by AudioFile to tell the doc that it does not need the decoder anymore. */ void decreaseDecoderUsage( AudioDecoder* ); void increaseDecoderUsage( AudioDecoder* ); AudioTrack* m_firstTrack; AudioTrack* m_lastTrack; bool m_hideFirstTrack; bool m_normalize; KUrl::List m_notFoundFiles; KUrl::List m_unknownFileFormatFiles; // CD-Text // -------------------------------------------------- Device::CdText m_cdTextData; bool m_cdText; // -------------------------------------------------- // Audio ripping int m_audioRippingParanoiaMode; int m_audioRippingRetries; bool m_audioRippingIgnoreReadErrors; // // decoder housekeeping // -------------------------------------------------- // used to check if we may delete a decoder QMap m_decoderUsageCounterMap; // used to check if we already have a decoder for a specific file QMap m_decoderPresenceMap; class Private; Private* d; }; } #endif diff --git a/libk3b/projects/audiocd/k3baudioimager.cpp b/libk3b/projects/audiocd/k3baudioimager.cpp index fb8e2ba27..1a592af81 100644 --- a/libk3b/projects/audiocd/k3baudioimager.cpp +++ b/libk3b/projects/audiocd/k3baudioimager.cpp @@ -1,167 +1,169 @@ /* * * Copyright (C) 2004-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3baudioimager.h" #include "k3baudiodoc.h" #include "k3baudiotrack.h" #include "k3baudiodatasource.h" #include #include #include #include #include +#include #include class K3b::AudioImager::Private { public: Private() - : fd(-1) { + : ioDev(0) { } - int fd; + QIODevice* ioDev; QStringList imageNames; K3b::AudioImager::ErrorType lastError; K3b::AudioDoc* doc; }; K3b::AudioImager::AudioImager( K3b::AudioDoc* doc, K3b::JobHandler* jh, QObject* parent ) : K3b::ThreadJob( jh, parent ), d( new Private() ) { d->doc = doc; } K3b::AudioImager::~AudioImager() { delete d; } -void K3b::AudioImager::writeToFd( int fd ) +void K3b::AudioImager::writeTo( QIODevice* dev ) { - d->fd = fd; + d->ioDev = dev; } void K3b::AudioImager::setImageFilenames( const QStringList& p ) { d->imageNames = p; - d->fd = -1; + d->ioDev = 0; } K3b::AudioImager::ErrorType K3b::AudioImager::lastErrorType() const { return d->lastError; } bool K3b::AudioImager::run() { d->lastError = K3b::AudioImager::ERROR_UNKNOWN; QStringList::iterator imageFileIt = d->imageNames.begin(); K3b::WaveFileWriter waveFileWriter; K3b::AudioTrack* track = d->doc->firstTrack(); int trackNumber = 1; unsigned long long totalSize = d->doc->length().audioBytes(); unsigned long long totalRead = 0; char buffer[2352 * 10]; while( track ) { emit nextTrack( trackNumber, d->doc->numOfTracks() ); // // Seek to the beginning of the track // if( !track->seek(0) ) { emit infoMessage( i18n("Unable to seek in track %1.", trackNumber), K3b::Job::ERROR ); return false; } // // Initialize the reading // int read = 0; unsigned long long trackRead = 0; // // Create the image file // - if( d->fd == -1 ) { + if( !d->ioDev ) { if( !waveFileWriter.open( *imageFileIt ) ) { emit infoMessage( i18n("Could not open %1 for writing", *imageFileIt), K3b::Job::ERROR ); return false; } } // // Read data from the track // while( (read = track->read( buffer, sizeof(buffer) )) > 0 ) { - if( d->fd == -1 ) { + if( !d->ioDev ) { waveFileWriter.write( buffer, read, K3b::WaveFileWriter::BigEndian ); } else { - if( ::write( d->fd, reinterpret_cast(buffer), read ) != read ) { - kDebug() << "(K3b::AudioImager::WorkThread) writing to fd " << d->fd << " failed."; + qint64 w = d->ioDev->write( buffer, read ); + if ( w != read ) { + kDebug() << "(K3b::AudioImager::WorkThread) writing to device" << d->ioDev << "failed:" << read << w; d->lastError = K3b::AudioImager::ERROR_FD_WRITE; return false; } } if( canceled() ) { return false; } // // Emit progress // totalRead += read; trackRead += read; emit subPercent( 100*trackRead/track->length().audioBytes() ); emit percent( 100*totalRead/totalSize ); emit processedSubSize( trackRead/1024/1024, track->length().audioBytes()/1024/1024 ); emit processedSize( totalRead/1024/1024, totalSize/1024/1024 ); } if( read < 0 ) { emit infoMessage( i18n("Error while decoding track %1.", trackNumber), K3b::Job::ERROR ); kDebug() << "(K3b::AudioImager::WorkThread) read error on track " << trackNumber << " at pos " << K3b::Msf(trackRead/2352) << endl; d->lastError = K3b::AudioImager::ERROR_DECODING_TRACK; return false; } track = track->next(); trackNumber++; imageFileIt++; } return true; } #include "k3baudioimager.moc" diff --git a/libk3b/projects/audiocd/k3baudioimager.h b/libk3b/projects/audiocd/k3baudioimager.h index 605fa219c..46e2c47e1 100644 --- a/libk3b/projects/audiocd/k3baudioimager.h +++ b/libk3b/projects/audiocd/k3baudioimager.h @@ -1,60 +1,62 @@ /* * * Copyright (C) 2004-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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. */ #ifndef _K3B_AUDIO_IMAGER_H_ #define _K3B_AUDIO_IMAGER_H_ #include +class QIODevice; + namespace K3b { class AudioDoc; class AudioImager : public ThreadJob { Q_OBJECT public: AudioImager( AudioDoc*, JobHandler*, QObject* parent = 0 ); ~AudioImager(); /** * the data gets written directly into fd instead of the imagefile. * Be aware that this only makes sense before starting the job. - * To disable just set fd to -1 + * To disable just set dev to 0 */ - void writeToFd( int fd ); + void writeTo( QIODevice* dev ); /** * Image path. Should be an empty directory or a non-existing * directory in which case it will be created. */ void setImageFilenames( const QStringList& p ); enum ErrorType { ERROR_FD_WRITE, ERROR_DECODING_TRACK, ERROR_UNKNOWN }; ErrorType lastErrorType() const; private: bool run(); class Private; Private* const d; }; } #endif diff --git a/libk3b/projects/audiocd/k3baudiojob.cpp b/libk3b/projects/audiocd/k3baudiojob.cpp index 4c099e56d..f73436eeb 100644 --- a/libk3b/projects/audiocd/k3baudiojob.cpp +++ b/libk3b/projects/audiocd/k3baudiojob.cpp @@ -1,869 +1,869 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3baudiojob.h" #include "k3baudioimager.h" #include #include "k3baudiotrack.h" #include "k3baudiodatasource.h" #include "k3baudionormalizejob.h" #include "k3baudiojobtempdata.h" #include "k3baudiomaxspeedjob.h" #include "k3baudiocdtracksource.h" #include "k3baudiofile.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QString createNonExistingFilesString( const QList& items, int max ) { QString s; int cnt = 0; for( QList::const_iterator it = items.begin(); it != items.end(); ++it ) { s += KStringHandler::csqueeze( (*it)->filename(), 60 ); ++cnt; if( cnt >= max || it == items.end() ) break; s += "
"; } if( items.count() > max ) s += "..."; return s; } class K3b::AudioJob::Private { public: Private() : copies(1), copiesDone(0) { } int copies; int copiesDone; int usedSpeed; bool useCdText; bool maxSpeed; bool zeroPregap; bool less4Sec; }; K3b::AudioJob::AudioJob( K3b::AudioDoc* doc, K3b::JobHandler* hdl, QObject* parent ) : K3b::BurnJob( hdl, parent ), m_doc( doc ), m_normalizeJob(0), m_maxSpeedJob(0) { d = new Private; m_audioImager = new K3b::AudioImager( m_doc, this, this ); connect( m_audioImager, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_audioImager, SIGNAL(percent(int)), this, SLOT(slotAudioDecoderPercent(int)) ); connect( m_audioImager, SIGNAL(subPercent(int)), this, SLOT(slotAudioDecoderSubPercent(int)) ); connect( m_audioImager, SIGNAL(finished(bool)), this, SLOT(slotAudioDecoderFinished(bool)) ); connect( m_audioImager, SIGNAL(nextTrack(int, int)), this, SLOT(slotAudioDecoderNextTrack(int, int)) ); m_writer = 0; m_tempData = new K3b::AudioJobTempData( m_doc, this ); } K3b::AudioJob::~AudioJob() { delete d; } K3b::Device::Device* K3b::AudioJob::writer() const { if( m_doc->onlyCreateImages() ) return 0; // no writer needed -> no blocking on K3b::BurnJob else return m_doc->burner(); } K3b::Doc* K3b::AudioJob::doc() const { return m_doc; } void K3b::AudioJob::start() { jobStarted(); m_written = true; m_canceled = false; m_errorOccuredAndAlreadyReported = false; d->copies = m_doc->copies(); d->copiesDone = 0; d->useCdText = m_doc->cdText(); d->usedSpeed = m_doc->speed(); d->maxSpeed = false; if( m_doc->dummy() ) d->copies = 1; emit newTask( i18n("Preparing data") ); // // Check if all files exist // QList nonExistingFiles; K3b::AudioTrack* track = m_doc->firstTrack(); while( track ) { K3b::AudioDataSource* source = track->firstSource(); while( source ) { if( K3b::AudioFile* file = dynamic_cast( source ) ) { if( !QFile::exists( file->filename() ) ) nonExistingFiles.append( file ); } source = source->next(); } track = track->next(); } if( !nonExistingFiles.isEmpty() ) { if( questionYesNo( "

" + i18n("The following files could not be found. Do you want to remove them from the " "project and continue without adding them to the image?") + "

" + createNonExistingFilesString( nonExistingFiles, 10 ), i18n("Warning"), i18n("Remove missing files and continue"), i18n("Cancel and go back") ) ) { for( QList::const_iterator it = nonExistingFiles.constBegin(); it != nonExistingFiles.constEnd(); ++it ) { delete *it; } } else { m_canceled = true; emit canceled(); jobFinished(false); return; } } // // Make sure the project is not empty // if( m_doc->numOfTracks() == 0 ) { emit infoMessage( i18n("Please add files to your project first."), ERROR ); jobFinished(false); return; } if( m_doc->onTheFly() && !checkAudioSources() ) { emit infoMessage( i18n("Unable to write on-the-fly with these audio sources."), WARNING ); m_doc->setOnTheFly(false); } // we don't need this when only creating image and it is possible // that the burn device is null if( !m_doc->onlyCreateImages() ) { // // there are a lot of writers out there which produce coasters // in dao mode if the CD contains pregaps of length 0 (or maybe already != 2 secs?) // // Also most writers do not accept cuesheets with tracks smaller than 4 seconds (a violation // of the red book standard) in DAO mode. // d->zeroPregap = false; d->less4Sec = false; track = m_doc->firstTrack(); while( track ) { if( track->postGap() == 0 && track->next() != 0 ) // the last track's postgap is always 0 d->zeroPregap = true; if( track->length() < K3b::Msf( 0, 4, 0 ) ) d->less4Sec = true; track = track->next(); } // determine writing mode if( m_doc->writingMode() == K3b::WRITING_MODE_AUTO ) { // // DAO is always the first choice // RAW second and TAO last // there are none-DAO writers that are supported by cdrdao // // older cdrecord versions do not support the -shorttrack option in RAW writing mode // if( !writer()->dao() && writingApp() == K3b::WRITING_APP_CDRECORD ) { if(!writer()->supportsRawWriting() && ( !d->less4Sec || k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "short-track-raw" ) ) ) m_usedWritingMode = K3b::WRITING_MODE_RAW; else m_usedWritingMode = K3b::WRITING_MODE_TAO; } else { if( (d->zeroPregap||d->less4Sec) && writer()->supportsRawWriting() ) { m_usedWritingMode = K3b::WRITING_MODE_RAW; if( d->less4Sec ) emit infoMessage( i18n("Tracklengths below 4 seconds violate the Red Book standard."), WARNING ); } else m_usedWritingMode = K3b::WRITING_MODE_DAO; } } else m_usedWritingMode = m_doc->writingMode(); bool cdrecordOnTheFly = false; bool cdrecordCdText = false; if( k3bcore->externalBinManager()->binObject("cdrecord") ) { cdrecordOnTheFly = k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "audio-stdin" ); cdrecordCdText = k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "cdtext" ); } // determine writing app if( writingApp() == K3b::WRITING_APP_DEFAULT ) { if( m_usedWritingMode == K3b::WRITING_MODE_DAO ) { // there are none-DAO writers that are supported by cdrdao if( !writer()->dao() || ( !cdrecordOnTheFly && m_doc->onTheFly() ) || ( d->useCdText && !cdrecordCdText ) || m_doc->hideFirstTrack() ) m_usedWritingApp = K3b::WRITING_APP_CDRDAO; else m_usedWritingApp = K3b::WRITING_APP_CDRECORD; } else m_usedWritingApp = K3b::WRITING_APP_CDRECORD; } else m_usedWritingApp = writingApp(); // on-the-fly writing with cdrecord >= 2.01a13 if( m_usedWritingApp == K3b::WRITING_APP_CDRECORD && m_doc->onTheFly() && !cdrecordOnTheFly ) { emit infoMessage( i18n("On-the-fly writing with cdrecord < 2.01a13 not supported."), ERROR ); m_doc->setOnTheFly(false); } if( m_usedWritingApp == K3b::WRITING_APP_CDRECORD && d->useCdText ) { if( !cdrecordCdText ) { emit infoMessage( i18n("Cdrecord %1 does not support CD-Text writing.", k3bcore->externalBinManager()->binObject("cdrecord")->version), ERROR ); d->useCdText = false; } else if( m_usedWritingMode == K3b::WRITING_MODE_TAO ) { emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode."), WARNING ); d->useCdText = false; } } } if( !m_doc->onlyCreateImages() && m_doc->onTheFly() ) { if( m_doc->speed() == 0 ) { // try to determine the max possible speed emit newSubTask( i18n("Determining maximum writing speed") ); if( !m_maxSpeedJob ) { m_maxSpeedJob = new K3b::AudioMaxSpeedJob( m_doc, this, this ); connect( m_maxSpeedJob, SIGNAL(percent(int)), this, SIGNAL(subPercent(int)) ); connect( m_maxSpeedJob, SIGNAL(finished(bool)), this, SLOT(slotMaxSpeedJobFinished(bool)) ); } m_maxSpeedJob->start(); return; } else { if( !prepareWriter() ) { cleanupAfterError(); jobFinished(false); return; } if( startWriting() ) { // now the writer is running and we can get it's stdin // we only use this method when writing on-the-fly since - // we cannot easily change the audioDecode fd while it's working + // we cannot easily change the audioDecode device while it's working // which we would need to do since we write into several // image files. - m_audioImager->writeToFd( m_writer->fd() ); + m_audioImager->writeTo( m_writer->ioDevice() ); } else { // startWriting() already did the cleanup return; } } } else { emit burning(false); emit infoMessage( i18n("Creating image files in %1", m_doc->tempDir()), INFO ); emit newTask( i18n("Creating image files") ); m_tempData->prepareTempFileNames( doc()->tempDir() ); QStringList filenames; for( int i = 1; i <= m_doc->numOfTracks(); ++i ) filenames += m_tempData->bufferFileName( i ); m_audioImager->setImageFilenames( filenames ); } m_audioImager->start(); } void K3b::AudioJob::slotMaxSpeedJobFinished( bool success ) { d->maxSpeed = success; if( !success ) emit infoMessage( i18n("Unable to determine maximum speed for some reason. Ignoring."), WARNING ); // now start the writing // same code as above. See the commecnts there if( !prepareWriter() ) { cleanupAfterError(); jobFinished(false); return; } if( startWriting() ) - m_audioImager->writeToFd( m_writer->fd() ); + m_audioImager->writeTo( m_writer->ioDevice() ); m_audioImager->start(); } void K3b::AudioJob::cancel() { m_canceled = true; if( m_maxSpeedJob ) m_maxSpeedJob->cancel(); if( m_writer ) m_writer->cancel(); m_audioImager->cancel(); emit infoMessage( i18n("Writing canceled."), K3b::Job::ERROR ); removeBufferFiles(); emit canceled(); jobFinished(false); } void K3b::AudioJob::slotWriterFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( !success ) { cleanupAfterError(); jobFinished(false); return; } else { d->copiesDone++; if( d->copiesDone == d->copies ) { if( m_doc->onTheFly() || m_doc->removeImages() ) removeBufferFiles(); if ( k3bcore->globalSettings()->ejectMedia() ) { K3b::Device::eject( m_doc->burner() ); } jobFinished(true); } else { if( !m_doc->burner()->eject() ) { blockingInformation( i18n("K3b was unable to eject the written disk. Please do so manually.") ); } if( startWriting() ) { if( m_doc->onTheFly() ) { // now the writer is running and we can get it's stdin // we only use this method when writing on-the-fly since // we cannot easily change the audioDecode fd while it's working // which we would need to do since we write into several // image files. - m_audioImager->writeToFd( m_writer->fd() ); + m_audioImager->writeTo( m_writer->ioDevice() ); m_audioImager->start(); } } } } } void K3b::AudioJob::slotAudioDecoderFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( !success ) { if( m_audioImager->lastErrorType() == K3b::AudioImager::ERROR_FD_WRITE ) { // this means that the writer job failed so let's use the error handling there. return; } emit infoMessage( i18n("Error while decoding audio tracks."), ERROR ); cleanupAfterError(); jobFinished(false); return; } if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) { emit infoMessage( i18n("Successfully decoded all tracks."), SUCCESS ); if( m_doc->normalize() ) { normalizeFiles(); } else if( !m_doc->onlyCreateImages() ) { if( !prepareWriter() ) { cleanupAfterError(); jobFinished(false); } else startWriting(); } else { jobFinished(true); } } } void K3b::AudioJob::slotAudioDecoderNextTrack( int t, int tt ) { if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) { K3b::AudioTrack* track = m_doc->getTrack(t); emit newSubTask( i18n("Decoding audio track %1 of %2%3", t, tt, track->title().isEmpty() || track->artist().isEmpty() ? QString() : " (" + track->artist() + " - " + track->title() + ")" ) ); } } bool K3b::AudioJob::prepareWriter() { delete m_writer; if( m_usedWritingApp == K3b::WRITING_APP_CDRECORD ) { if( !writeInfFiles() ) { kDebug() << "(K3b::AudioJob) could not write inf-files."; emit infoMessage( i18n("IO Error. Most likely no space left on harddisk."), ERROR ); return false; } K3b::CdrecordWriter* writer = new K3b::CdrecordWriter( m_doc->burner(), this, this ); writer->setWritingMode( m_usedWritingMode ); writer->setSimulate( m_doc->dummy() ); writer->setBurnSpeed( d->usedSpeed ); writer->addArgument( "-useinfo" ); if( d->useCdText ) { writer->setRawCdText( m_doc->cdTextData().rawPackData() ); } // add all the audio tracks writer->addArgument( "-audio" ); // we only need to pad in one case. cdrecord < 2.01.01a03 cannot handle shorttrack + raw if( d->less4Sec ) { if( m_usedWritingMode == K3b::WRITING_MODE_RAW && !k3bcore->externalBinManager()->binObject( "cdrecord" )->hasFeature( "short-track-raw" ) ) { writer->addArgument( "-pad" ); } else { // Allow tracks shorter than 4 seconds writer->addArgument( "-shorttrack" ); } } K3b::AudioTrack* track = m_doc->firstTrack(); while( track ) { if( m_doc->onTheFly() ) { // this is only supported by cdrecord versions >= 2.01a13 writer->addArgument( QFile::encodeName( m_tempData->infFileName( track ) ) ); } else { writer->addArgument( QFile::encodeName( m_tempData->bufferFileName( track ) ) ); } track = track->next(); } m_writer = writer; } else { if( !writeTocFile() ) { kDebug() << "(K3b::DataJob) could not write tocfile."; emit infoMessage( i18n("IO Error"), ERROR ); return false; } // create the writer // create cdrdao job K3b::CdrdaoWriter* writer = new K3b::CdrdaoWriter( m_doc->burner(), this, this ); writer->setCommand( K3b::CdrdaoWriter::WRITE ); writer->setSimulate( m_doc->dummy() ); writer->setBurnSpeed( d->usedSpeed ); writer->setTocFile( m_tempData->tocFileName() ); m_writer = writer; } connect( m_writer, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_writer, SIGNAL(percent(int)), this, SLOT(slotWriterJobPercent(int)) ); connect( m_writer, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); connect( m_writer, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) ); connect( m_writer, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); connect( m_writer, SIGNAL(nextTrack(int, int)), this, SLOT(slotWriterNextTrack(int, int)) ); connect( m_writer, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); connect( m_writer, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); connect( m_writer, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)), this, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)) ); connect( m_writer, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) ); // connect( m_writer, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); connect( m_writer, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( m_writer, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); return true; } void K3b::AudioJob::slotWriterNextTrack( int t, int tt ) { K3b::AudioTrack* track = m_doc->getTrack(t); // t is in range 1..tt if( m_doc->hideFirstTrack() ) track = m_doc->getTrack(t+1); emit newSubTask( i18n("Writing track %1 of %2%3", t, tt, track->title().isEmpty() || track->artist().isEmpty() ? QString() : " (" + track->artist() + " - " + track->title() + ")" ) ); } void K3b::AudioJob::slotWriterJobPercent( int p ) { double totalTasks = d->copies; double tasksDone = d->copiesDone; if( m_doc->normalize() ) { totalTasks+=1.0; tasksDone+=1.0; } if( !m_doc->onTheFly() ) { totalTasks+=1.0; tasksDone+=1.0; } emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } void K3b::AudioJob::slotAudioDecoderPercent( int p ) { if( m_doc->onlyCreateImages() ) { if( m_doc->normalize() ) emit percent( p/2 ); else emit percent( p ); } else if( !m_doc->onTheFly() ) { double totalTasks = d->copies; double tasksDone = d->copiesDone; // =0 when creating an image if( m_doc->normalize() ) { totalTasks+=1.0; } if( !m_doc->onTheFly() ) { totalTasks+=1.0; } emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } } void K3b::AudioJob::slotAudioDecoderSubPercent( int p ) { // when writing on the fly the writer produces the subPercent if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) { emit subPercent( p ); } } bool K3b::AudioJob::startWriting() { if( m_doc->dummy() ) emit newTask( i18n("Simulating") ); else if( d->copies > 1 ) emit newTask( i18n("Writing Copy %1", d->copiesDone+1) ); else emit newTask( i18n("Writing") ); emit newSubTask( i18n("Waiting for media") ); if( waitForMedia( m_doc->burner() ) < 0 ) { cancel(); return false; } // just to be sure we did not get canceled during the async discWaiting if( m_canceled ) return false; // in case we determined the max possible writing speed we have to reset the speed on the writer job // here since an inserted media is necessary // the Max speed job will compare the max speed value with the supported values of the writer if( d->maxSpeed ) m_writer->setBurnSpeed( m_maxSpeedJob->maxSpeed() ); emit burning(true); m_writer->start(); return true; } void K3b::AudioJob::cleanupAfterError() { m_errorOccuredAndAlreadyReported = true; m_audioImager->cancel(); if( m_writer ) m_writer->cancel(); // remove the temp files removeBufferFiles(); } void K3b::AudioJob::removeBufferFiles() { if ( !m_doc->onTheFly() ) { emit infoMessage( i18n("Removing temporary files."), INFO ); } // removes buffer images and temp toc or inf files m_tempData->cleanup(); } void K3b::AudioJob::normalizeFiles() { if( !m_normalizeJob ) { m_normalizeJob = new K3b::AudioNormalizeJob( this, this ); connect( m_normalizeJob, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_normalizeJob, SIGNAL(percent(int)), this, SLOT(slotNormalizeProgress(int)) ); connect( m_normalizeJob, SIGNAL(subPercent(int)), this, SLOT(slotNormalizeSubProgress(int)) ); connect( m_normalizeJob, SIGNAL(finished(bool)), this, SLOT(slotNormalizeJobFinished(bool)) ); connect( m_normalizeJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( m_normalizeJob, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } // add all the files // TODO: we may need to split the wave files and put them back together! QList files; K3b::AudioTrack* track = m_doc->firstTrack(); while( track ) { files.append( m_tempData->bufferFileName(track) ); track = track->next(); } m_normalizeJob->setFilesToNormalize( files ); emit newTask( i18n("Normalizing volume levels") ); m_normalizeJob->start(); } void K3b::AudioJob::slotNormalizeJobFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( success ) { if( m_doc->onlyCreateImages() ) { jobFinished(true); } else { // start the writing if( !prepareWriter() ) { cleanupAfterError(); jobFinished(false); } else startWriting(); } } else { cleanupAfterError(); jobFinished(false); } } void K3b::AudioJob::slotNormalizeProgress( int p ) { double totalTasks = d->copies+2.0; double tasksDone = 1; // the decoding has been finished emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } void K3b::AudioJob::slotNormalizeSubProgress( int p ) { emit subPercent( p ); } bool K3b::AudioJob::writeTocFile() { K3b::TocFileWriter tocWriter; tocWriter.setData( m_doc->toToc() ); tocWriter.setHideFirstTrack( m_doc->hideFirstTrack() ); if( d->useCdText ) tocWriter.setCdText( m_doc->cdTextData() ); if( !m_doc->onTheFly() ) { QStringList filenames; for( int i = 1; i <= m_doc->numOfTracks(); ++i ) filenames += m_tempData->bufferFileName( i ); tocWriter.setFilenames( filenames ); } return tocWriter.save( m_tempData->tocFileName() ); } bool K3b::AudioJob::writeInfFiles() { K3b::InfFileWriter infFileWriter; K3b::AudioTrack* track = m_doc->firstTrack(); while( track ) { infFileWriter.setTrack( track->toCdTrack() ); infFileWriter.setTrackNumber( track->trackNumber() ); if( !m_doc->onTheFly() ) infFileWriter.setBigEndian( false ); if( !infFileWriter.save( m_tempData->infFileName(track) ) ) return false; track = track->next(); } return true; } // checks if the doc contains sources from an audio cd which cannot be read on-the-fly bool K3b::AudioJob::checkAudioSources() { K3b::AudioTrack* track = m_doc->firstTrack(); K3b::AudioDataSource* source = track->firstSource(); while( source ) { if( K3b::AudioCdTrackSource* cdSource = dynamic_cast(source) ) { // // If which cases we cannot wite on-the-fly: // 1. the writing device contains one of the audio cds // 2. Well, one of the cds is missing // K3b::Device::Device* dev = cdSource->searchForAudioCD(); if( !dev || dev == writer() ) return false; else cdSource->setDevice( dev ); } // next source source = source->next(); if( !source ) { track = track->next(); if( track ) source = track->firstSource(); } } return true; } QString K3b::AudioJob::jobDescription() const { return i18n("Writing Audio CD") + ( m_doc->title().isEmpty() ? QString() : QString( " (%1)" ).arg(m_doc->title()) ); } QString K3b::AudioJob::jobDetails() const { return ( i18np( "1 track (%2 minutes)", "%1 tracks (%2 minutes)", m_doc->numOfTracks(), m_doc->length().toString()) + ( m_doc->copies() > 1 && !m_doc->dummy() ? i18np(" - %1 copy", " - %1 copies", m_doc->copies()) : QString() ) ); } #include "k3baudiojob.moc" diff --git a/libk3b/projects/audiocd/k3baudionormalizejob.cpp b/libk3b/projects/audiocd/k3baudionormalizejob.cpp index e3e410021..51cbbec7e 100644 --- a/libk3b/projects/audiocd/k3baudionormalizejob.cpp +++ b/libk3b/projects/audiocd/k3baudionormalizejob.cpp @@ -1,204 +1,203 @@ /* * - * Copyright (C) 2003 Sebastian Trueg + * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2007 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3baudionormalizejob.h" #include #include #include #include #include K3b::AudioNormalizeJob::AudioNormalizeJob( K3b::JobHandler* hdl, QObject* parent ) : K3b::Job( hdl, parent ), m_process(0) { } K3b::AudioNormalizeJob::~AudioNormalizeJob() { - if( m_process ) - delete m_process; + delete m_process; } void K3b::AudioNormalizeJob::start() { m_canceled = false; m_currentAction = COMPUTING_LEVELS; m_currentTrack = 1; jobStarted(); if( m_process ) delete m_process; m_process = new K3b::Process(); connect( m_process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); connect( m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessExited(int, QProcess::ExitStatus)) ); const K3b::ExternalBin* bin = k3bcore->externalBinManager()->binObject( "normalize" ); if( !bin ) { emit infoMessage( i18n("Could not find normalize executable."), ERROR ); jobFinished(false); return; } if( !bin->copyright.isEmpty() ) emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3",bin->name(),bin->version,bin->copyright), INFO ); // create the commandline *m_process << bin; // additional user parameters from config const QStringList& params = bin->userParameters(); for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) *m_process << *it; // end the options *m_process << "--"; // add the files for( int i = 0; i < m_files.count(); ++i ) *m_process << m_files[i]; // now start the process - if( !m_process->start( K3Process::AllOutput ) ) { + if( !m_process->start( KProcess::OnlyStderrChannel ) ) { // something went wrong when starting the program // it "should" be the executable kDebug() << "(K3b::AudioNormalizeJob) could not start normalize"; emit infoMessage( i18n("Could not start normalize."), K3b::Job::ERROR ); jobFinished(false); } } void K3b::AudioNormalizeJob::cancel() { m_canceled = true; if( m_process ) if( m_process->isRunning() ) { m_process->kill(); } } void K3b::AudioNormalizeJob::slotStdLine( const QString& line ) { // percent, subPercent, newTask (compute level and adjust) // emit newSubTask( i18n("Normalizing track %1 of %2 (%3)",t,tt,m_files.at(t-1)) ); emit debuggingOutput( "normalize", line ); // wenn "% done" drin: // wenn ein --% drin ist, so beginnt ein neuer track // sonst prozent parsen "batch xxx" ist der fortschritt der action // also ev. den batch fortschritt * 1/2 if( line.startsWith( "Applying adjustment" ) ) { if( m_currentAction == COMPUTING_LEVELS ) { // starting the adjustment with track 1 m_currentTrack = 1; m_currentAction = ADJUSTING_LEVELS; } } else if( line.contains( "already normalized" ) ) { // no normalization necessary for the current track emit infoMessage( i18n("Track %1 is already normalized.",m_currentTrack), INFO ); m_currentTrack++; } else if( line.contains( "--% done") ) { if( m_currentAction == ADJUSTING_LEVELS ) { emit newTask( i18n("Adjusting volume level for track %1 of %2",m_currentTrack,m_files.count()) ); kDebug() << "(K3b::AudioNormalizeJob) adjusting level for track " << m_currentTrack << " " << m_files.at(m_currentTrack-1) << endl; } else { emit newTask( i18n("Computing level for track %1 of %2",m_currentTrack,m_files.count()) ); kDebug() << "(K3b::AudioNormalizeJob) computing level for track " << m_currentTrack << " " << m_files.at(m_currentTrack-1) << endl; } m_currentTrack++; } else if( int pos = line.indexOf( "% done" ) > 0 ) { // parse progress: "XXX% done" and "batch XXX% done" pos -= 3; bool ok; // TODO: do not use fixed values // track progress starts at position 19 in version 0.7.6 int p = line.mid( 19, 3 ).toInt(&ok); if( ok ) emit subPercent( p ); else kDebug() << "(K3b::AudioNormalizeJob) subPercent parsing error at pos " << 19 << " in line '" << line.mid( 19, 3 ) << "'" << endl; // batch progress starts at position 50 in version 0.7.6 p = line.mid( 50, 3 ).toInt(&ok); if( ok && m_currentAction == COMPUTING_LEVELS ) emit percent( (int)((double)p/2.0) ); else if( ok && m_currentAction == ADJUSTING_LEVELS ) emit percent( 50 + (int)((double)p/2.0) ); else kDebug() << "(K3b::AudioNormalizeJob) percent parsing error at pos " << 50 << " in line '" << line.mid( 50, 3 ) << "'" << endl; } } void K3b::AudioNormalizeJob::slotProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { if( exitStatus == QProcess::NormalExit ) { switch( exitCode ) { case 0: emit infoMessage( i18n("Successfully normalized all tracks."), SUCCESS ); jobFinished(true); break; default: if( !m_canceled ) { emit infoMessage( i18n("%1 returned an unknown error (code %2).",QString("normalize"), exitCode), K3b::Job::ERROR ); emit infoMessage( i18n("Please send me an email with the last output."), K3b::Job::ERROR ); emit infoMessage( i18n("Error while normalizing tracks."), ERROR ); } else emit canceled(); jobFinished(false); break; } } else { emit infoMessage( i18n("%1 did not exit cleanly.",QString("Normalize")), K3b::Job::ERROR ); jobFinished( false ); } } #include "k3baudionormalizejob.moc" diff --git a/libk3b/projects/datacd/k3bdatadoc.cpp b/libk3b/projects/datacd/k3bdatadoc.cpp index ccc6ce522..17d3d935b 100644 --- a/libk3b/projects/datacd/k3bdatadoc.cpp +++ b/libk3b/projects/datacd/k3bdatadoc.cpp @@ -1,1412 +1,1413 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3bdatadoc.h" #include "k3bfileitem.h" #include "k3bdiritem.h" #include "k3bsessionimportitem.h" #include "k3bdatajob.h" #include "k3bbootitem.h" #include "k3bspecialdataitem.h" #include "k3bfilecompilationsizehandler.h" #include "k3bmkisofshandler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * There are two ways to fill a data project with files and folders: * \li Use the addUrl and addUrlsT methods * \li or create your own K3b::DirItems and K3b::FileItems. The doc will be properly updated * by the constructors of the items. */ K3b::DataDoc::DataDoc( QObject* parent ) : K3b::Doc( parent ) { m_root = 0; m_sizeHandler = new K3b::FileCompilationSizeHandler(); } K3b::DataDoc::~DataDoc() { delete m_root; delete m_sizeHandler; // delete m_oldSessionSizeHandler; } bool K3b::DataDoc::newDocument() { clear(); if ( !m_root ) m_root = new K3b::RootItem( this ); m_bExistingItemsReplaceAll = m_bExistingItemsIgnoreAll = false; m_multisessionMode = AUTO; m_dataMode = K3b::DATA_MODE_AUTO; m_isoOptions = K3b::IsoOptions(); return K3b::Doc::newDocument(); } void K3b::DataDoc::clear() { clearImportedSession(); m_importedSession = -1; m_oldSessionSize = 0; m_bootCataloge = 0; if( m_root ) { while( !m_root->children().isEmpty() ) removeItem( m_root->children().first() ); } m_sizeHandler->clear(); } QString K3b::DataDoc::name() const { return m_isoOptions.volumeID(); } void K3b::DataDoc::setIsoOptions( const K3b::IsoOptions& o ) { m_isoOptions = o; emit changed(); } void K3b::DataDoc::setVolumeID( const QString& v ) { m_isoOptions.setVolumeID( v ); emit changed(); } void K3b::DataDoc::addUrls( const KUrl::List& urls ) { addUrlsToDir( urls, root() ); } void K3b::DataDoc::addUrlsToDir( const KUrl::List& l, K3b::DirItem* dir ) { if( !dir ) dir = root(); KUrl::List urls = K3b::convertToLocalUrls(l); for( KUrl::List::ConstIterator it = urls.constBegin(); it != urls.constEnd(); ++it ) { const KUrl& url = *it; QFileInfo f( url.path() ); QString k3bname = f.absoluteFilePath().section( "/", -1 ); // filenames cannot end in backslashes (mkisofs problem. See comments in k3bisoimager.cpp (escapeGraftPoint())) while( k3bname[k3bname.length()-1] == '\\' ) k3bname.truncate( k3bname.length()-1 ); // backup dummy name if( k3bname.isEmpty() ) k3bname = "1"; K3b::DirItem* newDirItem = 0; // rename the new item if an item with that name already exists int cnt = 0; bool ok = false; while( !ok ) { ok = true; QString name( k3bname ); if( cnt > 0 ) name += QString("_%1").arg(cnt); if( K3b::DataItem* oldItem = dir->find( name ) ) { if( f.isDir() && oldItem->isDir() ) { // ok, just reuse the dir newDirItem = static_cast(oldItem); } // directories cannot replace files in an old session (I think) // and also directories can for sure never be replaced (only be reused as above) // so we always rename if the old item is a dir. else if( !oldItem->isFromOldSession() || f.isDir() || oldItem->isDir() ) { ++cnt; ok = false; } } } if( cnt > 0 ) k3bname += QString("_%1").arg(cnt); // QFileInfo::exists and QFileInfo::isReadable return false for broken symlinks :( if( f.isDir() && !f.isSymLink() ) { if( !newDirItem ) { newDirItem = new K3b::DirItem( k3bname, this, dir ); newDirItem->setLocalPath( url.path() ); // HACK: see k3bdiritem.h } // recursively add all the files in the directory QStringList dlist = QDir( f.absoluteFilePath() ).entryList( QDir::TypeMask|QDir::System|QDir::Hidden|QDir::NoDotAndDotDot ); KUrl::List newUrls; for( QStringList::ConstIterator it = dlist.constBegin(); it != dlist.constEnd(); ++it ) newUrls.append( KUrl( f.absoluteFilePath() + "/" + *it ) ); addUrlsToDir( newUrls, newDirItem ); } else if( f.isSymLink() || f.isFile() ) (void)new K3b::FileItem( url.path(), this, dir, k3bname ); } emit changed(); setModified( true ); } bool K3b::DataDoc::nameAlreadyInDir( const QString& name, K3b::DirItem* dir ) { if( !dir ) return false; else return ( dir->find( name ) != 0 ); } K3b::DirItem* K3b::DataDoc::addEmptyDir( const QString& name, K3b::DirItem* parent ) { K3b::DirItem* item = new K3b::DirItem( name, this, parent ); setModified( true ); return item; } KIO::filesize_t K3b::DataDoc::size() const { if( m_isoOptions.doNotCacheInodes() ) return root()->blocks().mode1Bytes(); else return m_sizeHandler->blocks( m_isoOptions.followSymbolicLinks() || !m_isoOptions.createRockRidge() ).mode1Bytes(); } KIO::filesize_t K3b::DataDoc::burningSize() const { return size() - m_oldSessionSize; //m_oldSessionSizeHandler->size(); } K3b::Msf K3b::DataDoc::length() const { // 1 block consists of 2048 bytes real data // and 1 block equals to 1 audio frame // so this is the way to calculate: return K3b::Msf( size() / 2048 ); } K3b::Msf K3b::DataDoc::burningLength() const { return K3b::Msf( burningSize() / 2048 ); } QString K3b::DataDoc::typeString() const { return QString::fromLatin1("data"); } bool K3b::DataDoc::loadDocumentData( QDomElement* rootElem ) { if( !root() ) newDocument(); QDomNodeList nodes = rootElem->childNodes(); if( nodes.item(0).nodeName() != "general" ) { kDebug() << "(K3b::DataDoc) could not find 'general' section."; return false; } if( !readGeneralDocumentData( nodes.item(0).toElement() ) ) return false; // parse options // ----------------------------------------------------------------- if( nodes.item(1).nodeName() != "options" ) { kDebug() << "(K3b::DataDoc) could not find 'options' section."; return false; } if( !loadDocumentDataOptions( nodes.item(1).toElement() ) ) return false; // ----------------------------------------------------------------- // parse header // ----------------------------------------------------------------- if( nodes.item(2).nodeName() != "header" ) { kDebug() << "(K3b::DataDoc) could not find 'header' section."; return false; } if( !loadDocumentDataHeader( nodes.item(2).toElement() ) ) return false; // ----------------------------------------------------------------- // parse files // ----------------------------------------------------------------- if( nodes.item(3).nodeName() != "files" ) { kDebug() << "(K3b::DataDoc) could not find 'files' section."; return false; } if( m_root == 0 ) m_root = new K3b::RootItem( this ); QDomNodeList filesList = nodes.item(3).childNodes(); for( int i = 0; i < filesList.count(); i++ ) { QDomElement e = filesList.item(i).toElement(); if( !loadDataItem( e, root() ) ) return false; } // ----------------------------------------------------------------- // // Old versions of K3b do not properly save the boot catalog location // and name. So to ensure we have one around even if loading an old project // file we create a default one here. // if( !m_bootImages.isEmpty() && !m_bootCataloge ) createBootCatalogeItem( m_bootImages.first()->parent() ); informAboutNotFoundFiles(); return true; } bool K3b::DataDoc::loadDocumentDataOptions( QDomElement elem ) { QDomNodeList headerList = elem.childNodes(); for( int i = 0; i < headerList.count(); i++ ) { QDomElement e = headerList.item(i).toElement(); if( e.isNull() ) return false; if( e.nodeName() == "rock_ridge") m_isoOptions.setCreateRockRidge( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "joliet") m_isoOptions.setCreateJoliet( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "udf") m_isoOptions.setCreateUdf( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "joliet_allow_103_characters") m_isoOptions.setJolietLong( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_allow_lowercase") m_isoOptions.setISOallowLowercase( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_allow_period_at_begin") m_isoOptions.setISOallowPeriodAtBegin( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_allow_31_char") m_isoOptions.setISOallow31charFilenames( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_omit_version_numbers") m_isoOptions.setISOomitVersionNumbers( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_omit_trailing_period") m_isoOptions.setISOomitTrailingPeriod( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_max_filename_length") m_isoOptions.setISOmaxFilenameLength( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_relaxed_filenames") m_isoOptions.setISOrelaxedFilenames( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_no_iso_translate") m_isoOptions.setISOnoIsoTranslate( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_allow_multidot") m_isoOptions.setISOallowMultiDot( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_untranslated_filenames") m_isoOptions.setISOuntranslatedFilenames( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "follow_symbolic_links") m_isoOptions.setFollowSymbolicLinks( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "create_trans_tbl") m_isoOptions.setCreateTRANS_TBL( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "hide_trans_tbl") m_isoOptions.setHideTRANS_TBL( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "iso_level") m_isoOptions.setISOLevel( e.text().toInt() ); else if( e.nodeName() == "discard_symlinks") m_isoOptions.setDiscardSymlinks( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "discard_broken_symlinks") m_isoOptions.setDiscardBrokenSymlinks( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "preserve_file_permissions") m_isoOptions.setPreserveFilePermissions( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "do_not_cache_inodes" ) m_isoOptions.setDoNotCacheInodes( e.attributeNode( "activated" ).value() == "yes" ); else if( e.nodeName() == "whitespace_treatment" ) { if( e.text() == "strip" ) m_isoOptions.setWhiteSpaceTreatment( K3b::IsoOptions::strip ); else if( e.text() == "extended" ) m_isoOptions.setWhiteSpaceTreatment( K3b::IsoOptions::extended ); else if( e.text() == "extended" ) m_isoOptions.setWhiteSpaceTreatment( K3b::IsoOptions::replace ); else m_isoOptions.setWhiteSpaceTreatment( K3b::IsoOptions::noChange ); } else if( e.nodeName() == "whitespace_replace_string") m_isoOptions.setWhiteSpaceTreatmentReplaceString( e.text() ); else if( e.nodeName() == "data_track_mode" ) { if( e.text() == "mode1" ) m_dataMode = K3b::DATA_MODE_1; else if( e.text() == "mode2" ) m_dataMode = K3b::DATA_MODE_2; else m_dataMode = K3b::DATA_MODE_AUTO; } else if( e.nodeName() == "multisession" ) { QString mode = e.text(); if( mode == "start" ) setMultiSessionMode( START ); else if( mode == "continue" ) setMultiSessionMode( CONTINUE ); else if( mode == "finish" ) setMultiSessionMode( FINISH ); else if( mode == "none" ) setMultiSessionMode( NONE ); else setMultiSessionMode( AUTO ); } else if( e.nodeName() == "verify_data" ) setVerifyData( e.attributeNode( "activated" ).value() == "yes" ); else kDebug() << "(K3b::DataDoc) unknown option entry: " << e.nodeName(); } return true; } bool K3b::DataDoc::loadDocumentDataHeader( QDomElement headerElem ) { QDomNodeList headerList = headerElem.childNodes(); for( int i = 0; i < headerList.count(); i++ ) { QDomElement e = headerList.item(i).toElement(); if( e.isNull() ) return false; if( e.nodeName() == "volume_id" ) m_isoOptions.setVolumeID( e.text() ); else if( e.nodeName() == "application_id" ) m_isoOptions.setApplicationID( e.text() ); else if( e.nodeName() == "publisher" ) m_isoOptions.setPublisher( e.text() ); else if( e.nodeName() == "preparer" ) m_isoOptions.setPreparer( e.text() ); else if( e.nodeName() == "volume_set_id" ) m_isoOptions.setVolumeSetId( e.text() ); else if( e.nodeName() == "volume_set_size" ) m_isoOptions.setVolumeSetSize( e.text().toInt() ); else if( e.nodeName() == "volume_set_number" ) m_isoOptions.setVolumeSetNumber( e.text().toInt() ); else if( e.nodeName() == "system_id" ) m_isoOptions.setSystemId( e.text() ); else kDebug() << "(K3b::DataDoc) unknown header entry: " << e.nodeName(); } return true; } bool K3b::DataDoc::loadDataItem( QDomElement& elem, K3b::DirItem* parent ) { K3b::DataItem* newItem = 0; if( elem.nodeName() == "file" ) { QDomElement urlElem = elem.firstChild().toElement(); if( urlElem.isNull() ) { kDebug() << "(K3b::DataDoc) file-element without url!"; return false; } QFileInfo f( urlElem.text() ); // We canot use exists() here since this always disqualifies broken symlinks if( !f.isFile() && !f.isSymLink() ) m_notFoundFiles.append( urlElem.text() ); // broken symlinks are not readable according to QFileInfo which is wrong in our case else if( f.isFile() && !f.isReadable() ) m_noPermissionFiles.append( urlElem.text() ); else if( !elem.attribute( "bootimage" ).isEmpty() ) { K3b::BootItem* bootItem = new K3b::BootItem( urlElem.text(), this, parent, elem.attributeNode( "name" ).value() ); if( elem.attribute( "bootimage" ) == "floppy" ) bootItem->setImageType( K3b::BootItem::FLOPPY ); else if( elem.attribute( "bootimage" ) == "harddisk" ) bootItem->setImageType( K3b::BootItem::HARDDISK ); else bootItem->setImageType( K3b::BootItem::NONE ); bootItem->setNoBoot( elem.attribute( "no_boot" ) == "yes" ); bootItem->setBootInfoTable( elem.attribute( "boot_info_table" ) == "yes" ); bootItem->setLoadSegment( elem.attribute( "load_segment" ).toInt() ); bootItem->setLoadSize( elem.attribute( "load_size" ).toInt() ); newItem = bootItem; } else { newItem = new K3b::FileItem( urlElem.text(), this, parent, elem.attributeNode( "name" ).value() ); } } else if( elem.nodeName() == "special" ) { if( elem.attributeNode( "type" ).value() == "boot cataloge" ) createBootCatalogeItem( parent )->setK3bName( elem.attributeNode( "name" ).value() ); } else if( elem.nodeName() == "directory" ) { // This is for the VideoDVD project which already contains the *_TS folders K3b::DirItem* newDirItem = 0; if( K3b::DataItem* item = parent->find( elem.attributeNode( "name" ).value() ) ) { if( item->isDir() ) { newDirItem = static_cast(item); } else { kError() << "(K3b::DataDoc) INVALID DOCUMENT: item " << item->k3bPath() << " saved twice" << endl; return false; } } if( !newDirItem ) newDirItem = new K3b::DirItem( elem.attributeNode( "name" ).value(), this, parent ); QDomNodeList childNodes = elem.childNodes(); for( int i = 0; i < childNodes.count(); i++ ) { QDomElement e = childNodes.item(i).toElement(); if( !loadDataItem( e, newDirItem ) ) return false; } newItem = newDirItem; } else { kDebug() << "(K3b::DataDoc) wrong tag in files-section: " << elem.nodeName(); return false; } // load the sort weight if( newItem ) newItem->setSortWeight( elem.attribute( "sort_weight", "0" ).toInt() ); return true; } bool K3b::DataDoc::saveDocumentData( QDomElement* docElem ) { QDomDocument doc = docElem->ownerDocument(); saveGeneralDocumentData( docElem ); // all options // ---------------------------------------------------------------------- QDomElement optionsElem = doc.createElement( "options" ); saveDocumentDataOptions( optionsElem ); docElem->appendChild( optionsElem ); // ---------------------------------------------------------------------- // the header stuff // ---------------------------------------------------------------------- QDomElement headerElem = doc.createElement( "header" ); saveDocumentDataHeader( headerElem ); docElem->appendChild( headerElem ); // now do the "real" work: save the entries // ---------------------------------------------------------------------- QDomElement topElem = doc.createElement( "files" ); Q_FOREACH( K3b::DataItem* item, root()->children() ) { saveDataItem( item, &doc, &topElem ); } docElem->appendChild( topElem ); // ---------------------------------------------------------------------- return true; } void K3b::DataDoc::saveDocumentDataOptions( QDomElement& optionsElem ) { QDomDocument doc = optionsElem.ownerDocument(); QDomElement topElem = doc.createElement( "rock_ridge" ); topElem.setAttribute( "activated", isoOptions().createRockRidge() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "joliet" ); topElem.setAttribute( "activated", isoOptions().createJoliet() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "udf" ); topElem.setAttribute( "activated", isoOptions().createUdf() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "joliet_allow_103_characters" ); topElem.setAttribute( "activated", isoOptions().jolietLong() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_allow_lowercase" ); topElem.setAttribute( "activated", isoOptions().ISOallowLowercase() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_allow_period_at_begin" ); topElem.setAttribute( "activated", isoOptions().ISOallowPeriodAtBegin() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_allow_31_char" ); topElem.setAttribute( "activated", isoOptions().ISOallow31charFilenames() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_omit_version_numbers" ); topElem.setAttribute( "activated", isoOptions().ISOomitVersionNumbers() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_omit_trailing_period" ); topElem.setAttribute( "activated", isoOptions().ISOomitTrailingPeriod() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_max_filename_length" ); topElem.setAttribute( "activated", isoOptions().ISOmaxFilenameLength() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_relaxed_filenames" ); topElem.setAttribute( "activated", isoOptions().ISOrelaxedFilenames() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_no_iso_translate" ); topElem.setAttribute( "activated", isoOptions().ISOnoIsoTranslate() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_allow_multidot" ); topElem.setAttribute( "activated", isoOptions().ISOallowMultiDot() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_untranslated_filenames" ); topElem.setAttribute( "activated", isoOptions().ISOuntranslatedFilenames() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "follow_symbolic_links" ); topElem.setAttribute( "activated", isoOptions().followSymbolicLinks() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "create_trans_tbl" ); topElem.setAttribute( "activated", isoOptions().createTRANS_TBL() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "hide_trans_tbl" ); topElem.setAttribute( "activated", isoOptions().hideTRANS_TBL() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "iso_level" ); topElem.appendChild( doc.createTextNode( QString::number(isoOptions().ISOLevel()) ) ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "discard_symlinks" ); topElem.setAttribute( "activated", isoOptions().discardSymlinks() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "discard_broken_symlinks" ); topElem.setAttribute( "activated", isoOptions().discardBrokenSymlinks() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "preserve_file_permissions" ); topElem.setAttribute( "activated", isoOptions().preserveFilePermissions() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "do_not_cache_inodes" ); topElem.setAttribute( "activated", isoOptions().doNotCacheInodes() ? "yes" : "no" ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "whitespace_treatment" ); switch( isoOptions().whiteSpaceTreatment() ) { case K3b::IsoOptions::strip: topElem.appendChild( doc.createTextNode( "strip" ) ); break; case K3b::IsoOptions::extended: topElem.appendChild( doc.createTextNode( "extended" ) ); break; case K3b::IsoOptions::replace: topElem.appendChild( doc.createTextNode( "replace" ) ); break; default: topElem.appendChild( doc.createTextNode( "noChange" ) ); break; } optionsElem.appendChild( topElem ); topElem = doc.createElement( "whitespace_replace_string" ); topElem.appendChild( doc.createTextNode( isoOptions().whiteSpaceTreatmentReplaceString() ) ); optionsElem.appendChild( topElem ); topElem = doc.createElement( "data_track_mode" ); if( m_dataMode == K3b::DATA_MODE_1 ) topElem.appendChild( doc.createTextNode( "mode1" ) ); else if( m_dataMode == K3b::DATA_MODE_2 ) topElem.appendChild( doc.createTextNode( "mode2" ) ); else topElem.appendChild( doc.createTextNode( "auto" ) ); optionsElem.appendChild( topElem ); // save multisession topElem = doc.createElement( "multisession" ); switch( m_multisessionMode ) { case START: topElem.appendChild( doc.createTextNode( "start" ) ); break; case CONTINUE: topElem.appendChild( doc.createTextNode( "continue" ) ); break; case FINISH: topElem.appendChild( doc.createTextNode( "finish" ) ); break; case NONE: topElem.appendChild( doc.createTextNode( "none" ) ); break; default: topElem.appendChild( doc.createTextNode( "auto" ) ); break; } optionsElem.appendChild( topElem ); topElem = doc.createElement( "verify_data" ); topElem.setAttribute( "activated", verifyData() ? "yes" : "no" ); optionsElem.appendChild( topElem ); // ---------------------------------------------------------------------- } void K3b::DataDoc::saveDocumentDataHeader( QDomElement& headerElem ) { QDomDocument doc = headerElem.ownerDocument(); QDomElement topElem = doc.createElement( "volume_id" ); topElem.appendChild( doc.createTextNode( isoOptions().volumeID() ) ); headerElem.appendChild( topElem ); topElem = doc.createElement( "volume_set_id" ); topElem.appendChild( doc.createTextNode( isoOptions().volumeSetId() ) ); headerElem.appendChild( topElem ); topElem = doc.createElement( "volume_set_size" ); topElem.appendChild( doc.createTextNode( QString::number(isoOptions().volumeSetSize()) ) ); headerElem.appendChild( topElem ); topElem = doc.createElement( "volume_set_number" ); topElem.appendChild( doc.createTextNode( QString::number(isoOptions().volumeSetNumber()) ) ); headerElem.appendChild( topElem ); topElem = doc.createElement( "system_id" ); topElem.appendChild( doc.createTextNode( isoOptions().systemId() ) ); headerElem.appendChild( topElem ); topElem = doc.createElement( "application_id" ); topElem.appendChild( doc.createTextNode( isoOptions().applicationID() ) ); headerElem.appendChild( topElem ); topElem = doc.createElement( "publisher" ); topElem.appendChild( doc.createTextNode( isoOptions().publisher() ) ); headerElem.appendChild( topElem ); topElem = doc.createElement( "preparer" ); topElem.appendChild( doc.createTextNode( isoOptions().preparer() ) ); headerElem.appendChild( topElem ); // ---------------------------------------------------------------------- } void K3b::DataDoc::saveDataItem( K3b::DataItem* item, QDomDocument* doc, QDomElement* parent ) { if( K3b::FileItem* fileItem = dynamic_cast( item ) ) { if( m_oldSession.contains( fileItem ) ) { kDebug() << "(K3b::DataDoc) ignoring fileitem " << fileItem->k3bName() << " from old session while saving..."; } else { QDomElement topElem = doc->createElement( "file" ); topElem.setAttribute( "name", fileItem->k3bName() ); QDomElement subElem = doc->createElement( "url" ); subElem.appendChild( doc->createTextNode( fileItem->localPath() ) ); topElem.appendChild( subElem ); if( item->sortWeight() != 0 ) topElem.setAttribute( "sort_weight", QString::number(item->sortWeight()) ); parent->appendChild( topElem ); // add boot options as attributes to preserve compatibility to older K3b versions if( K3b::BootItem* bootItem = dynamic_cast( fileItem ) ) { if( bootItem->imageType() == K3b::BootItem::FLOPPY ) topElem.setAttribute( "bootimage", "floppy" ); else if( bootItem->imageType() == K3b::BootItem::HARDDISK ) topElem.setAttribute( "bootimage", "harddisk" ); else topElem.setAttribute( "bootimage", "none" ); topElem.setAttribute( "no_boot", bootItem->noBoot() ? "yes" : "no" ); topElem.setAttribute( "boot_info_table", bootItem->bootInfoTable() ? "yes" : "no" ); topElem.setAttribute( "load_segment", QString::number( bootItem->loadSegment() ) ); topElem.setAttribute( "load_size", QString::number( bootItem->loadSize() ) ); } } } else if( item == m_bootCataloge ) { QDomElement topElem = doc->createElement( "special" ); topElem.setAttribute( "name", m_bootCataloge->k3bName() ); topElem.setAttribute( "type", "boot cataloge" ); parent->appendChild( topElem ); } else if( K3b::DirItem* dirItem = dynamic_cast( item ) ) { QDomElement topElem = doc->createElement( "directory" ); topElem.setAttribute( "name", dirItem->k3bName() ); if( item->sortWeight() != 0 ) topElem.setAttribute( "sort_weight", QString::number(item->sortWeight()) ); Q_FOREACH( K3b::DataItem* item, dirItem->children() ) { saveDataItem( item, doc, &topElem ); } parent->appendChild( topElem ); } } void K3b::DataDoc::removeItem( K3b::DataItem* item ) { if( !item ) return; if( item->isRemoveable() ) { delete item; } else kDebug() << "(K3b::DataDoc) tried to remove non-removable entry!"; } void K3b::DataDoc::aboutToRemoveItemFromDir( K3b::DirItem* /*parent*/, K3b::DataItem* removedItem ) { emit aboutToRemoveItem( removedItem ); } void K3b::DataDoc::aboutToAddItemToDir( K3b::DirItem* parent, K3b::DataItem* addedItem ) { emit aboutToAddItem( parent, addedItem ); } void K3b::DataDoc::itemRemovedFromDir( K3b::DirItem*, K3b::DataItem* removedItem ) { // update the project size if( !removedItem->isFromOldSession() ) m_sizeHandler->removeFile( removedItem ); // update the boot item list if( removedItem->isBootItem() ) { m_bootImages.removeAll( static_cast( removedItem ) ); if( m_bootImages.isEmpty() ) { delete m_bootCataloge; m_bootCataloge = 0; } } emit itemRemoved( removedItem ); emit changed(); } void K3b::DataDoc::itemAddedToDir( K3b::DirItem*, K3b::DataItem* item ) { // update the project size if( !item->isFromOldSession() ) m_sizeHandler->addFile( item ); // update the boot item list if( item->isBootItem() ) m_bootImages.append( static_cast( item ) ); emit itemAdded( item ); emit changed(); } void K3b::DataDoc::moveItem( K3b::DataItem* item, K3b::DirItem* newParent ) { if( !item || !newParent ) { kDebug() << "(K3b::DataDoc) item or parentitem was NULL while moving."; return; } if( !item->isMoveable() ) { kDebug() << "(K3b::DataDoc) item is not movable! "; return; } item->reparent( newParent ); } void K3b::DataDoc::moveItems( const QList& itemList, K3b::DirItem* newParent ) { if( !newParent ) { kDebug() << "(K3b::DataDoc) tried to move items to nowhere...!"; return; } Q_FOREACH( K3b::DataItem* item, itemList ) { // check if newParent is subdir of item if( K3b::DirItem* dirItem = dynamic_cast( item ) ) { if( dirItem->isSubItem( newParent ) ) { continue; } } if( item->isMoveable() ) item->reparent( newParent ); } } K3b::BurnJob* K3b::DataDoc::newBurnJob( K3b::JobHandler* hdl, QObject* parent ) { return new K3b::DataJob( this, hdl, parent ); } QString K3b::DataDoc::treatWhitespace( const QString& path ) { // TODO: // It could happen that two files with different names // will have the same name after the treatment // Perhaps we should add a number at the end or something // similar (s.a.) if( isoOptions().whiteSpaceTreatment() != K3b::IsoOptions::noChange ) { QString result = path; if( isoOptions().whiteSpaceTreatment() == K3b::IsoOptions::replace ) { result.replace( ' ', isoOptions().whiteSpaceTreatmentReplaceString() ); } else if( isoOptions().whiteSpaceTreatment() == K3b::IsoOptions::strip ) { result.remove( ' ' ); } else if( isoOptions().whiteSpaceTreatment() == K3b::IsoOptions::extended ) { result.truncate(0); for( int i = 0; i < path.length(); i++ ) { if( path[i] == ' ' ) { if( path[i+1] != ' ' ) result.append( path[++i].toUpper() ); } else result.append( path[i] ); } } kDebug() << "(K3b::DataDoc) converted " << path << " to " << result; return result; } else return path; } void K3b::DataDoc::prepareFilenames() { m_needToCutFilenames = false; m_needToCutFilenameItems.clear(); // // if joliet is used cut the names and rename if necessary // 64 characters for standard joliet and 103 characters for long joliet names // // Rockridge supports the full 255 UNIX chars and in case Rockridge is disabled we leave // it to mkisofs for now since handling all the options to alter the ISO9660 standard it just // too much. // K3b::DataItem* item = root(); int maxlen = ( isoOptions().jolietLong() ? 103 : 64 ); while( (item = item->nextSibling()) ) { item->setWrittenName( treatWhitespace( item->k3bName() ) ); if( isoOptions().createJoliet() && item->writtenName().length() > maxlen ) { m_needToCutFilenames = true; item->setWrittenName( K3b::cutFilename( item->writtenName(), maxlen ) ); m_needToCutFilenameItems.append( item ); } // TODO: check the Joliet charset } // // 3. check if a directory contains items with the same name // prepareFilenamesInDir( root() ); } void K3b::DataDoc::prepareFilenamesInDir( K3b::DirItem* dir ) { if( !dir ) return; QList sortedChildren; QList children( dir->children() ); QList::const_iterator it = children.constEnd(); while ( it != children.constBegin() ) { --it; K3b::DataItem* item = *it; if( item->isDir() ) prepareFilenamesInDir( dynamic_cast( item ) ); // insertion sort int i = 0; while( i < sortedChildren.count() && item->writtenName() > sortedChildren.at(i)->writtenName() ) ++i; sortedChildren.insert( i, item ); } if( isoOptions().createJoliet() || isoOptions().createRockRidge() ) { QList sameNameList; while( !sortedChildren.isEmpty() ) { sameNameList.clear(); do { sameNameList.append( sortedChildren.takeFirst() ); } while( !sortedChildren.isEmpty() && sortedChildren.first()->writtenName() == sameNameList.first()->writtenName() ); if( sameNameList.count() > 1 ) { // now we need to rename the items unsigned int maxlen = 255; if( isoOptions().createJoliet() ) { if( isoOptions().jolietLong() ) maxlen = 103; else maxlen = 64; } int cnt = 1; Q_FOREACH( K3b::DataItem* item, sameNameList ) { item->setWrittenName( K3b::appendNumberToFilename( item->writtenName(), cnt++, maxlen ) ); } } } } } void K3b::DataDoc::informAboutNotFoundFiles() { if( !m_notFoundFiles.isEmpty() ) { KMessageBox::informationList( qApp->activeWindow(), i18n("Could not find the following files:"), m_notFoundFiles, i18n("Not Found") ); m_notFoundFiles.clear(); } if( !m_noPermissionFiles.isEmpty() ) { KMessageBox::informationList( qApp->activeWindow(), i18n("No permission to read the following files:"), m_noPermissionFiles, i18n("No Read Permission") ); m_noPermissionFiles.clear(); } } void K3b::DataDoc::setMultiSessionMode( K3b::DataDoc::MultiSessionMode mode ) { if( m_multisessionMode == NONE || m_multisessionMode == START ) clearImportedSession(); m_multisessionMode = mode; } bool K3b::DataDoc::importSession( K3b::Device::Device* device, int session ) { K3b::Device::DiskInfo diskInfo = device->diskInfo(); // DVD+RW media is reported as non-appendable if( !diskInfo.appendable() && !(diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR)) ) return false; K3b::Device::Toc toc = device->readToc(); if( toc.isEmpty() || toc.last().type() != K3b::Device::Track::TYPE_DATA ) return false; long startSec = toc.last().firstSector().lba(); if ( session > 0 ) { for ( K3b::Device::Toc::const_iterator it = toc.constBegin(); it != toc.constEnd(); ++it ) { if ( ( *it ).session() == session ) { startSec = ( *it ).firstSector().lba(); break; } } } K3b::Iso9660 iso( device, startSec ); if( iso.open() ) { // remove previously imported sessions clearImportedSession(); // set multisession option if( m_multisessionMode != FINISH && m_multisessionMode != AUTO ) m_multisessionMode = CONTINUE; // since in iso9660 it is possible that two files share it's data // simply summing the file sizes could result in wrong values // that's why we use the size from the toc. This is more accurate // anyway since there might be files overwritten or removed m_oldSessionSize = toc.last().lastSector().mode1Bytes(); m_importedSession = session; kDebug() << "(K3b::DataDoc) imported session size: " << KIO::convertSize(m_oldSessionSize); // the track size for DVD+RW media and DVD-RW Overwrite media has nothing to do with the filesystem // size. in that case we need to use the filesystem's size (which is ok since it's one track anyway, // no real multisession) if( diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) ) { m_oldSessionSize = iso.primaryDescriptor().volumeSpaceSize * iso.primaryDescriptor().logicalBlockSize; } // import some former settings m_isoOptions.setCreateRockRidge( iso.firstRRDirEntry() != 0 ); m_isoOptions.setCreateJoliet( iso.firstJolietDirEntry() != 0 ); m_isoOptions.setVolumeID( iso.primaryDescriptor().volumeId ); // TODO: also import some other pd fields const K3b::Iso9660Directory* rootDir = iso.firstRRDirEntry(); // J�rg Schilling says that it is impossible to import the joliet tree for multisession // if( !rootDir ) // rootDir = iso.firstJolietDirEntry(); if( !rootDir ) rootDir = iso.firstIsoDirEntry(); if( rootDir ) { createSessionImportItems( rootDir, root() ); emit changed(); return true; } else { kDebug() << "(K3b::DataDoc::importSession) Could not find primary volume desc."; return false; } } else { kDebug() << "(K3b::DataDoc) unable to read toc."; return false; } } void K3b::DataDoc::createSessionImportItems( const K3b::Iso9660Directory* importDir, K3b::DirItem* parent ) { Q_ASSERT(importDir); QStringList entries = importDir->entries(); entries.removeAll( "." ); entries.removeAll( ".." ); for( QStringList::const_iterator it = entries.constBegin(); it != entries.constEnd(); ++it ) { const K3b::Iso9660Entry* entry = importDir->entry( *it ); K3b::DataItem* oldItem = parent->find( entry->name() ); if( entry->isDirectory() ) { K3b::DirItem* dir = 0; if( oldItem && oldItem->isDir() ) { dir = (K3b::DirItem*)oldItem; } else { // we overwrite without warning! if( oldItem ) removeItem( oldItem ); dir = new K3b::DirItem( entry->name(), this, parent ); } dir->setRemoveable(false); dir->setRenameable(false); dir->setMoveable(false); dir->setHideable(false); dir->setWriteToCd(false); dir->setExtraInfo( i18n("From previous session") ); m_oldSession.append( dir ); createSessionImportItems( static_cast(entry), dir ); } else { const K3b::Iso9660File* file = static_cast(entry); // we overwrite without warning! if( oldItem ) removeItem( oldItem ); K3b::SessionImportItem* item = new K3b::SessionImportItem( file, this, parent ); item->setExtraInfo( i18n("From previous session") ); m_oldSession.append( item ); } } } void K3b::DataDoc::clearImportedSession() { // m_oldSessionSizeHandler->clear(); m_oldSessionSize = 0; while( !m_oldSession.isEmpty() ) { K3b::DataItem* item = m_oldSession.takeFirst(); if( item->isDir() ) { K3b::DirItem* dir = (K3b::DirItem*)item; if( dir->numDirs() + dir->numFiles() == 0 ) { // this imported dir is not needed anymore // since it is empty delete item; } else { Q_FOREACH( K3b::DataItem* item, dir->children() ) { if( !m_oldSession.contains( item ) ) { // now the dir becomes a totally normal dir dir->setRemoveable(true); dir->setRenameable(true); dir->setMoveable(true); dir->setHideable(true); dir->setWriteToCd(true); dir->setExtraInfo( "" ); break; } } } } else { delete item; } } m_multisessionMode = AUTO; emit changed(); } K3b::DirItem* K3b::DataDoc::bootImageDir() { K3b::DataItem* b = m_root->find( "boot" ); if( !b ) { b = new K3b::DirItem( "boot", this, m_root ); setModified( true ); } // if we cannot create the dir because there is a file named boot just use the root dir if( !b->isDir() ) return m_root; else return static_cast(b); } K3b::BootItem* K3b::DataDoc::createBootItem( const QString& filename, K3b::DirItem* dir ) { if( !dir ) dir = bootImageDir(); K3b::BootItem* boot = new K3b::BootItem( filename, this, dir ); if( !m_bootCataloge ) createBootCatalogeItem(dir); return boot; } K3b::DataItem* K3b::DataDoc::createBootCatalogeItem( K3b::DirItem* dir ) { if( !m_bootCataloge ) { QString newName = "boot.catalog"; int i = 0; while( dir->alreadyInDirectory( "boot.catalog" ) ) { ++i; newName = QString( "boot%1.catalog" ).arg(i); } K3b::SpecialDataItem* b = new K3b::SpecialDataItem( this, 0, dir, newName ); m_bootCataloge = b; m_bootCataloge->setRemoveable(false); m_bootCataloge->setHideable(false); m_bootCataloge->setWriteToCd(false); m_bootCataloge->setExtraInfo( i18n("El Torito boot catalog file") ); b->setSpecialType( i18n("Boot catalog") ); } else m_bootCataloge->reparent( dir ); return m_bootCataloge; } QList K3b::DataDoc::findItemByLocalPath( const QString& path ) const { Q_UNUSED( path ); return QList(); } int K3b::DataDoc::importedSession() const { return ( m_oldSession.isEmpty() ? -1 : m_importedSession ); } -int K3b::DataDoc::supportedMediaTypes() const +K3b::Device::MediaTypes K3b::DataDoc::supportedMediaTypes() const { - int m = K3b::Device::MEDIA_WRITABLE; + Device::MediaTypes m = K3b::Device::MEDIA_WRITABLE; // we go bottom-up and remove those media types that are too small // (very very rough for now, we need the media size handling in the // empty disk waiter) if ( size() >= 1024ULL*1024ULL*1024ULL ) { // 1 GB -> no CD m ^= K3b::Device::MEDIA_WRITABLE_CD; } // specal case: writing modes TAO and RAW apply only to CD else if ( writingMode() == K3b::WRITING_MODE_TAO || writingMode() == K3b::WRITING_MODE_RAW ) { m = K3b::Device::MEDIA_WRITABLE_CD; } // 4.3 GB -> no SL-DVD // in case overburn is enabled we allow some made up max size // before we force a DL medium if( size() > 4700372992LL ) { if( !k3bcore->globalSettings()->overburn() || size() > 4900000000LL ) { m ^= K3b::Device::MEDIA_WRITABLE_DVD_SL; } } // 9 GB -> no DVD at all if ( size() >= 9ULL*1024ULL*1024ULL*1024ULL ) { m ^= K3b::Device::MEDIA_WRITABLE_DVD; } // // special case: the user selected a specific writing mode // else if( writingMode() == K3b::WRITING_MODE_RES_OVWR ) { // // we treat DVD+R(W) as restricted overwrite media // m = K3b::Device::MEDIA_DVD_RW_OVWR|K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_PLUS_R; // } +#warning BLu-Ray! return m; } #include "k3bdatadoc.moc" diff --git a/libk3b/projects/datacd/k3bdatadoc.h b/libk3b/projects/datacd/k3bdatadoc.h index 3989c60fc..a521e8174 100644 --- a/libk3b/projects/datacd/k3bdatadoc.h +++ b/libk3b/projects/datacd/k3bdatadoc.h @@ -1,311 +1,311 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3BDATADOC_H #define K3BDATADOC_H #include #include #include "k3bisooptions.h" #include #include //Added by qt3to4: #include #include #include "k3b_export.h" class KConfig; class QString; class QStringList; class QDomDocument; class QDomElement; namespace K3b { class DataItem; class RootItem; class DirItem; class Job; class BootItem; class FileCompilationSizeHandler; class Iso9660Directory; namespace Device { class Device; } /** *@author Sebastian Trueg */ class LIBK3B_EXPORT DataDoc : public Doc { Q_OBJECT public: DataDoc( QObject* parent = 0 ); virtual ~DataDoc(); virtual int type() const { return DATA; } virtual QString typeString() const; virtual QString name() const; /** * The spported media types based on the project size * and settings (example: if writing mode == TAO we force * CD media) */ - virtual int supportedMediaTypes() const; + virtual Device::MediaTypes supportedMediaTypes() const; enum MultiSessionMode { /** * Let the DataJob decide if to close the CD or not. * The decision is based on the state of the inserted media * (appendable/closed), the size of the project (will it fill * up the CD?), and the free space on the inserted media. */ AUTO, NONE, START, CONTINUE, FINISH }; RootItem* root() const { return m_root; } virtual bool newDocument(); virtual void clear(); virtual KIO::filesize_t size() const; /** * This is used for multisession where size() also returnes the imported session's size */ virtual KIO::filesize_t burningSize() const; virtual Msf length() const; virtual Msf burningLength() const; /** * Simply deletes the item if it is removable (meaning isRemovable() returns true. * Be aware that you can remove items simply by deleting them even if isRemovable() * returns false. */ void removeItem( DataItem* item ); /** * Simply calls reparent. */ void moveItem( DataItem* item, DirItem* newParent ); void moveItems( const QList& itemList, DirItem* newParent ); DirItem* addEmptyDir( const QString& name, DirItem* parent ); QString treatWhitespace( const QString& ); virtual BurnJob* newBurnJob( JobHandler* hdl, QObject* parent = 0 ); MultiSessionMode multiSessionMode() const { return m_multisessionMode; } void setMultiSessionMode( MultiSessionMode mode ); int dataMode() const { return m_dataMode; } void setDataMode( int m ) { m_dataMode = m; } void setVerifyData( bool b ) { m_verifyData = b; } bool verifyData() const { return m_verifyData; } static bool nameAlreadyInDir( const QString&, DirItem* ); /** * Most of the options that map to the mkisofs parameters are grouped * together in the IsoOptions class to allow easy saving to and loading * from a KConfig object. */ const IsoOptions& isoOptions() const { return m_isoOptions; } void setIsoOptions( const IsoOptions& ); QList bootImages() { return m_bootImages; } DataItem* bootCataloge() { return m_bootCataloge; } DirItem* bootImageDir(); /** * Create a boot item and also create a boot catalogue file in case none * exists in the project. * * Calling this method has the same effect like creating a new BootItem * instance manually and then calling createBootCatalogeItem. * * \return The new boot item on success or 0 in case a file with the same * name already exists. */ BootItem* createBootItem( const QString& filename, DirItem* bootDir = 0 ); /** * Create a new boot catalog item. * For now this is not called automatically for internal reasons. * * Call this if you create boot items manually instead of using createBootItem. * * The boot catalog is automatically deleted once the last boot item is removed * from the doc. * * \return The new boot catalog item or the old one if it already exists. */ DataItem* createBootCatalogeItem( DirItem* bootDir ); /** * This will prepare the filenames as written to the image. * These filenames are saved in DataItem::writtenName */ void prepareFilenames(); /** * Returns true if filenames need to be cut due to the limitations of Joliet. * * This is only valid after a call to @p prepareFilenames() */ bool needToCutFilenames() const { return m_needToCutFilenames; } QList needToCutFilenameItems() const { return m_needToCutFilenameItems; } /** * Imports a session into the project. This will create SessionImportItems * and properly set the imported session size. * Some settings will be adjusted to the imported session (joliet, rr). * * Be aware that this method is blocking. * * \return true if the old session was successfully imported, false if no * session could be found. * * \see clearImportedSession() */ bool importSession( Device::Device*, int session ); /** * The session number that has been imported. * \return The number of the imported session or 0 if no session information * was available (last track imported) or -1 if no session was imported. */ int importedSession() const; /** * Searches for an item by it's local path. * * NOT IMPLEMENTED YET! * * \return The items that correspond to the specified local path. */ QList findItemByLocalPath( const QString& path ) const; public Q_SLOTS: virtual void addUrls( const KUrl::List& urls ); /** * Add urls syncroneously * This method adds files recursively including symlinks, hidden, and system files. * If a file already exists the new file's name will be appended a number. */ virtual void addUrlsToDir( const KUrl::List& urls, K3b::DirItem* dir ); void clearImportedSession(); /** * Just a convience method to prevent using setIsoOptions for this * often used value. */ void setVolumeID( const QString& ); Q_SIGNALS: void aboutToRemoveItem( K3b::DataItem* ); void aboutToAddItem( K3b::DirItem* futureParent, K3b::DataItem* ); void itemRemoved( K3b::DataItem* ); void itemAdded( K3b::DataItem* ); protected: /** reimplemented from Doc */ virtual bool loadDocumentData( QDomElement* root ); /** reimplemented from Doc */ virtual bool saveDocumentData( QDomElement* ); void saveDocumentDataOptions( QDomElement& optionsElem ); void saveDocumentDataHeader( QDomElement& headerElem ); bool loadDocumentDataOptions( QDomElement optionsElem ); bool loadDocumentDataHeader( QDomElement optionsElem ); FileCompilationSizeHandler* m_sizeHandler; // FileCompilationSizeHandler* m_oldSessionSizeHandler; KIO::filesize_t m_oldSessionSize; private: void prepareFilenamesInDir( DirItem* dir ); void createSessionImportItems( const Iso9660Directory*, DirItem* parent ); /** * used by DirItem to inform about removed items. */ void aboutToRemoveItemFromDir( DirItem* parent, DataItem* removedItem ); void aboutToAddItemToDir( DirItem* parent, DataItem* addedItem ); void itemRemovedFromDir( DirItem* parent, DataItem* removedItem ); void itemAddedToDir( DirItem* parent, DataItem* addedItem ); /** * load recursivly */ bool loadDataItem( QDomElement& e, DirItem* parent ); /** * save recursivly */ void saveDataItem( DataItem* item, QDomDocument* doc, QDomElement* parent ); void informAboutNotFoundFiles(); // FIXME: move all the members into a private d-pointer structure QStringList m_notFoundFiles; QStringList m_noPermissionFiles; RootItem* m_root; int m_dataMode; bool m_verifyData; IsoOptions m_isoOptions; MultiSessionMode m_multisessionMode; QList m_oldSession; int m_importedSession; // boot cd stuff DataItem* m_bootCataloge; QList m_bootImages; bool m_bExistingItemsReplaceAll; bool m_bExistingItemsIgnoreAll; bool m_needToCutFilenames; QList m_needToCutFilenameItems; friend class MixedDoc; friend class DirItem; }; } #endif diff --git a/libk3b/projects/datacd/k3bdatajob.cpp b/libk3b/projects/datacd/k3bdatajob.cpp index f70f3b039..6dad2b161 100644 --- a/libk3b/projects/datacd/k3bdatajob.cpp +++ b/libk3b/projects/datacd/k3bdatajob.cpp @@ -1,1070 +1,1116 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bdatajob.h" #include "k3bdatadoc.h" #include "k3bisoimager.h" #include "k3bdatamultisessionparameterjob.h" - +#include "k3bchecksumpipe.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K3b::DataJob::Private { public: Private() : usedWritingApp(K3b::WRITING_APP_CDRECORD), - verificationJob(0) { + verificationJob( 0 ), + pipe( 0 ) { } K3b::DataDoc* doc; bool initializingImager; bool imageFinished; bool canceled; KTemporaryFile* tocFile; int usedDataMode; K3b::WritingApp usedWritingApp; K3b::WritingMode usedWritingMode; int copies; int copiesDone; K3b::VerificationJob* verificationJob; K3b::FileSplitter imageFile; - K3b::ActivePipe pipe; + K3b::ActivePipe* pipe; K3b::DataMultiSessionParameterJob* multiSessionParameterJob; }; K3b::DataJob::DataJob( K3b::DataDoc* doc, K3b::JobHandler* hdl, QObject* parent ) : K3b::BurnJob( hdl, parent ) { d = new Private; d->multiSessionParameterJob = new K3b::DataMultiSessionParameterJob( doc, this, this ); connectSubJob( d->multiSessionParameterJob, SLOT( slotMultiSessionParamterSetupDone( bool ) ), SIGNAL( newTask( const QString& ) ), SIGNAL( newSubTask( const QString& ) ) ); d->doc = doc; m_writerJob = 0; d->tocFile = 0; m_isoImager = 0; d->imageFinished = true; } + K3b::DataJob::~DataJob() { + delete d->pipe; delete d->tocFile; delete d; } K3b::Doc* K3b::DataJob::doc() const { return d->doc; } K3b::Device::Device* K3b::DataJob::writer() const { if( doc()->onlyCreateImages() ) return 0; // no writer needed -> no blocking on K3b::BurnJob else return doc()->burner(); } void K3b::DataJob::start() { + kDebug(); jobStarted(); d->canceled = false; d->imageFinished = false; d->copies = d->doc->copies(); d->copiesDone = 0; prepareImager(); if( d->doc->dummy() ) { d->doc->setVerifyData( false ); d->copies = 1; } emit newTask( i18n("Preparing data") ); // there is no harm in setting these even if we write on-the-fly d->imageFile.setName( d->doc->tempDir() ); - d->pipe.readFromIODevice( &d->imageFile ); d->multiSessionParameterJob->start(); } void K3b::DataJob::slotMultiSessionParamterSetupDone( bool success ) { if ( success ) { prepareWriting(); } else { if ( d->multiSessionParameterJob->hasBeenCanceled() ) { emit canceled(); } jobFinished( false ); } } void K3b::DataJob::prepareWriting() { + kDebug(); if( !d->doc->onlyCreateImages() && ( d->multiSessionParameterJob->usedMultiSessionMode() == K3b::DataDoc::CONTINUE || d->multiSessionParameterJob->usedMultiSessionMode() == K3b::DataDoc::FINISH ) ) { unsigned int nextSessionStart = d->multiSessionParameterJob->nextSessionStart(); // for some reason cdrdao needs 150 additional sectors in the ms info if( writingApp() == K3b::WRITING_APP_CDRDAO ) { nextSessionStart += 150; } m_isoImager->setMultiSessionInfo( QString().sprintf( "%u,%u", d->multiSessionParameterJob->previousSessionStart(), nextSessionStart ), d->multiSessionParameterJob->importPreviousSession() ? d->doc->burner() : 0 ); } else { m_isoImager->setMultiSessionInfo( QString(), 0 ); } d->initializingImager = true; m_isoImager->init(); } void K3b::DataJob::writeImage() { + kDebug(); d->initializingImager = false; emit burning(false); // get image file path if( d->doc->tempDir().isEmpty() ) d->doc->setTempDir( K3b::findUniqueFilePrefix( d->doc->isoOptions().volumeID() ) + ".iso" ); // TODO: check if the image file is part of the project and if so warn the user // and append some number to make the path unique. + // + // Check the image file + if( !d->doc->onTheFly() || d->doc->onlyCreateImages() ) { + d->imageFile.setName( d->doc->tempDir() ); + if( !d->imageFile.open( QIODevice::WriteOnly ) ) { + emit infoMessage( i18n("Could not open %1 for writing", d->doc->tempDir() ), ERROR ); + cleanup(); + jobFinished(false); + return; + } + } + emit newTask( i18n("Creating image file") ); emit newSubTask( i18n("Track 1 of 1") ); emit infoMessage( i18n("Creating image file in %1",d->doc->tempDir()), INFO ); - m_isoImager->writeToImageFile( d->doc->tempDir() ); m_isoImager->start(); + startPipe(); +} + + +void K3b::DataJob::startPipe() +{ + kDebug(); + // + // Open the active pipe which does the streaming + // + delete d->pipe; + if ( d->imageFinished || !d->doc->verifyData() ) + d->pipe = new K3b::ActivePipe(); + else + d->pipe = new K3b::ChecksumPipe(); + + if( d->imageFinished || ( d->doc->onTheFly() && !d->doc->onlyCreateImages() ) ) + d->pipe->writeTo( m_writerJob->ioDevice(), true ); + else + d->pipe->writeTo( &d->imageFile, true ); + + if ( d->imageFinished ) + d->pipe->readFrom( &d->imageFile, true ); + else + d->pipe->readFrom( m_isoImager->ioDevice(), true ); + + d->pipe->open( true ); } bool K3b::DataJob::startOnTheFlyWriting() { + kDebug(); if( prepareWriterJob() ) { if( startWriterJob() ) { - // try a direct connection between the processes - if( m_writerJob->fd() != -1 ) - m_isoImager->writeToFd( m_writerJob->fd() ); d->initializingImager = false; m_isoImager->start(); + startPipe(); return true; } } return false; } void K3b::DataJob::cancel() { emit infoMessage( i18n("Writing canceled."), K3b::Job::ERROR ); emit canceled(); if( m_writerJob && m_writerJob->active() ) { // // lets wait for the writer job to finish // and let it finish the job for good. // cancelAll(); } else { // // Just cancel all and return - // This is bad design as we should wait for all subjobs to finish + // FIXME: This is bad design as we should wait for all subjobs to finish // cancelAll(); jobFinished( false ); } } void K3b::DataJob::slotIsoImagerPercent( int p ) { if( d->doc->onlyCreateImages() ) { emit subPercent( p ); emit percent( p ); } else if( !d->doc->onTheFly() ) { double totalTasks = d->copies; double tasksDone = d->copiesDone; // =0 when creating an image if( d->doc->verifyData() ) { totalTasks*=2; tasksDone*=2; } if( !d->doc->onTheFly() ) { totalTasks+=1.0; } emit subPercent( p ); emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } } void K3b::DataJob::slotIsoImagerFinished( bool success ) { + kDebug(); if( d->initializingImager ) { if( success ) { if( d->doc->onTheFly() && !d->doc->onlyCreateImages() ) { if( !startOnTheFlyWriting() ) { cancelAll(); jobFinished( false ); } } else { writeImage(); } } else { if( m_isoImager->hasBeenCanceled() ) emit canceled(); jobFinished( false ); } } else { - // tell the writer that there won't be more data - if( d->doc->onTheFly() && m_writerJob ) - m_writerJob->closeFd(); - if( !d->doc->onTheFly() || d->doc->onlyCreateImages() ) { if( success ) { - emit infoMessage( i18n("Image successfully created in %1",d->doc->tempDir()), K3b::Job::SUCCESS ); + emit infoMessage( i18n("Image successfully created in %1", d->doc->tempDir()), K3b::Job::SUCCESS ); d->imageFinished = true; if( d->doc->onlyCreateImages() ) { jobFinished( true ); } else { if( prepareWriterJob() ) { startWriterJob(); - d->pipe.writeToFd( m_writerJob->fd(), true ); - d->pipe.open(true); + startPipe(); } } } else { if( m_isoImager->hasBeenCanceled() ) emit canceled(); else emit infoMessage( i18n("Error while creating ISO image"), ERROR ); cancelAll(); jobFinished( false ); } } else if( !success ) { // on-the-fly // // In case the imager failed let's make sure the writer does not emit an unusable // error message. // if( m_writerJob && m_writerJob->active() ) m_writerJob->setSourceUnreadable( true ); // there is one special case which we need to handle here: the iso imager might be canceled // FIXME: the iso imager should not be able to cancel itself if( m_isoImager->hasBeenCanceled() && !this->hasBeenCanceled() ) cancel(); } } } bool K3b::DataJob::startWriterJob() { + kDebug(); if( d->doc->dummy() ) emit newTask( i18n("Simulating") ); else if( d->copies > 1 ) emit newTask( i18n("Writing Copy %1",d->copiesDone+1) ); else emit newTask( i18n("Writing") ); emit burning(true); m_writerJob->start(); return true; } void K3b::DataJob::slotWriterJobPercent( int p ) { double totalTasks = d->copies; double tasksDone = d->copiesDone; if( d->doc->verifyData() ) { totalTasks*=2; tasksDone*=2; } if( !d->doc->onTheFly() ) { totalTasks+=1.0; tasksDone+=1.0; } emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } void K3b::DataJob::slotWriterNextTrack( int t, int tt ) { emit newSubTask( i18n("Writing Track %1 of %2",t,tt) ); } void K3b::DataJob::slotWriterJobFinished( bool success ) { - d->pipe.close(); + kDebug(); // // This is a little workaround for the bad cancellation handling in this job // see cancel() // if( d->canceled ) { if( active() ) jobFinished( false ); } if( success ) { // allright // the writerJob should have emitted the "simulation/writing successful" signal if( d->doc->verifyData() ) { if( !d->verificationJob ) { d->verificationJob = new K3b::VerificationJob( this, this ); connect( d->verificationJob, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( d->verificationJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( d->verificationJob, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( d->verificationJob, SIGNAL(percent(int)), this, SLOT(slotVerificationProgress(int)) ); connect( d->verificationJob, SIGNAL(percent(int)), this, SIGNAL(subPercent(int)) ); connect( d->verificationJob, SIGNAL(finished(bool)), this, SLOT(slotVerificationFinished(bool)) ); connect( d->verificationJob, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } d->verificationJob->clear(); d->verificationJob->setDevice( d->doc->burner() ); d->verificationJob->setGrownSessionSize( m_isoImager->size() ); - d->verificationJob->addTrack( 0, m_isoImager->checksum(), m_isoImager->size() ); + d->verificationJob->addTrack( 0, static_cast( d->pipe )->checksum(), m_isoImager->size() ); emit burning(false); emit newTask( i18n("Verifying written data") ); d->verificationJob->start(); } else { d->copiesDone++; if( d->copiesDone < d->copies ) { if( !d->doc->burner()->eject() ) { blockingInformation( i18n("K3b was unable to eject the written disk. Please do so manually.") ); } bool failed = false; if( d->doc->onTheFly() ) failed = !startOnTheFlyWriting(); else failed = !prepareWriterJob() || !startWriterJob(); if( failed ) { cancel(); } else if( !d->doc->onTheFly() ) { - d->pipe.writeToFd( m_writerJob->fd(), true ); - d->pipe.open(true); + d->pipe->writeTo( m_writerJob->ioDevice(), true ); + d->pipe->open(true); } } else { cleanup(); if ( k3bcore->globalSettings()->ejectMedia() ) { K3b::Device::eject( d->doc->burner() ); } jobFinished(true); } } } else { cancelAll(); jobFinished( false ); } } void K3b::DataJob::slotVerificationProgress( int p ) { double totalTasks = d->copies*2; double tasksDone = d->copiesDone*2 + 1; // the writing of the current copy has already been finished if( !d->doc->onTheFly() ) { totalTasks+=1.0; tasksDone+=1.0; } emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } void K3b::DataJob::slotVerificationFinished( bool success ) { + kDebug(); d->copiesDone++; // reconnect our imager which we deconnected for the verification connectImager(); if( k3bcore->globalSettings()->ejectMedia() || d->copiesDone < d->copies ) K3b::Device::eject( d->doc->burner() ); if( !d->canceled && d->copiesDone < d->copies ) { bool failed = false; if( d->doc->onTheFly() ) failed = !startOnTheFlyWriting(); else failed = !prepareWriterJob() || !startWriterJob(); if( failed ) cancel(); else if( !d->doc->onTheFly() ) { - d->pipe.writeToFd( m_writerJob->fd(), true ); - d->pipe.open(true); + d->pipe->writeTo( m_writerJob->ioDevice(), true ); + d->pipe->open(true); } } else { cleanup(); jobFinished( success ); } } void K3b::DataJob::setWriterJob( K3b::AbstractWriter* writer ) { + kDebug(); // FIXME: progressedsize for multiple copies m_writerJob = writer; connect( m_writerJob, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_writerJob, SIGNAL(percent(int)), this, SLOT(slotWriterJobPercent(int)) ); connect( m_writerJob, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); connect( m_writerJob, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) ); connect( m_writerJob, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); connect( m_writerJob, SIGNAL(nextTrack(int, int)), this, SLOT(slotWriterNextTrack(int, int)) ); connect( m_writerJob, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); connect( m_writerJob, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); connect( m_writerJob, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)), this, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)) ); connect( m_writerJob, SIGNAL(finished(bool)), this, SLOT(slotWriterJobFinished(bool)) ); connect( m_writerJob, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( m_writerJob, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } void K3b::DataJob::setImager( K3b::IsoImager* imager ) { + kDebug(); if( m_isoImager != imager ) { delete m_isoImager; m_isoImager = imager; connectImager(); } } void K3b::DataJob::connectImager() { + kDebug(); m_isoImager->disconnect( this ); connect( m_isoImager, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_isoImager, SIGNAL(percent(int)), this, SLOT(slotIsoImagerPercent(int)) ); connect( m_isoImager, SIGNAL(finished(bool)), this, SLOT(slotIsoImagerFinished(bool)) ); connect( m_isoImager, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } void K3b::DataJob::prepareImager() { + kDebug(); if( !m_isoImager ) setImager( new K3b::IsoImager( d->doc, this, this ) ); } bool K3b::DataJob::prepareWriterJob() { + kDebug(); if( m_writerJob ) { delete m_writerJob; m_writerJob = 0; } // if we append a new session we asked for an appendable cd already if( !waitForMedium() ) { return false; } // It seems as if cdrecord is not able to append sessions in dao mode whereas cdrdao is if( d->usedWritingApp == K3b::WRITING_APP_CDRECORD ) { if( !setupCdrecordJob() ) { return false; } } else if ( d->usedWritingApp == K3b::WRITING_APP_CDRDAO ) { if ( !setupCdrdaoJob() ) { return false; } } else { if ( !setupGrowisofsJob() ) { return false; } } return true; } void K3b::DataJob::cancelAll() { d->canceled = true; m_isoImager->cancel(); if( m_writerJob ) m_writerJob->cancel(); if( d->verificationJob ) d->verificationJob->cancel(); - d->pipe.close(); + d->pipe->close(); cleanup(); } bool K3b::DataJob::waitForMedium() { // start with all media types supported by the writer int m = d->doc->supportedMediaTypes() & d->doc->burner()->writeCapabilities(); // if everything goes wrong we are left with no possible media to request if ( !m ) { emit infoMessage( i18n( "Internal Error: No medium type fits. This project cannot be burned." ), ERROR ); return false; } emit newSubTask( i18n("Waiting for a medium") ); int foundMedium = waitForMedia( d->doc->burner(), usedMultiSessionMode() == K3b::DataDoc::CONTINUE || usedMultiSessionMode() == K3b::DataDoc::FINISH ? K3b::Device::STATE_INCOMPLETE : K3b::Device::STATE_EMPTY, m ); if( foundMedium < 0 || hasBeenCanceled() ) { return false; } if( foundMedium == 0 ) { emit infoMessage( i18n("Forced by user. Writing will continue without further checks."), INFO ); return true; } else { return analyseBurnMedium( foundMedium ); } } bool K3b::DataJob::analyseBurnMedium( int foundMedium ) { // ------------------------------- // CD-R(W) // ------------------------------- if ( foundMedium & K3b::Device::MEDIA_CD_ALL ) { emit infoMessage( i18n( "Writing %1" , K3b::Device::mediaTypeString( foundMedium ) ), INFO ); // first of all we determine the data mode if( d->doc->dataMode() == K3b::DATA_MODE_AUTO ) { if( !d->doc->onlyCreateImages() && ( usedMultiSessionMode() == K3b::DataDoc::CONTINUE || usedMultiSessionMode() == K3b::DataDoc::FINISH ) ) { // try to get the last track's datamode // we already asked for an appendable cdr when fetching // the ms info kDebug() << "(K3b::DataJob) determining last track's datamode..."; // FIXME: use the DeviceHandler K3b::Device::Toc toc = d->doc->burner()->readToc(); if( toc.isEmpty() ) { kDebug() << "(K3b::DataJob) could not retrieve toc."; emit infoMessage( i18n("Unable to determine the last track's datamode. Using default."), ERROR ); d->usedDataMode = K3b::DATA_MODE_2; } else { if( toc.back().mode() == K3b::Device::Track::MODE1 ) d->usedDataMode = K3b::DATA_MODE_1; else d->usedDataMode = K3b::DATA_MODE_2; kDebug() << "(K3b::DataJob) using datamode: " << (d->usedDataMode == K3b::DATA_MODE_1 ? "mode1" : "mode2") << endl; } } else if( usedMultiSessionMode() == K3b::DataDoc::NONE ) d->usedDataMode = K3b::DATA_MODE_1; else d->usedDataMode = K3b::DATA_MODE_2; } else d->usedDataMode = d->doc->dataMode(); // determine the writing mode if( d->doc->writingMode() == K3b::WRITING_MODE_AUTO ) { // TODO: put this into the cdreocrdwriter and decide based on the size of the // track if( writer()->dao() && d->usedDataMode == K3b::DATA_MODE_1 && usedMultiSessionMode() == K3b::DataDoc::NONE ) d->usedWritingMode = K3b::WRITING_MODE_DAO; else d->usedWritingMode = K3b::WRITING_MODE_TAO; } else d->usedWritingMode = d->doc->writingMode(); if ( writingApp() == K3b::WRITING_APP_GROWISOFS ) { emit infoMessage( i18n( "Cannot write %1 media using %2. Falling back to default application." , QString("CD") , QString("growisofs") ), WARNING ); setWritingApp( K3b::WRITING_APP_DEFAULT ); } // cdrecord seems to have problems writing xa 1 disks in dao mode? At least on my system! if( writingApp() == K3b::WRITING_APP_DEFAULT ) { if( d->usedWritingMode == K3b::WRITING_MODE_DAO ) { if( usedMultiSessionMode() != K3b::DataDoc::NONE ) d->usedWritingApp = K3b::WRITING_APP_CDRDAO; else if( d->usedDataMode == K3b::DATA_MODE_2 ) d->usedWritingApp = K3b::WRITING_APP_CDRDAO; else d->usedWritingApp = K3b::WRITING_APP_CDRECORD; } else d->usedWritingApp = K3b::WRITING_APP_CDRECORD; } else { d->usedWritingApp = writingApp(); } } // ------------------------------- // DVD Plus // ------------------------------- else if ( foundMedium & K3b::Device::MEDIA_DVD_ALL ) { if ( writingApp() == K3b::WRITING_APP_CDRDAO ) { emit infoMessage( i18n( "Cannot write %1 media using %2. Falling back to default application.", K3b::Device::mediaTypeString( foundMedium, true ), "cdrdao" ), WARNING ); setWritingApp( K3b::WRITING_APP_DEFAULT ); } // make sure that we use the proper parameters for cdrecord d->usedDataMode = K3b::DATA_MODE_1; d->usedWritingApp = writingApp(); // let's default to cdrecord for the time being (except for special cases below) if ( d->usedWritingApp == K3b::WRITING_APP_DEFAULT ) { d->usedWritingApp = K3b::WRITING_APP_CDRECORD; } if( foundMedium & K3b::Device::MEDIA_DVD_PLUS_ALL ) { if( d->doc->dummy() ) { if( !questionYesNo( i18n("DVD+R(W) media do not support write simulation. " "Do you really want to continue? The media will be written " "for real."), i18n("No Simulation with DVD+R(W)") ) ) { return false; } d->doc->setDummy( false ); emit newTask( i18n("Writing") ); } if( d->doc->writingMode() != K3b::WRITING_MODE_AUTO && d->doc->writingMode() != K3b::WRITING_MODE_RES_OVWR ) emit infoMessage( i18n("Writing mode ignored when writing DVD+R(W) media."), INFO ); d->usedWritingMode = K3b::WRITING_MODE_DAO; // since cdrecord uses -sao for DVD+R(W) if( foundMedium & K3b::Device::MEDIA_DVD_PLUS_RW ) { if( usedMultiSessionMode() == K3b::DataDoc::NONE || usedMultiSessionMode() == K3b::DataDoc::START ) emit infoMessage( i18n("Writing DVD+RW."), INFO ); else { emit infoMessage( i18n("Growing ISO9660 filesystem on DVD+RW."), INFO ); // we can only do this with growisofs d->usedWritingApp = K3b::WRITING_APP_GROWISOFS; } } else if( foundMedium & K3b::Device::MEDIA_DVD_PLUS_R_DL ) emit infoMessage( i18n("Writing Double Layer DVD+R."), INFO ); else emit infoMessage( i18n("Writing DVD+R."), INFO ); } // ------------------------------- // DVD Minus // ------------------------------- else if ( foundMedium & K3b::Device::MEDIA_DVD_MINUS_ALL ) { if( d->doc->dummy() && !d->doc->burner()->dvdMinusTestwrite() ) { if( !questionYesNo( i18n("Your writer (%1 %2) does not support simulation with DVD-R(W) media. " "Do you really want to continue? The media will be written " "for real.", d->doc->burner()->vendor(), d->doc->burner()->description()), i18n("No Simulation with DVD-R(W)") ) ) { return false; } d->doc->setDummy( false ); } // RESTRICTED OVERWRITE // -------------------- if( foundMedium & K3b::Device::MEDIA_DVD_RW_OVWR ) { d->usedWritingMode = K3b::WRITING_MODE_RES_OVWR; if( usedMultiSessionMode() == K3b::DataDoc::NONE || usedMultiSessionMode() == K3b::DataDoc::START ) { // FIXME: can cdrecord handle this? emit infoMessage( i18n("Writing DVD-RW in restricted overwrite mode."), INFO ); } else { emit infoMessage( i18n("Growing ISO9660 filesystem on DVD-RW in restricted overwrite mode."), INFO ); // we can only do this with growisofs d->usedWritingApp = K3b::WRITING_APP_GROWISOFS; } } // NORMAL // ------ else { // FIXME: DVD-R DL jump and stuff if( d->doc->writingMode() == K3b::WRITING_MODE_DAO ) { d->usedWritingMode = K3b::WRITING_MODE_DAO; emit infoMessage( i18n("Writing %1 in DAO mode.", K3b::Device::mediaTypeString(foundMedium, true) ), INFO ); } else { // check if the writer supports writing sequential and thus multisession (on -1 the burner cannot handle // features and we simply ignore it and hope for the best) if( d->doc->burner()->featureCurrent( K3b::Device::FEATURE_INCREMENTAL_STREAMING_WRITABLE ) == 0 ) { if( !questionYesNo( i18n("Your writer (%1 %2) does not support Incremental Streaming with %3 " "media. Multisession will not be possible. Continue anyway?", d->doc->burner()->vendor(), d->doc->burner()->description(), K3b::Device::mediaTypeString(foundMedium, true) ), i18n("No Incremental Streaming") ) ) { return false; } else { d->usedWritingMode = K3b::WRITING_MODE_DAO; emit infoMessage( i18n("Writing %1 in DAO mode.", K3b::Device::mediaTypeString(foundMedium, true) ), INFO ); } } else { d->usedWritingMode = K3b::WRITING_MODE_INCR_SEQ; if( !(foundMedium & (K3b::Device::MEDIA_DVD_RW|K3b::Device::MEDIA_DVD_RW_OVWR|K3b::Device::MEDIA_DVD_RW_SEQ)) && d->doc->writingMode() == K3b::WRITING_MODE_RES_OVWR ) emit infoMessage( i18n("Restricted Overwrite is not possible with DVD-R media."), INFO ); emit infoMessage( i18n("Writing %1 in incremental mode.", K3b::Device::mediaTypeString(foundMedium, true) ), INFO ); } } } } } // -------------------- // Blu-Ray // -------------------- else if ( foundMedium & K3b::Device::MEDIA_BD_ALL ) { d->usedWritingApp = writingApp(); if ( d->usedWritingApp == K3b::WRITING_APP_DEFAULT ) { if ( k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "blu-ray" ) ) d->usedWritingApp = K3b::WRITING_APP_CDRECORD; else d->usedWritingApp = K3b::WRITING_APP_GROWISOFS; } if ( d->usedWritingApp == K3b::WRITING_APP_CDRECORD && !k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "blu-ray" ) ) { d->usedWritingApp = K3b::WRITING_APP_GROWISOFS; } // FIXME: what do we need to take care of with BD media? emit infoMessage( i18n( "Writing %1" , K3b::Device::mediaTypeString( foundMedium, true ) ), INFO ); } return true; } QString K3b::DataJob::jobDescription() const { if( d->doc->onlyCreateImages() ) { return i18n("Creating Data Image File"); } else if( d->doc->multiSessionMode() == K3b::DataDoc::NONE || d->doc->multiSessionMode() == K3b::DataDoc::AUTO ) { return i18n("Writing Data Project") + ( d->doc->isoOptions().volumeID().isEmpty() ? QString() : QString( " (%1)" ).arg(d->doc->isoOptions().volumeID()) ); } else { return i18n("Writing Multisession Project") + ( d->doc->isoOptions().volumeID().isEmpty() ? QString() : QString( " (%1)" ).arg(d->doc->isoOptions().volumeID()) ); } } QString K3b::DataJob::jobDetails() const { if( d->doc->copies() > 1 && !d->doc->dummy() && !(d->doc->multiSessionMode() == K3b::DataDoc::CONTINUE || d->doc->multiSessionMode() == K3b::DataDoc::FINISH) ) return i18np("ISO9660 Filesystem (Size: %1) - %2 copy", "ISO9660 Filesystem (Size: %1) - %2 copies", KIO::convertSize( d->doc->size() ), d->doc->copies() ); else return i18n( "ISO9660 Filesystem (Size: %1)", KIO::convertSize( d->doc->size() ) ); } K3b::DataDoc::MultiSessionMode K3b::DataJob::usedMultiSessionMode() const { return d->multiSessionParameterJob->usedMultiSessionMode(); } void K3b::DataJob::cleanup() { if( !d->doc->onTheFly() && d->doc->removeImages() ) { if( QFile::exists( d->doc->tempDir() ) ) { d->imageFile.remove(); emit infoMessage( i18n("Removed image file %1",d->doc->tempDir()), K3b::Job::SUCCESS ); } } if( d->tocFile ) { delete d->tocFile; d->tocFile = 0; } } bool K3b::DataJob::hasBeenCanceled() const { return d->canceled; } bool K3b::DataJob::setupCdrecordJob() { + kDebug(); K3b::CdrecordWriter* writer = new K3b::CdrecordWriter( d->doc->burner(), this, this ); // cdrecord manpage says that "not all" writers are able to write // multisession disks in dao mode. That means there are writers that can. // Does it really make sence to write Data ms cds in DAO mode since writing the // first session of a cd-extra in DAO mode is no problem with my writer while // writing the second data session is only possible in TAO mode. if( d->usedWritingMode == K3b::WRITING_MODE_DAO && usedMultiSessionMode() != K3b::DataDoc::NONE ) emit infoMessage( i18n("Most writers do not support writing " "multisession CDs in DAO mode."), INFO ); writer->setWritingMode( d->usedWritingMode ); writer->setSimulate( d->doc->dummy() ); writer->setBurnSpeed( d->doc->speed() ); // multisession if( usedMultiSessionMode() == K3b::DataDoc::START || usedMultiSessionMode() == K3b::DataDoc::CONTINUE ) { writer->addArgument("-multi"); } if( d->doc->onTheFly() && ( usedMultiSessionMode() == K3b::DataDoc::CONTINUE || usedMultiSessionMode() == K3b::DataDoc::FINISH ) ) writer->addArgument("-waiti"); if( d->usedDataMode == K3b::DATA_MODE_1 ) writer->addArgument( "-data" ); else { if( k3bcore->externalBinManager()->binObject("cdrecord") && k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) ) writer->addArgument( "-xa" ); else writer->addArgument( "-xa1" ); } writer->addArgument( QString("-tsize=%1s").arg(m_isoImager->size()) )->addArgument("-"); setWriterJob( writer ); return true; } bool K3b::DataJob::setupCdrdaoJob() { // create cdrdao job K3b::CdrdaoWriter* writer = new K3b::CdrdaoWriter( d->doc->burner(), this, this ); writer->setCommand( K3b::CdrdaoWriter::WRITE ); writer->setSimulate( d->doc->dummy() ); writer->setBurnSpeed( d->doc->speed() ); // multisession writer->setMulti( usedMultiSessionMode() == K3b::DataDoc::START || usedMultiSessionMode() == K3b::DataDoc::CONTINUE ); // now write the tocfile if( d->tocFile ) delete d->tocFile; d->tocFile = new KTemporaryFile(); d->tocFile->setSuffix( ".toc" ); d->tocFile->open(); QTextStream s( d->tocFile ); if( d->usedDataMode == K3b::DATA_MODE_1 ) { s << "CD_ROM" << "\n"; s << "\n"; s << "TRACK MODE1" << "\n"; } else { s << "CD_ROM_XA" << "\n"; s << "\n"; s << "TRACK MODE2_FORM1" << "\n"; } s << "DATAFILE \"-\" " << m_isoImager->size()*2048 << "\n"; d->tocFile->close(); writer->setTocFile( d->tocFile->fileName() ); setWriterJob( writer ); return true; } bool K3b::DataJob::setupGrowisofsJob() { K3b::GrowisofsWriter* writer = new K3b::GrowisofsWriter( d->doc->burner(), this, this ); // these do only make sense with DVD-R(W) writer->setSimulate( d->doc->dummy() ); writer->setBurnSpeed( d->doc->speed() ); // Andy said incremental sequential is the default mode and it seems uses have more problems with DAO anyway // BUT: I also had a report that incremental sequential produced unreadable media! if( d->doc->writingMode() == K3b::WRITING_MODE_DAO ) // || ( d->doc->writingMode() == K3b::WRITING_MODE_AUTO && // usedMultiSessionMode() == K3b::DataDoc::NONE ) ) writer->setWritingMode( K3b::WRITING_MODE_DAO ); writer->setMultiSession( usedMultiSessionMode() == K3b::DataDoc::CONTINUE || usedMultiSessionMode() == K3b::DataDoc::FINISH ); writer->setCloseDvd( usedMultiSessionMode() == K3b::DataDoc::NONE || usedMultiSessionMode() == K3b::DataDoc::FINISH ); writer->setImageToWrite( QString() ); // read from stdin writer->setTrackSize( m_isoImager->size() ); if( usedMultiSessionMode() != K3b::DataDoc::NONE ) { // // growisofs wants a valid -C parameter for multisession, so we get it from the // K3b::MsInfoFetcher (see K3b::DataJob::slotMsInfoFetched) // writer->setMultiSessionInfo( m_isoImager->multiSessionInfo() ); } setWriterJob( writer ); return true; } #include "k3bdatajob.moc" diff --git a/libk3b/projects/datacd/k3bdatajob.h b/libk3b/projects/datacd/k3bdatajob.h index 1f258d6c4..68dadb13a 100644 --- a/libk3b/projects/datacd/k3bdatajob.h +++ b/libk3b/projects/datacd/k3bdatajob.h @@ -1,108 +1,109 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3BDATAJOB_H #define K3BDATAJOB_H #include #include #include class QString; namespace K3b { class AbstractWriter; class IsoImager; namespace Device { class Device; } class DataJob : public BurnJob { Q_OBJECT public: DataJob( DataDoc*, JobHandler*, QObject* parent = 0 ); virtual ~DataJob(); Doc* doc() const; Device::Device* writer() const; virtual bool hasBeenCanceled() const; virtual QString jobDescription() const; virtual QString jobDetails() const; public Q_SLOTS: void cancel(); void start(); /** * Used to specify a non-default writer. * If this does notget called DataJob determines * the writer itself. */ void setWriterJob( AbstractWriter* ); void setImager( IsoImager* ); protected Q_SLOTS: void slotIsoImagerFinished( bool success ); void slotIsoImagerPercent(int); void slotWriterJobPercent( int p ); void slotWriterNextTrack( int t, int tt ); void slotWriterJobFinished( bool success ); void slotVerificationProgress( int ); void slotVerificationFinished( bool ); void writeImage(); void cancelAll(); /** * Just a little helper method that makes subclassing easier. * Basically used for DVD writing. */ virtual bool waitForMedium(); private Q_SLOTS: void slotMultiSessionParamterSetupDone( bool ); protected: virtual bool prepareWriterJob(); virtual void prepareImager(); virtual void cleanup(); DataDoc::MultiSessionMode usedMultiSessionMode() const; AbstractWriter* m_writerJob; IsoImager* m_isoImager; private: bool analyseBurnMedium( int medium ); bool startWriterJob(); bool startOnTheFlyWriting(); void prepareWriting(); void connectImager(); bool setupCdrecordJob(); bool setupCdrdaoJob(); bool setupGrowisofsJob(); + void startPipe(); class Private; Private* d; }; } #endif diff --git a/libk3b/projects/datacd/k3bisoimager.cpp b/libk3b/projects/datacd/k3bisoimager.cpp index d0c32dd13..013b9d00f 100644 --- a/libk3b/projects/datacd/k3bisoimager.cpp +++ b/libk3b/projects/datacd/k3bisoimager.cpp @@ -1,1188 +1,1118 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 #include #include "k3bisoimager.h" #include "k3bdiritem.h" #include "k3bbootitem.h" #include "k3bdatadoc.h" #include "k3bdatapreparationjob.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int K3b::IsoImager::s_imagerSessionCounter = 0; class K3b::IsoImager::Private { public: - Private() - : pipe(0) { - } - - ~Private() { - delete pipe; - } - - QString imagePath; K3b::FileSplitter imageFile; const K3b::ExternalBin* mkisofsBin; enum LinkHandling { KEEP_ALL, FOLLOW, DISCARD_ALL, DISCARD_BROKEN }; int usedLinkHandling; bool knownError; - K3b::ActivePipe* pipe; K3b::DataPreparationJob* dataPreparationJob; }; K3b::IsoImager::IsoImager( K3b::DataDoc* doc, K3b::JobHandler* hdl, QObject* parent ) : K3b::Job( hdl, parent ), m_pathSpecFile(0), m_rrHideFile(0), m_jolietHideFile(0), m_sortWeightFile(0), m_process( 0 ), m_processExited(false), m_doc( doc ), m_noDeepDirectoryRelocation( false ), m_importSession( false ), m_device(0), - m_mkisofsPrintSizeResult( 0 ), - m_fdToWriteTo(-1) + m_mkisofsPrintSizeResult( 0 ) { d = new Private(); d->dataPreparationJob = new K3b::DataPreparationJob( doc, this, this ); connectSubJob( d->dataPreparationJob, SLOT(slotDataPreparationDone(bool)), DEFAULT_SIGNAL_CONNECTION ); } K3b::IsoImager::~IsoImager() { cleanup(); delete d; } bool K3b::IsoImager::active() const { return K3b::Job::active(); } -void K3b::IsoImager::writeToFd( int fd ) -{ - m_fdToWriteTo = fd; -} - - -void K3b::IsoImager::writeToImageFile( const QString& path ) -{ - d->imagePath = path; - m_fdToWriteTo = -1; -} - - void K3b::IsoImager::slotReceivedStderr( const QString& line ) { parseMkisofsOutput( line ); emit debuggingOutput( "mkisofs", line ); } void K3b::IsoImager::handleMkisofsProgress( int p ) { emit percent( p ); } void K3b::IsoImager::handleMkisofsInfoMessage( const QString& line, int type ) { emit infoMessage( line, type ); if( type == ERROR ) d->knownError = true; } void K3b::IsoImager::slotProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { kDebug() << k_funcinfo; m_processExited = true; - d->pipe->close(); - - emit debuggingOutput( "K3b::IsoImager", - QString("Pipe throughput: %1 bytes read, %2 bytes written.") - .arg(d->pipe->bytesRead()).arg(d->pipe->bytesWritten()) ); - if( d->imageFile.isOpen() ) { d->imageFile.close(); if( m_canceled || (exitCode != 0) ) { d->imageFile.remove(); emit infoMessage( i18n("Removed incomplete image file %1.",d->imageFile.name()), WARNING ); } } if( m_canceled ) { emit canceled(); jobFinished(false); } else { if( exitStatus == QProcess::NormalExit ) { if( exitCode == 0 ) { jobFinished( !mkisofsReadError() ); } else { switch( exitCode ) { case 104: // connection reset by peer // This only happens if cdrecord does not finish successfully // so we may leave the error handling to it meaning we handle this // as a known error break; case 2: // mkisofs seems to have a bug that prevents to use filenames // that contain one or more backslashes // mkisofs 1.14 has the bug, 1.15a40 not // TODO: find out the version that fixed the bug if( m_containsFilesWithMultibleBackslashes && !k3bcore->externalBinManager()->binObject( "mkisofs" )->hasFeature( "backslashed_filenames" ) ) { emit infoMessage( i18n("Due to a bug in mkisofs <= 1.15a40, K3b is unable to handle " "filenames that contain more than one backslash:"), ERROR ); break; } // otherwise just fall through default: if( !d->knownError && !mkisofsReadError() ) { emit infoMessage( i18n("%1 returned an unknown error (code %2).",QString("mkisofs"), exitCode ), K3b::Job::ERROR ); emit infoMessage( i18n("Please send me an email with the last output."), K3b::Job::ERROR ); } } jobFinished( false ); } } else { - emit infoMessage( i18n("%1 did not exit cleanly.",QString("mkisofs")), ERROR ); + emit infoMessage( i18n("%1 crashed.",QString("mkisofs")), ERROR ); jobFinished( false ); } } cleanup(); } void K3b::IsoImager::cleanup() { // remove all temp files delete m_pathSpecFile; delete m_rrHideFile; delete m_jolietHideFile; delete m_sortWeightFile; // remove boot-images-temp files for( QStringList::iterator it = m_tempFiles.begin(); it != m_tempFiles.end(); ++it ) QFile::remove( *it ); m_tempFiles.clear(); m_pathSpecFile = m_jolietHideFile = m_rrHideFile = m_sortWeightFile = 0; - delete m_process; - m_process = 0; - clearDummyDirs(); } void K3b::IsoImager::init() { jobStarted(); cleanup(); d->dataPreparationJob->start(); } void K3b::IsoImager::slotDataPreparationDone( bool success ) { if( success ) { // // We always calculate the image size. It does not take long and at least the mixed job needs it // anyway // startSizeCalculation(); } else { if( d->dataPreparationJob->hasBeenCanceled() ) { m_canceled = true; emit canceled(); } jobFinished( false ); } } void K3b::IsoImager::calculateSize() { jobStarted(); startSizeCalculation(); } void K3b::IsoImager::startSizeCalculation() { d->mkisofsBin = initMkisofs(); if( !d->mkisofsBin ) { jobFinished( false ); return; } initVariables(); delete m_process; - m_process = new K3b::Process(); - m_process->setRunPrivileged(true); + m_process = new K3b::Process( this ); m_process->setSplitStdout(true); emit debuggingOutput( QLatin1String( "Used versions" ), QLatin1String( "mkisofs: " ) + d->mkisofsBin->version ); *m_process << d->mkisofsBin; if( !prepareMkisofsFiles() || !addMkisofsParameters(true) ) { cleanup(); jobFinished( false ); return; } // add empty dummy dir since one path-spec is needed // ??? Seems it is not needed after all. At least mkisofs 1.14 and above don't need it. ??? // *m_process << dummyDir(); kDebug() << "***** mkisofs calculate size parameters:"; QString s = m_process->joinedArgs(); kDebug() << s << endl << flush; emit debuggingOutput("mkisofs calculate size command:", s); // since output changed during mkisofs version changes we grab both // stdout and stderr // mkisofs version >= 1.15 (don't know about 1.14!) // the extends on stdout (as lonely number) // and error and warning messages on stderr // mkisofs >= 1.13 // everything is written to stderr // last line is: "Total extents scheduled to be written = XXXXX" - // TODO: use K3b::Process::OutputCollector instead iof our own two slots. - - connect( m_process, SIGNAL(receivedStderr(K3Process*, char*, int)), - this, SLOT(slotCollectMkisofsPrintSizeStderr(K3Process*, char*, int)) ); connect( m_process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotCollectMkisofsPrintSizeStdout(const QString&)) ); + connect( m_process, SIGNAL(stderrLine(const QString&)), + this, SLOT(slotCollectMkisofsPrintSizeStderr(const QString&)) ); connect( m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotMkisofsPrintSizeFinished()) ); // we also want error messages connect( m_process, SIGNAL(stderrLine( const QString& )), this, SLOT(slotReceivedStderr( const QString& )) ); m_collectedMkisofsPrintSizeStdout = QString(); m_collectedMkisofsPrintSizeStderr = QString(); m_mkisofsPrintSizeResult = 0; - if( !m_process->start( K3Process::AllOutput ) ) { + if( !m_process->start( KProcess::SeparateChannels ) ) { emit infoMessage( i18n("Could not start %1.",QString("mkisofs")), K3b::Job::ERROR ); cleanup(); jobFinished( false ); return; } } -void K3b::IsoImager::slotCollectMkisofsPrintSizeStderr(K3Process*, char* data , int len) +void K3b::IsoImager::slotCollectMkisofsPrintSizeStderr( const QString& line ) { - emit debuggingOutput( "mkisofs", QString::fromLocal8Bit( data, len ) ); - m_collectedMkisofsPrintSizeStderr.append( QString::fromLocal8Bit( data, len ) ); + m_collectedMkisofsPrintSizeStderr.append( line + '\n' ); } void K3b::IsoImager::slotCollectMkisofsPrintSizeStdout( const QString& line ) { // newer versions of mkisofs output additional lines of junk before the size :( // so we only use the last line emit debuggingOutput( "mkisofs", line ); m_collectedMkisofsPrintSizeStdout = line; } void K3b::IsoImager::slotMkisofsPrintSizeFinished() { if( m_canceled ) { emit canceled(); jobFinished( false ); return; } bool success = true; // if m_collectedMkisofsPrintSizeStdout is not empty we have a recent version of // mkisofs and parsing is very easy (s.o.) if( !m_collectedMkisofsPrintSizeStdout.isEmpty() ) { kDebug() << "(K3b::IsoImager) iso size: " << m_collectedMkisofsPrintSizeStdout; m_mkisofsPrintSizeResult = m_collectedMkisofsPrintSizeStdout.toInt( &success ); } else { // parse the stderr output // I hope parsing the last line is enough! int pos = m_collectedMkisofsPrintSizeStderr.lastIndexOf( "extents scheduled to be written" ); if( pos == -1 ) success = false; else m_mkisofsPrintSizeResult = m_collectedMkisofsPrintSizeStderr.mid( pos+33 ).toInt( &success ); } emit debuggingOutput( "K3b::IsoImager", QString("mkisofs print size result: %1 (%2 bytes)") .arg(m_mkisofsPrintSizeResult) .arg(quint64(m_mkisofsPrintSizeResult)*2048ULL) ); cleanup(); if( success ) { jobFinished( true ); } else { m_mkisofsPrintSizeResult = 0; kDebug() << "(K3b::IsoImager) Parsing mkisofs -print-size failed: " << m_collectedMkisofsPrintSizeStdout; emit infoMessage( i18n("Could not determine size of resulting image file."), ERROR ); jobFinished( false ); } } void K3b::IsoImager::initVariables() { m_containsFilesWithMultibleBackslashes = false; m_processExited = false; m_canceled = false; d->knownError = false; // determine symlink handling // follow links superseeds discard all links which superseeds discard broken links // without rockridge we follow the links or discard all if( m_doc->isoOptions().followSymbolicLinks() ) d->usedLinkHandling = Private::FOLLOW; else if( m_doc->isoOptions().discardSymlinks() ) d->usedLinkHandling = Private::DISCARD_ALL; else if( m_doc->isoOptions().createRockRidge() ) { if( m_doc->isoOptions().discardBrokenSymlinks() ) d->usedLinkHandling = Private::DISCARD_BROKEN; else d->usedLinkHandling = Private::KEEP_ALL; } else { d->usedLinkHandling = Private::FOLLOW; } m_sessionNumber = s_imagerSessionCounter++; } void K3b::IsoImager::start() { jobStarted(); cleanup(); d->mkisofsBin = initMkisofs(); if( !d->mkisofsBin ) { jobFinished( false ); return; } initVariables(); - m_process = new K3b::Process(); - m_process->setRunPrivileged(true); + m_process = new K3b::Process( this ); + m_process->setFlags( K3bQProcess::RawStdout ); *m_process << d->mkisofsBin; // prepare the filenames as written to the image m_doc->prepareFilenames(); if( !prepareMkisofsFiles() || !addMkisofsParameters() ) { cleanup(); jobFinished( false ); return; } connect( m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessExited(int, QProcess::ExitStatus)) ); - connect( m_process, SIGNAL(stderrLine( const QString& )), this, SLOT(slotReceivedStderr( const QString& )) ); - // - // Check the image file - if( m_fdToWriteTo == -1 ) { - d->imageFile.setName( d->imagePath ); - if( !d->imageFile.open( QIODevice::WriteOnly ) ) { - emit infoMessage( i18n("Could not open %1 for writing",d->imagePath), ERROR ); - cleanup(); - jobFinished(false); - return; - } - } - - // - // Open the active pipe which does the streaming - delete d->pipe; - if( m_doc->verifyData() ) - d->pipe = new K3b::ChecksumPipe(); - else - d->pipe = new K3b::ActivePipe(); - - if( m_fdToWriteTo == -1 ) - d->pipe->writeToIODevice( &d->imageFile ); - else - d->pipe->writeToFd( m_fdToWriteTo ); - d->pipe->open(); - m_process->writeToFd( d->pipe->in() ); - - kDebug() << "***** mkisofs parameters:\n"; QString s = m_process->joinedArgs(); kDebug() << s << endl << flush; emit debuggingOutput("mkisofs command:", s); - if( !m_process->start( K3Process::AllOutput ) ) { + if( !m_process->start( KProcess::SeparateChannels ) ) { // something went wrong when starting the program // it "should" be the executable kDebug() << "(K3b::IsoImager) could not start mkisofs"; emit infoMessage( i18n("Could not start %1.",QString("mkisofs")), K3b::Job::ERROR ); jobFinished( false ); cleanup(); } } void K3b::IsoImager::cancel() { m_canceled = true; if( m_process && !m_processExited ) { m_process->kill(); } else if( active() ) { emit canceled(); jobFinished(false); } } void K3b::IsoImager::setMultiSessionInfo( const QString& info, K3b::Device::Device* dev ) { m_multiSessionInfo = info; m_device = dev; } QString K3b::IsoImager::multiSessionInfo() const { return m_multiSessionInfo; } K3b::Device::Device* K3b::IsoImager::multiSessionImportDevice() const { return m_device; } // iso9660 + RR use some latin1 variant. So we need to cut the desc fields // counting 8bit chars. The GUI should take care of restricting the length // and the charset static void truncateTheHardWay( QString& s, int max ) { QByteArray cs = s.toUtf8(); cs.truncate(max); s = QString::fromUtf8( cs ); } bool K3b::IsoImager::addMkisofsParameters( bool printSize ) { // add multisession info if( !m_multiSessionInfo.isEmpty() ) { *m_process << "-cdrecord-params" << m_multiSessionInfo; if( m_device && !m_doc->isoOptions().doNotImportSession() ) { *m_process << "-prev-session" << m_device->blockDeviceName(); } } // add the arguments *m_process << "-gui"; *m_process << "-graft-points"; if( printSize ) *m_process << "-print-size" << "-quiet"; if( !m_doc->isoOptions().volumeID().isEmpty() ) { QString s = m_doc->isoOptions().volumeID(); truncateTheHardWay(s, 32); // ensure max length *m_process << "-volid" << s; } else { emit infoMessage( i18n("No volume id specified. Using default."), WARNING ); *m_process << "-volid" << "CDROM"; } QString s = m_doc->isoOptions().volumeSetId(); truncateTheHardWay(s, 128); // ensure max length *m_process << "-volset" << s; s = m_doc->isoOptions().applicationID(); truncateTheHardWay(s, 128); // ensure max length *m_process << "-appid" << s; s = m_doc->isoOptions().publisher(); truncateTheHardWay(s, 128); // ensure max length *m_process << "-publisher" << s; s = m_doc->isoOptions().preparer(); truncateTheHardWay(s, 128); // ensure max length *m_process << "-preparer" << s; s = m_doc->isoOptions().systemId(); truncateTheHardWay(s, 32); // ensure max length *m_process << "-sysid" << s; s = m_doc->isoOptions().abstractFile(); truncateTheHardWay(s, 37); // ensure max length if ( !s.isEmpty() ) *m_process << "-abstract" << s; s = m_doc->isoOptions().copyrightFile(); truncateTheHardWay(s, 37); // ensure max length if ( !s.isEmpty() ) *m_process << "-copyright" << s; s = m_doc->isoOptions().bibliographFile(); truncateTheHardWay(s, 37); // ensure max length if ( !s.isEmpty() ) *m_process << "-biblio" << s; int volsetSize = m_doc->isoOptions().volumeSetSize(); int volsetSeqNo = m_doc->isoOptions().volumeSetNumber(); if( volsetSeqNo > volsetSize ) { kDebug() << "(K3b::IsoImager) invalid volume set sequence number: " << volsetSeqNo << " with volume set size: " << volsetSize << endl; volsetSeqNo = volsetSize; } *m_process << "-volset-size" << QString::number(volsetSize); *m_process << "-volset-seqno" << QString::number(volsetSeqNo); if( m_sortWeightFile ) { *m_process << "-sort" << m_sortWeightFile->fileName(); } if( m_doc->isoOptions().createRockRidge() ) { if( m_doc->isoOptions().preserveFilePermissions() ) *m_process << "-rock"; else *m_process << "-rational-rock"; if( m_rrHideFile ) *m_process << "-hide-list" << m_rrHideFile->fileName(); } if( m_doc->isoOptions().createJoliet() ) { *m_process << "-joliet"; if( m_doc->isoOptions().jolietLong() ) *m_process << "-joliet-long"; if( m_jolietHideFile ) *m_process << "-hide-joliet-list" << m_jolietHideFile->fileName(); } if( m_doc->isoOptions().doNotCacheInodes() ) *m_process << "-no-cache-inodes"; // // Check if we have files > 2 GB and enable udf in that case. // bool filesGreaterThan2Gb = false; bool filesGreaterThan4Gb = false; K3b::DataItem* item = m_doc->root(); while( (item = item->nextSibling()) ) { if ( item->isFile() && item->size() >= 0xFFFFFFFFULL ) { filesGreaterThan4Gb = filesGreaterThan2Gb = true; break; } else if( item->isFile() && item->size() > 2LL*1024LL*1024LL*1024LL ) { filesGreaterThan2Gb = true; if ( filesGreaterThan4Gb ) break; } } if ( filesGreaterThan4Gb ) { if ( !d->mkisofsBin->hasFeature( "no-4gb-limit" ) ) { emit infoMessage( i18n( "Found files bigger than 4 GB. K3b needs at least %1 to continue." , QString( "mkisofs >= 2.01.01a33 / genisoimage >= 1.1.4" ) ), ERROR ); return false; } } if( filesGreaterThan2Gb ) { emit infoMessage( i18n("Found files bigger than 2 GB. These files will only be fully accessible if mounted with UDF."), WARNING ); // in genisoimage 1.1.3 "they" silently introduced this aweful parameter if ( d->mkisofsBin->hasFeature( "genisoimage" ) && d->mkisofsBin->version >= K3b::Version( 1, 1, 3 ) ) { *m_process << "-allow-limited-size"; } } bool udf = m_doc->isoOptions().createUdf(); if( !udf && filesGreaterThan2Gb ) { emit infoMessage( i18n("Enabling UDF extension."), INFO ); udf = true; } if( udf ) *m_process << "-udf"; if( m_doc->isoOptions().ISOuntranslatedFilenames() ) { *m_process << "-untranslated-filenames"; } else { if( m_doc->isoOptions().ISOallowPeriodAtBegin() ) *m_process << "-allow-leading-dots"; if( m_doc->isoOptions().ISOallow31charFilenames() ) *m_process << "-full-iso9660-filenames"; if( m_doc->isoOptions().ISOomitVersionNumbers() && !m_doc->isoOptions().ISOmaxFilenameLength() ) *m_process << "-omit-version-number"; if( m_doc->isoOptions().ISOrelaxedFilenames() ) *m_process << "-relaxed-filenames"; if( m_doc->isoOptions().ISOallowLowercase() ) *m_process << "-allow-lowercase"; if( m_doc->isoOptions().ISOnoIsoTranslate() ) *m_process << "-no-iso-translate"; if( m_doc->isoOptions().ISOallowMultiDot() ) *m_process << "-allow-multidot"; if( m_doc->isoOptions().ISOomitTrailingPeriod() ) *m_process << "-omit-period"; } if( m_doc->isoOptions().ISOmaxFilenameLength() ) *m_process << "-max-iso9660-filenames"; if( m_noDeepDirectoryRelocation ) *m_process << "-disable-deep-relocation"; // We do our own following // if( m_doc->isoOptions().followSymbolicLinks() || !m_doc->isoOptions().createRockRidge() ) // *m_process << "-follow-links"; if( m_doc->isoOptions().createTRANS_TBL() ) *m_process << "-translation-table"; if( m_doc->isoOptions().hideTRANS_TBL() ) *m_process << "-hide-joliet-trans-tbl"; int isoLevel = m_doc->isoOptions().ISOLevel(); if ( filesGreaterThan4Gb && isoLevel < 3 ) { emit infoMessage( i18n( "Setting iso level to 3 to support files bigger than 4 GB." ), WARNING ); isoLevel = 3; } *m_process << "-iso-level" << QString::number( isoLevel ); *m_process << "-path-list" << m_pathSpecFile->fileName(); // boot stuff if( !m_doc->bootImages().isEmpty() ) { bool first = true; QList bootItems = m_doc->bootImages(); Q_FOREACH( K3b::BootItem* bootItem, bootItems ) { if( !first ) *m_process << "-eltorito-alt-boot"; *m_process << "-eltorito-boot"; *m_process << bootItem->writtenPath(); if( bootItem->imageType() == K3b::BootItem::HARDDISK ) { *m_process << "-hard-disk-boot"; } else if( bootItem->imageType() == K3b::BootItem::NONE ) { *m_process << "-no-emul-boot"; if( bootItem->loadSegment() > 0 ) *m_process << "-boot-load-seg" << QString::number(bootItem->loadSegment()); if( bootItem->loadSize() > 0 ) *m_process << "-boot-load-size" << QString::number(bootItem->loadSize()); } if( bootItem->imageType() != K3b::BootItem::NONE && bootItem->noBoot() ) *m_process << "-no-boot"; if( bootItem->bootInfoTable() ) *m_process << "-boot-info-table"; first = false; } *m_process << "-eltorito-catalog" << m_doc->bootCataloge()->writtenPath(); } // additional parameters from config const QStringList& params = k3bcore->externalBinManager()->binObject( "mkisofs" )->userParameters(); for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) *m_process << *it; return true; } int K3b::IsoImager::writePathSpec() { delete m_pathSpecFile; m_pathSpecFile = new KTemporaryFile(); if ( m_pathSpecFile->open() ) { kDebug() << "Opened path spec file" << m_pathSpecFile->fileName(); QTextStream s( m_pathSpecFile ); // recursive path spec writing return writePathSpecForDir( m_doc->root(), s ); } else { return -1; } } int K3b::IsoImager::writePathSpecForDir( K3b::DirItem* dirItem, QTextStream& stream ) { if( !m_noDeepDirectoryRelocation && dirItem->depth() > 7 ) { kDebug() << "(K3b::IsoImager) found directory depth > 7. Enabling no deep directory relocation."; m_noDeepDirectoryRelocation = true; } // now create the graft points int num = 0; Q_FOREACH( K3b::DataItem* item, dirItem->children() ) { bool writeItem = item->writeToCd(); if( item->isSymLink() ) { if( d->usedLinkHandling == Private::DISCARD_ALL || ( d->usedLinkHandling == Private::DISCARD_BROKEN && !item->isValid() ) ) writeItem = false; else if( d->usedLinkHandling == Private::FOLLOW ) { QFileInfo f( K3b::resolveLink( item->localPath() ) ); if( !f.exists() ) { emit infoMessage( i18n("Could not follow link %1 to non-existing file %2. Skipping...", item->k3bName(), f.filePath()), WARNING ); writeItem = false; } else if( f.isDir() ) { emit infoMessage( i18n("Ignoring link %1 to folder %2. K3b is unable to follow links to folders.", item->k3bName(), f.filePath()), WARNING ); writeItem = false; } } } else if( item->isFile() ) { QFileInfo f( item->localPath() ); if( !f.exists() ) { emit infoMessage( i18n("Could not find file %1. Skipping...",item->localPath()), WARNING ); writeItem = false; } else if( !f.isReadable() ) { emit infoMessage( i18n("Could not read file %1. Skipping...",item->localPath()), WARNING ); writeItem = false; } } if( writeItem ) { num++; // some versions of mkisofs seem to have a bug that prevents to use filenames // that contain one or more backslashes if( item->writtenPath().contains("\\") ) m_containsFilesWithMultibleBackslashes = true; if( item->isDir() ) { stream << escapeGraftPoint( item->writtenPath() ) << "=" << escapeGraftPoint( dummyDir( static_cast(item) ) ) << "\n"; int x = writePathSpecForDir( dynamic_cast(item), stream ); if( x >= 0 ) num += x; else return -1; } else { writePathSpecForFile( static_cast(item), stream ); } } } return num; } void K3b::IsoImager::writePathSpecForFile( K3b::FileItem* item, QTextStream& stream ) { stream << escapeGraftPoint( item->writtenPath() ) << "="; if( m_doc->bootImages().contains( dynamic_cast(item) ) ) { // boot-image-backup-hack // create temp file KTemporaryFile temp; temp.open(); QString tempPath = temp.fileName(); temp.remove(); if( !KIO::NetAccess::file_copy( KUrl(item->localPath()), tempPath ) ) { emit infoMessage( i18n("Failed to backup boot image file %1",item->localPath()), ERROR ); return; } static_cast(item)->setTempPath( tempPath ); m_tempFiles.append(tempPath); stream << escapeGraftPoint( tempPath ) << "\n"; } else if( item->isSymLink() && d->usedLinkHandling == Private::FOLLOW ) stream << escapeGraftPoint( K3b::resolveLink( item->localPath() ) ) << "\n"; else stream << escapeGraftPoint( item->localPath() ) << "\n"; } bool K3b::IsoImager::writeRRHideFile() { delete m_rrHideFile; m_rrHideFile = new KTemporaryFile(); m_rrHideFile->open(); QTextStream s( m_rrHideFile ); K3b::DataItem* item = m_doc->root(); while( item ) { if( item->hideOnRockRidge() ) { if( !item->isDir() ) // hiding directories does not work (all dirs point to the dummy-dir) s << escapeGraftPoint( item->localPath() ) << endl; } item = item->nextSibling(); } return true; } bool K3b::IsoImager::writeJolietHideFile() { delete m_jolietHideFile; m_jolietHideFile = new KTemporaryFile(); m_jolietHideFile->open(); QTextStream s( m_jolietHideFile ); K3b::DataItem* item = m_doc->root(); while( item ) { if( item->hideOnRockRidge() ) { if( !item->isDir() ) // hiding directories does not work (all dirs point to the dummy-dir but we could introduce a second hidden dummy dir) s << escapeGraftPoint( item->localPath() ) << endl; } item = item->nextSibling(); } return true; } bool K3b::IsoImager::writeSortWeightFile() { delete m_sortWeightFile; m_sortWeightFile = new KTemporaryFile(); m_sortWeightFile->open(); QTextStream s( m_sortWeightFile ); // // We need to write the local path in combination with the sort weight // mkisofs will take care of multiple entries for one local file and always // use the highest weight // K3b::DataItem* item = m_doc->root(); while( (item = item->nextSibling()) ) { // we skip the root here if( item->sortWeight() != 0 ) { if( m_doc->bootImages().contains( dynamic_cast(item) ) ) { // boot-image-backup-hack s << escapeGraftPoint( static_cast(item)->tempPath() ) << " " << item->sortWeight() << endl; } else if( item->isDir() ) { // // Since we use dummy dirs for all directories in the filesystem and mkisofs uses the local path // for sorting we need to create a different dummy dir for every sort weight value. // s << escapeGraftPoint( dummyDir( static_cast(item) ) ) << " " << item->sortWeight() << endl; } else s << escapeGraftPoint( item->localPath() ) << " " << item->sortWeight() << endl; } } return true; } QString K3b::IsoImager::escapeGraftPoint( const QString& str ) { QString enc = str; // // mkisofs manpage (-graft-points) is incorrect (as of mkisofs 2.01.01) // // Actually an equal sign needs to be escaped with one backslash only // Single backslashes inside a filename can be used without change // while single backslashes at the end of a filename need to be escaped // with two backslashes. // // There is one more problem though: the name in the iso tree can never // in any number of backslashes. mkisofs simply cannot handle it. So we // need to remove these slashes somewhere or ignore those files (we do // that in K3b::DataDoc::addUrls) // // // we do not use QString::replace to have full control // this might be slow since QString::insert is slow but we don't care // since this is only called to prepare the iso creation which is not // time critical. :) // int pos = 0; while( pos < enc.length() ) { // escape every equal sign with one backslash if( enc[pos] == '=' ) { enc.insert( pos, "\\" ); pos += 2; } else if( enc[pos] == '\\' ) { // escape every occurrence of two backslashes with two backslashes if( pos+1 < enc.length() && enc[pos+1] == '\\' ) { enc.insert( pos, "\\\\" ); pos += 4; } // escape the last single backslash in the filename (see above) else if( pos == enc.length()-1 ) { enc.insert( pos, "\\" ); pos += 2; } else ++pos; } else ++pos; } // enc.replace( "\\\\", "\\\\\\\\" ); // enc.replace( "=", "\\=" ); return enc; } bool K3b::IsoImager::prepareMkisofsFiles() { // write path spec file // ---------------------------------------------------- int num = writePathSpec(); if( num < 0 ) { emit infoMessage( i18n("Could not write temporary file"), K3b::Job::ERROR ); return false; } else if( num == 0 ) { emit infoMessage( i18n("No files to be written."), K3b::Job::ERROR ); return false; } if( m_doc->isoOptions().createRockRidge() ) { if( !writeRRHideFile() ) { emit infoMessage( i18n("Could not write temporary file"), K3b::Job::ERROR ); return false; } } if( m_doc->isoOptions().createJoliet() ) { if( !writeJolietHideFile() ) { emit infoMessage( i18n("Could not write temporary file"), K3b::Job::ERROR ); return false ; } } if( !writeSortWeightFile() ) { emit infoMessage( i18n("Could not write temporary file"), K3b::Job::ERROR ); return false; } return true; } QString K3b::IsoImager::dummyDir( K3b::DirItem* dir ) { // // since we use virtual folders in order to have folders with different weight factors and different // permissions we create different dummy dirs to be passed to mkisofs // QDir _appDir( KStandardDirs::locateLocal( "appdata", "temp/" ) ); // // create a unique isoimager session id // This might become important in case we will allow multiple instances of the isoimager // to run at the same time. // QString jobId = qApp->sessionId() + "_" + QString::number( m_sessionNumber ); if( !_appDir.cd( jobId ) ) { _appDir.mkdir( jobId ); _appDir.cd( jobId ); } QString name( "dummydir_" ); name += QString::number( dir->sortWeight() ); bool perm = false; k3b_struct_stat statBuf; if( !dir->localPath().isEmpty() ) { // permissions if( k3b_stat( QFile::encodeName(dir->localPath()), &statBuf ) == 0 ) { name += "_"; name += QString::number( statBuf.st_uid ); name += "_"; name += QString::number( statBuf.st_gid ); name += "_"; name += QString::number( statBuf.st_mode ); name += "_"; name += QString::number( statBuf.st_mtime ); perm = true; } } if( !_appDir.cd( name ) ) { kDebug() << "(K3b::IsoImager) creating dummy dir: " << _appDir.absolutePath() << "/" << name; _appDir.mkdir( name ); _appDir.cd( name ); if( perm ) { ::chmod( QFile::encodeName( _appDir.absolutePath() ), statBuf.st_mode ); ::chown( QFile::encodeName( _appDir.absolutePath() ), statBuf.st_uid, statBuf.st_gid ); struct utimbuf tb; tb.actime = tb.modtime = statBuf.st_mtime; ::utime( QFile::encodeName( _appDir.absolutePath() ), &tb ); } } return _appDir.absolutePath() + "/"; } void K3b::IsoImager::clearDummyDirs() { QString jobId = qApp->sessionId() + "_" + QString::number( m_sessionNumber ); QDir appDir( KStandardDirs::locateLocal( "appdata", "temp/" ) ); if( appDir.cd( jobId ) ) { QStringList dummyDirEntries = appDir.entryList( QStringList() << "dummydir*", QDir::Dirs ); for( QStringList::iterator it = dummyDirEntries.begin(); it != dummyDirEntries.end(); ++it ) appDir.rmdir( *it ); appDir.cdUp(); appDir.rmdir( jobId ); } } -QByteArray K3b::IsoImager::checksum() const +bool K3b::IsoImager::hasBeenCanceled() const { - if( K3b::ChecksumPipe* p = dynamic_cast( d->pipe ) ) - return p->checksum(); - else - return QByteArray(); + return m_canceled; } -bool K3b::IsoImager::hasBeenCanceled() const +QIODevice* K3b::IsoImager::ioDevice() const { - return m_canceled; + return m_process; } #include "k3bisoimager.moc" diff --git a/libk3b/projects/datacd/k3bisoimager.h b/libk3b/projects/datacd/k3bisoimager.h index 7b013a457..9015db20c 100644 --- a/libk3b/projects/datacd/k3bisoimager.h +++ b/libk3b/projects/datacd/k3bisoimager.h @@ -1,190 +1,173 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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. */ #ifndef K3B_ISO_IMAGER_H #define K3B_ISO_IMAGER_H #include #include "k3bmkisofshandler.h" +#include "k3bprocess.h" #include #include -#include class QTextStream; -class K3Process; class KTemporaryFile; namespace K3b { class DataDoc; class DirItem; class FileItem; - class Process; class IsoImager : public Job, public MkisofsHandler { Q_OBJECT public: IsoImager( DataDoc*, JobHandler*, QObject* parent = 0 ); virtual ~IsoImager(); virtual bool active() const; int size() const { return m_mkisofsPrintSizeResult; } virtual bool hasBeenCanceled() const; - /** - * Get the checksum calculated during the creation of the image. - */ - QByteArray checksum() const; + QIODevice* ioDevice() const; public Q_SLOTS: /** * Starts the actual image creation. Always run init() * before starting the image creation */ virtual void start(); virtual void cancel(); /** * Initialize the image creator. This calculates the image size and performs * some checks on the project. * * The initialization process also finishes with the finished() signal just * like a normal job operation. Get the calculated image size via size() */ virtual void init(); /** * Only calculates the size of the image without the additional checks in * init() * * Use this if you need to recalculate the image size for example if the * multisession info changed. */ virtual void calculateSize(); - /** - * lets the isoimager write directly into fd instead of writing - * to an image file. - * Be aware that this only makes sense before starting the job. - * To disable just set @p fd to -1 - */ - void writeToFd( int fd ); - - void writeToImageFile( const QString& path ); - /** * If dev == 0 IsoImager will ignore the data in the previous session. * This is usable for CD-Extra. */ void setMultiSessionInfo( const QString&, Device::Device* dev = 0 ); QString multiSessionInfo() const; Device::Device* multiSessionImportDevice() const; Device::Device* device() const { return m_device; } DataDoc* doc() const { return m_doc; } protected: virtual void handleMkisofsProgress( int ); virtual void handleMkisofsInfoMessage( const QString&, int ); virtual bool addMkisofsParameters( bool printSize = false ); /** * calls writePathSpec, writeRRHideFile, and writeJolietHideFile */ bool prepareMkisofsFiles(); /** * The dummy dir is used to create dirs on the iso-filesystem. * * @return an empty dummy dir for use with DirItems. */ QString dummyDir( DirItem* ); void outputData(); void initVariables(); virtual void cleanup(); void clearDummyDirs(); /** * @returns The number of entries written or -1 on error */ virtual int writePathSpec(); bool writeRRHideFile(); bool writeJolietHideFile(); bool writeSortWeightFile(); // used by writePathSpec virtual int writePathSpecForDir( DirItem* dirItem, QTextStream& stream ); virtual void writePathSpecForFile( FileItem*, QTextStream& stream ); QString escapeGraftPoint( const QString& str ); KTemporaryFile* m_pathSpecFile; KTemporaryFile* m_rrHideFile; KTemporaryFile* m_jolietHideFile; KTemporaryFile* m_sortWeightFile; Process* m_process; bool m_processExited; bool m_canceled; protected Q_SLOTS: virtual void slotReceivedStderr( const QString& ); virtual void slotProcessExited( int, QProcess::ExitStatus ); private Q_SLOTS: - void slotCollectMkisofsPrintSizeStderr(K3Process*, char*, int); + void slotCollectMkisofsPrintSizeStderr( const QString& ); void slotCollectMkisofsPrintSizeStdout( const QString& ); void slotMkisofsPrintSizeFinished(); void slotDataPreparationDone( bool success ); private: void startSizeCalculation(); class Private; Private* d; DataDoc* m_doc; bool m_noDeepDirectoryRelocation; bool m_importSession; QString m_multiSessionInfo; Device::Device* m_device; // used for mkisofs -print-size parsing QString m_collectedMkisofsPrintSizeStdout; QString m_collectedMkisofsPrintSizeStderr; int m_mkisofsPrintSizeResult; QStringList m_tempFiles; - int m_fdToWriteTo; - bool m_containsFilesWithMultibleBackslashes; // used to create a unique session id static int s_imagerSessionCounter; int m_sessionNumber; }; } #endif diff --git a/libk3b/projects/datacd/k3bisooptions.cpp b/libk3b/projects/datacd/k3bisooptions.cpp index a8f6be38a..9c7e845fa 100644 --- a/libk3b/projects/datacd/k3bisooptions.cpp +++ b/libk3b/projects/datacd/k3bisooptions.cpp @@ -1,209 +1,209 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bisooptions.h" #include #include #include #include #include #include #include K3b::IsoOptions::IsoOptions() : m_volumeID( i18n( "K3b data project" ) ), m_applicationID( QString("K3B THE CD KREATOR (C) 1998-2007 SEBASTIAN TRUEG") ), m_systemId( K3b::systemName().toUpper() ), m_whiteSpaceTreatmentReplaceString( "_" ) { m_createRockRidge = true; m_createJoliet = true; m_createUdf = false; m_ISOallowLowercase = false; m_ISOallowPeriodAtBegin = false; m_ISOallow31charFilenames = true; m_ISOomitVersionNumbers = false; m_ISOomitTrailingPeriod = false; m_ISOmaxFilenameLength = false; m_ISOrelaxedFilenames = false; m_ISOnoIsoTranslate = false; m_ISOallowMultiDot = false; m_ISOuntranslatedFilenames = false; m_followSymbolicLinks = false; m_createTRANS_TBL = false; m_hideTRANS_TBL = false; m_jolietLong = true; m_doNotCacheInodes = true; m_doNotImportSession = false; - m_isoLevel = 2; + m_isoLevel = 3; m_discardSymlinks = false; m_discardBrokenSymlinks = false; m_preserveFilePermissions = false; m_whiteSpaceTreatment = noChange; m_volumeSetSize = 1; m_volumeSetNumber = 1; } void K3b::IsoOptions::save( KConfigGroup c, bool saveVolumeDesc ) { if( saveVolumeDesc ) { c.writeEntry( "volume id", m_volumeID ); c.writeEntry( "application id", m_applicationID ); c.writeEntry( "preparer", m_preparer ); c.writeEntry( "publisher", m_publisher ); c.writeEntry( "system id", m_systemId ); c.writeEntry( "volume set id", m_volumeSetId ); c.writeEntry( "volume set size", m_volumeSetSize ); c.writeEntry( "volume set number", m_volumeSetNumber ); c.writeEntry( "abstract file", m_abstractFile ); c.writeEntry( "copyright file", m_copyrightFile ); c.writeEntry( "bibliograph file", m_bibliographFile ); } c.writeEntry( "rock_ridge", m_createRockRidge ); c.writeEntry( "joliet", m_createJoliet ); c.writeEntry( "udf", m_createUdf ); // save iso-level c.writeEntry( "iso_level", m_isoLevel ); c.writeEntry( "create TRANS_TBL", m_createTRANS_TBL ); c.writeEntry( "hide TRANS_TBL", m_hideTRANS_TBL ); c.writeEntry( "untranslated filenames", m_ISOuntranslatedFilenames ); c.writeEntry( "allow 31 character filenames", m_ISOallow31charFilenames ); c.writeEntry( "max ISO filenames", m_ISOmaxFilenameLength ); c.writeEntry( "allow beginning period", m_ISOallowPeriodAtBegin ); c.writeEntry( "relaxed filenames", m_ISOrelaxedFilenames ); c.writeEntry( "omit version numbers", m_ISOomitVersionNumbers ); c.writeEntry( "omit trailing period", m_ISOomitTrailingPeriod ); c.writeEntry( "no iSO translation", m_ISOnoIsoTranslate ); c.writeEntry( "allow multiple dots", m_ISOallowMultiDot ); c.writeEntry( "allow lowercase filenames", m_ISOallowLowercase ); c.writeEntry( "follow symbolic links", m_followSymbolicLinks ); c.writeEntry( "joliet long", m_jolietLong ); c.writeEntry( "do not cache inodes", m_doNotCacheInodes ); c.writeEntry( "do not import last session", m_doNotImportSession ); // save whitespace-treatment switch( m_whiteSpaceTreatment ) { case strip: c.writeEntry( "white_space_treatment", "strip" ); break; case extended: c.writeEntry( "white_space_treatment", "extended" ); break; case replace: c.writeEntry( "white_space_treatment", "replace" ); break; default: c.writeEntry( "white_space_treatment", "noChange" ); } c.writeEntry( "whitespace replace string", m_whiteSpaceTreatmentReplaceString ); c.writeEntry( "discard symlinks", discardSymlinks() ); c.writeEntry( "discard broken symlinks", discardBrokenSymlinks() ); c.writeEntry( "preserve file permissions", m_preserveFilePermissions ); } K3b::IsoOptions K3b::IsoOptions::load( const KConfigGroup& c, bool loadVolumeDesc ) { K3b::IsoOptions options; if( loadVolumeDesc ) { options.setVolumeID( c.readEntry( "volume id", options.volumeID() ) ); options.setApplicationID( c.readEntry( "application id", options.applicationID() ) ); options.setPreparer( c.readEntry( "preparer", options.preparer() ) ); options.setPublisher( c.readEntry( "publisher", options.publisher() ) ); options.setSystemId( c.readEntry( "system id", options.systemId() ) ); options.setVolumeSetId( c.readEntry( "volume set id", options.volumeSetId() ) ); options.setVolumeSetSize( c.readEntry( "volume set size", options.volumeSetSize() ) ); options.setVolumeSetNumber( c.readEntry( "volume set number", options.volumeSetNumber() ) ); options.setAbstractFile( c.readEntry( "abstract file", options.abstractFile() ) ); options.setCoprightFile( c.readEntry( "copyright file", options.copyrightFile() ) ); options.setBibliographFile( c.readEntry( "bibliograph file", options.bibliographFile() ) ); } options.setCreateRockRidge( c.readEntry( "rock_ridge", options.createRockRidge() ) ); options.setCreateJoliet( c.readEntry( "joliet", options.createJoliet() ) ); options.setCreateUdf( c.readEntry( "udf", options.createUdf() ) ); options.setISOLevel( c.readEntry( "iso_level", options.ISOLevel() ) ); options.setCreateTRANS_TBL( c.readEntry( "create TRANS_TBL", options.createTRANS_TBL() ) ); options.setHideTRANS_TBL( c.readEntry( "hide TRANS_TBL", options.hideTRANS_TBL() ) ); // // We need to use the memeber variables here instead of the access methods // which do not return the actual value of the member variables but the value // representing the use in mkisofs (i.e. ISOomitVersionNumbers is also enabled // if ISOmaxFilenameLength is enabled. // options.setISOuntranslatedFilenames( c.readEntry( "untranslated filenames", options.m_ISOuntranslatedFilenames ) ); options.setISOallow31charFilenames( c.readEntry( "allow 31 character filenames", options.m_ISOallow31charFilenames ) ); options.setISOmaxFilenameLength( c.readEntry( "max ISO filenames", options.m_ISOmaxFilenameLength ) ); options.setISOallowPeriodAtBegin( c.readEntry( "allow beginning period", options.m_ISOallowPeriodAtBegin ) ); options.setISOrelaxedFilenames( c.readEntry( "relaxed filenames", options.m_ISOrelaxedFilenames ) ); options.setISOomitVersionNumbers( c.readEntry( "omit version numbers", options.m_ISOomitVersionNumbers ) ); options.setISOnoIsoTranslate( c.readEntry( "no iSO translation", options.m_ISOnoIsoTranslate ) ); options.setISOallowMultiDot( c.readEntry( "allow multiple dots", options.m_ISOallowMultiDot ) ); options.setISOallowLowercase( c.readEntry( "allow lowercase filenames", options.m_ISOallowLowercase ) ); options.setISOomitTrailingPeriod( c.readEntry( "omit trailing period", options.m_ISOomitTrailingPeriod ) ); options.setFollowSymbolicLinks( c.readEntry( "follow symbolic links", options.m_followSymbolicLinks ) ); options.setJolietLong( c.readEntry( "joliet long", options.jolietLong() ) ); options.setDoNotCacheInodes( c.readEntry( "do not cache inodes", options.doNotCacheInodes() ) ); options.setDoNotImportSession( c.readEntry( "no not import last session", options.doNotImportSession() ) ); QString w = c.readEntry( "white_space_treatment", "noChange" ); if( w == "replace" ) options.setWhiteSpaceTreatment( replace ); else if( w == "strip" ) options.setWhiteSpaceTreatment( strip ); else if( w == "extended" ) options.setWhiteSpaceTreatment( extended ); else options.setWhiteSpaceTreatment( noChange ); options.setWhiteSpaceTreatmentReplaceString( c.readEntry( "whitespace replace string", options.whiteSpaceTreatmentReplaceString() ) ); options.setDiscardSymlinks( c.readEntry("discard symlinks", options.discardSymlinks() ) ); options.setDiscardBrokenSymlinks( c.readEntry("discard broken symlinks", options.discardBrokenSymlinks() ) ); options.setPreserveFilePermissions( c.readEntry( "preserve file permissions", options.preserveFilePermissions() ) ); return options; } K3b::IsoOptions K3b::IsoOptions::defaults() { // let the constructor create defaults return K3b::IsoOptions(); } diff --git a/libk3b/projects/datacd/k3bmsinfofetcher.cpp b/libk3b/projects/datacd/k3bmsinfofetcher.cpp index a01a2889a..0c96a7ce8 100644 --- a/libk3b/projects/datacd/k3bmsinfofetcher.cpp +++ b/libk3b/projects/datacd/k3bmsinfofetcher.cpp @@ -1,245 +1,234 @@ /* * * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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 "k3bmsinfofetcher.h" #include #include #include #include #include #include #include +#include "k3bprocess.h" #include #include #include #include K3b::MsInfoFetcher::MsInfoFetcher( K3b::JobHandler* jh, QObject* parent ) : K3b::Job( jh, parent ), m_process(0), m_device(0), m_dvd(false) { } K3b::MsInfoFetcher::~MsInfoFetcher() { delete m_process; } void K3b::MsInfoFetcher::start() { jobStarted(); emit infoMessage( i18n("Searching previous session"), K3b::Job::INFO ); if( !k3bcore->externalBinManager()->foundBin( "cdrecord" ) ) { kDebug() << "(K3b::MsInfoFetcher) could not find cdrecord executable"; emit infoMessage( i18n("Could not find %1 executable.",QString("cdrecord")), K3b::Job::ERROR ); jobFinished(false); return; } if( m_device == 0 ) { kDebug() << "(K3b::MsInfoFetcher) internal error: No device set!"; jobFinished(false); return; } // // first we try to determine if it is a dvd. If so we need to // read the info on our own // connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::NG_DISKINFO, m_device ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotMediaDetectionFinished(K3b::Device::DeviceHandler*)) ); } void K3b::MsInfoFetcher::getMsInfo() { delete m_process; - m_process = new KProcess(this); + m_process = new Process(this); const K3b::ExternalBin* bin = 0; if( m_dvd ) { // already handled } else { bin = k3bcore->externalBinManager()->binObject( "cdrecord" ); if( !bin ) { emit infoMessage( i18n("Could not find %1 executable.", m_dvd ? QString("dvdrecord") : QString("cdrecord" )), ERROR ); jobFinished(false); return; } *m_process << bin->path; // add the device (e.g. /dev/sg1) *m_process << QString("dev=") + K3b::externalBinDeviceParameter(m_device, bin); *m_process << "-msinfo"; // additional user parameters from config *m_process << bin->userParameters(); kDebug() << "***** " << bin->name() << " parameters:\n"; QStringList args = m_process->program(); args.removeFirst(); QString s = args.join(" "); kDebug() << s << flush; emit debuggingOutput( "msinfo command:", s ); - // connect( m_process, SIGNAL(readyReadStandardError()), - // this, SLOT(slotCollectOutput()) ); - connect( m_process, SIGNAL(readyReadStandardOutput()), - this, SLOT(slotCollectOutput()) ); connect( m_process, SIGNAL(finished()), this, SLOT(slotProcessExited()) ); m_msInfo = QString(); m_collectedOutput = QString(); m_canceled = false; - m_process->setOutputChannelMode(KProcess::SeparateChannels); - - m_process->start(); + m_process->start( KProcess::OnlyStdoutChannel ); } } void K3b::MsInfoFetcher::slotMediaDetectionFinished( K3b::Device::DeviceHandler* h ) { if( h->success() ) { m_dvd = h->diskInfo().isDvdMedia(); } else { // for now we just default to cd and go on with the detecting m_dvd = false; } if( m_dvd ) { if( h->diskInfo().mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) ) { // get info from iso filesystem K3b::Iso9660 iso( m_device, h->toc().last().firstSector().lba() ); if( iso.open() ) { unsigned long long nextSession = iso.primaryDescriptor().volumeSpaceSize; // pad to closest 32K boundary nextSession += 15; nextSession /= 16; nextSession *= 16; m_msInfo.sprintf( "16,%llu", nextSession ); jobFinished( true ); } else { emit infoMessage( i18n("Could not open Iso9660 filesystem in %1.", m_device->vendor() + " " + m_device->description() ), ERROR ); jobFinished( false ); } } else { unsigned int lastSessionStart, nextWritableAdress; if( m_device->getNextWritableAdress( lastSessionStart, nextWritableAdress ) ) { m_msInfo.sprintf( "%u,%u", lastSessionStart+16, nextWritableAdress ); jobFinished( true ); } else { emit infoMessage( i18n("Could not determine next writable address."), ERROR ); jobFinished( false ); } } } else // call cdrecord getMsInfo(); } void K3b::MsInfoFetcher::slotProcessExited() { if( m_canceled ) return; if (m_process->error() == QProcess::FailedToStart) { emit infoMessage( i18n("Could not start %1", m_process->program().at(0)), K3b::Job::ERROR ); jobFinished(false); return; } kDebug() << "(K3b::MsInfoFetcher) msinfo fetched"; + m_collectedOutput = QString::fromLocal8Bit( m_process->readAllStandardOutput() ); + + emit debuggingOutput( "msinfo", m_collectedOutput ); + // now parse the output QString firstLine = m_collectedOutput.left( m_collectedOutput.indexOf('\n') ); QStringList list = firstLine.split( ',' ); if( list.count() == 2 ) { bool ok1, ok2; m_lastSessionStart = list.first().toInt( &ok1 ); m_nextSessionStart = list[1].toInt( &ok2 ); if( ok1 && ok2 ) m_msInfo = firstLine.trimmed(); else m_msInfo = QString(); } else { m_msInfo = QString(); } kDebug() << "(K3b::MsInfoFetcher) msinfo parsed: " << m_msInfo; if( m_msInfo.isEmpty() ) { emit infoMessage( i18n("Could not retrieve multisession information from disk."), K3b::Job::ERROR ); emit infoMessage( i18n("The disk is either empty or not appendable."), K3b::Job::ERROR ); jobFinished(false); } else { jobFinished(true); } } -void K3b::MsInfoFetcher::slotCollectOutput() -{ - QByteArray a = m_process->readAllStandardOutput(); - - emit debuggingOutput( "msinfo", QString::fromLocal8Bit( a ) ); - - m_collectedOutput += QString::fromLocal8Bit( a ); -} - - void K3b::MsInfoFetcher::cancel() { // FIXME: this does not work if the devicehandler is running if( m_process ) if( m_process->state() != QProcess::NotRunning) { m_canceled = true; - m_process->kill(); + m_process->terminate(); emit canceled(); jobFinished(false); } } #include "k3bmsinfofetcher.moc" diff --git a/libk3b/projects/datacd/k3bmsinfofetcher.h b/libk3b/projects/datacd/k3bmsinfofetcher.h index aa760eb3d..d2c953466 100644 --- a/libk3b/projects/datacd/k3bmsinfofetcher.h +++ b/libk3b/projects/datacd/k3bmsinfofetcher.h @@ -1,67 +1,65 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3B_MSINFO_FETCHER_H #define K3B_MSINFO_FETCHER_H #include -class KProcess; - namespace K3b { + class Process; namespace Device { class Device; class DeviceHandler; } class MsInfoFetcher : public Job { Q_OBJECT public: MsInfoFetcher( JobHandler*, QObject* parent = 0 ); ~MsInfoFetcher(); QString msInfo() const { return m_msInfo; } int lastSessionStart() const { return m_lastSessionStart; } int nextSessionStart() const { return m_nextSessionStart; } public Q_SLOTS: void start(); void cancel(); void setDevice( K3b::Device::Device* dev ) { m_device = dev; } private Q_SLOTS: void slotProcessExited(); - void slotCollectOutput(); void slotMediaDetectionFinished( K3b::Device::DeviceHandler* ); void getMsInfo(); private: QString m_msInfo; int m_lastSessionStart; int m_nextSessionStart; QString m_collectedOutput; - KProcess* m_process; + Process* m_process; Device::Device* m_device; bool m_canceled; bool m_dvd; }; } #endif diff --git a/libk3b/projects/k3babstractwriter.cpp b/libk3b/projects/k3babstractwriter.cpp index 40456bbc8..8743e969d 100644 --- a/libk3b/projects/k3babstractwriter.cpp +++ b/libk3b/projects/k3babstractwriter.cpp @@ -1,95 +1,95 @@ /* * * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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 "k3babstractwriter.h" #include #include #include #include #include #include K3b::AbstractWriter::AbstractWriter( K3b::Device::Device* dev, K3b::JobHandler* jh, QObject* parent ) : K3b::Job( jh, parent ), m_burnDevice(dev), m_burnSpeed(1), m_simulate(false), m_sourceUnreadable(false) { } K3b::AbstractWriter::~AbstractWriter() { } K3b::Device::Device* K3b::AbstractWriter::burnDevice() { if( m_burnDevice ) return m_burnDevice; else return k3bcore->deviceManager()->burningDevices()[0]; } void K3b::AbstractWriter::cancel() { if( burnDevice() ) { // we need to unlock the writer because cdrecord locked it while writing emit infoMessage( i18n("Unlocking drive..."), INFO ); connect( K3b::Device::unblock( burnDevice() ), SIGNAL(finished(bool)), this, SLOT(slotUnblockWhileCancellationFinished(bool)) ); } else { emit canceled(); jobFinished(false); } } void K3b::AbstractWriter::slotUnblockWhileCancellationFinished( bool success ) { if( !success ) - emit infoMessage( i18n("Could not unlock CD drive."), K3b::Job::ERROR ); // FIXME: simply "drive", not "CD drive" + emit infoMessage( i18n("Could not unlock drive."), K3b::Job::ERROR ); if( k3bcore->globalSettings()->ejectMedia() ) { - emit newSubTask( i18n("Ejecting CD") ); // FIXME: "media" instead of "CD" + emit newSubTask( i18n("Ejecting Medium") ); connect( K3b::Device::eject( burnDevice() ), SIGNAL(finished(bool)), this, SLOT(slotEjectWhileCancellationFinished(bool)) ); } else { emit canceled(); jobFinished( false ); } } void K3b::AbstractWriter::slotEjectWhileCancellationFinished( bool success ) { if( !success ) { - emit infoMessage( i18n("Unable to eject media."), K3b::Job::ERROR ); + emit infoMessage( i18n("Unable to eject medium."), K3b::Job::ERROR ); } emit canceled(); jobFinished( false ); } #include "k3babstractwriter.moc" diff --git a/libk3b/projects/k3babstractwriter.h b/libk3b/projects/k3babstractwriter.h index 8fc3d55d1..4809f8feb 100644 --- a/libk3b/projects/k3babstractwriter.h +++ b/libk3b/projects/k3babstractwriter.h @@ -1,91 +1,99 @@ /* * * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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. */ #ifndef K3B_ABSTRACT_WRITER_H #define K3B_ABSTRACT_WRITER_H #include "k3bjob.h" #include "k3bdevicetypes.h" #include +#include + +class QIODevice; + namespace K3b { class JobHandler; class AbstractWriter : public Job { Q_OBJECT public: virtual ~AbstractWriter(); Device::Device* burnDevice(); int burnSpeed() const { return m_burnSpeed; } bool simulate() const { return m_simulate; } /** * This can be used to setup direct streaming between two processes * for example the cdrecordwriter returns the stdin fd which can be * connected to the stdout fd of mkisofs in the isoimager */ - virtual int fd() const { return -1; } - virtual bool closeFd() { return false; } + virtual QIODevice* ioDevice() const { return 0; } + + /** + * \deprecated use QIODevice::close() instead + */ + KDE_DEPRECATED virtual bool closeFd() { return false; } public Q_SLOTS: /** * If the burnDevice is set this will try to unlock the drive and * eject the disk if K3b is configured to do so. * Will also emit canceled and finished signals. * may be called by subclasses. */ void cancel(); void setBurnDevice( K3b::Device::Device* dev ) { m_burnDevice = dev; } void setBurnSpeed( int s ) { m_burnSpeed = s; } void setSimulate( bool b ) { m_simulate = b; } /** * Used to inform the writer that the source (especially useful when reading from * another cd/dvd media) could not be read. * * Basically it should be used to make sure no "write an email" message is thrown. */ void setSourceUnreadable( bool b = true ) { m_sourceUnreadable = b; } Q_SIGNALS: void buffer( int ); void deviceBuffer( int ); void writeSpeed( int speed, K3b::Device::SpeedMultiplicator multiplicator ); protected: AbstractWriter( Device::Device* dev, JobHandler* hdl, QObject* parent = 0 ); bool wasSourceUnreadable() const { return m_sourceUnreadable; } protected Q_SLOTS: void slotUnblockWhileCancellationFinished( bool success ); void slotEjectWhileCancellationFinished( bool success ); private: Device::Device* m_burnDevice; int m_burnSpeed; bool m_simulate; bool m_sourceUnreadable; }; } #endif diff --git a/libk3b/projects/k3bcdrdaowriter.cpp b/libk3b/projects/k3bcdrdaowriter.cpp index 96264459e..f46922249 100644 --- a/libk3b/projects/k3bcdrdaowriter.cpp +++ b/libk3b/projects/k3bcdrdaowriter.cpp @@ -1,1062 +1,1062 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * Copyright (C) 2003 Klaus-Dieter Krannich * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bcdrdaowriter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include +#include #include #include #include #include #include #include #include #include #define PGSMSG_MIN PGSMSG_RCD_ANALYZING #define PGSMSG_RCD_ANALYZING 1 #define PGSMSG_RCD_EXTRACTING 2 #define PGSMSG_WCD_LEADIN 3 #define PGSMSG_WCD_DATA 4 #define PGSMSG_WCD_LEADOUT 5 #define PGSMSG_BLK 6 #define PGSMSG_MAX PGSMSG_BLK struct ProgressMsg { int status; // see PGSMSG_* constants int totalTracks; // total number of tracks int track; // actually written track int trackProgress; // progress for current track 0..1000 int totalProgress; // total writing progress 0..1000 int bufferFillRate; // buffer fill rate 0..100 }; #define PSGMSG_MINSIZE 24 struct ProgressMsg2 { int status; // see PGSMSG_* constants int totalTracks; // total number of tracks int track; // actually written track int trackProgress; // progress for current track 0..1000 int totalProgress; // total writing progress 0..1000 int bufferFillRate; // buffer fill rate 0..100 int writerFillRate; // device write buffer fill rate 0..100 }; inline bool operator<( const ProgressMsg2& m1, const ProgressMsg2& m2 ) { return m1.track < m2.track || ( m1.track == m2.track && m1.trackProgress < m2.trackProgress ) || m1.totalProgress < m2.totalProgress; } inline bool operator==( const ProgressMsg2& m1, const ProgressMsg2& m2 ) { return m1.status == m2.status && m1.track == m2.track && m1.totalTracks == m2.totalTracks && m1.trackProgress == m2.trackProgress && m1.totalProgress == m2.totalProgress && m1.bufferFillRate == m2.bufferFillRate; } inline bool operator!=( const ProgressMsg2& m1, const ProgressMsg2& m2 ) { return !( m1 == m2 ); } class K3b::CdrdaoWriter::Private { public: Private() { } K3b::ThroughputEstimator* speedEst; int usedSpeed; ProgressMsg2 oldMsg; ProgressMsg2 newMsg; unsigned int progressMsgSize; }; K3b::CdrdaoWriter::CdrdaoWriter( K3b::Device::Device* dev, K3b::JobHandler* hdl, QObject* parent ) : K3b::AbstractWriter( dev, hdl, parent ), m_command(WRITE), m_blankMode(MINIMAL), m_sourceDevice(0), m_readRaw(false), m_multi(false), m_force(false), m_onTheFly(false), m_fastToc(false), m_readSubchan(None), m_taoSource(false), m_taoSourceAdjust(-1), m_paranoiaMode(-1), m_session(-1), m_eject( false ), m_process(0), m_comSock(0), m_currentTrack(0) { d = new Private(); d->speedEst = new K3b::ThroughputEstimator( this ); connect( d->speedEst, SIGNAL(throughput(int)), this, SLOT(slotThroughput(int)) ); ::memset( &d->oldMsg, 0, sizeof(ProgressMsg2) ); ::memset( &d->newMsg, 0, sizeof(ProgressMsg2) ); if( socketpair(AF_UNIX,SOCK_STREAM,0,m_cdrdaoComm) ) { kDebug() << "(K3b::CdrdaoWriter) could not open socketpair for cdrdao remote messages"; } else { delete m_comSock; m_comSock = new Q3Socket(); m_comSock->setSocket(m_cdrdaoComm[1]); m_comSock->socketDevice()->setReceiveBufferSize(49152); // magic number from Qt documentation m_comSock->socketDevice()->setBlocking(false); connect( m_comSock, SIGNAL(readyRead()), this, SLOT(parseCdrdaoMessage())); } } K3b::CdrdaoWriter::~CdrdaoWriter() { delete d->speedEst; delete d; // close the socket if( m_comSock ) { m_comSock->close(); ::close( m_cdrdaoComm[0] ); } delete m_process; delete m_comSock; } bool K3b::CdrdaoWriter::active() const { return (m_process ? m_process->isRunning() : false); } void K3b::CdrdaoWriter::prepareArgumentList() { // binary *m_process << m_cdrdaoBinObject; // command switch ( m_command ) { case COPY: *m_process << "copy"; setWriteArguments(); setReadArguments(); setCopyArguments(); break; case WRITE: *m_process << "write"; setWriteArguments(); break; case READ: *m_process << "read-cd"; // source device and source driver if ( m_sourceDevice ) *m_process << "--device" << K3b::externalBinDeviceParameter(m_sourceDevice, m_cdrdaoBinObject); if( defaultToGenericMMC( m_sourceDevice, false ) ) { kDebug() << "(K3b::CdrdaoWriter) defaulting to generic-mmc driver for " << m_sourceDevice->blockDeviceName(); *m_process << "--driver" << "generic-mmc"; } setReadArguments(); break; case BLANK: *m_process << "blank"; setBlankArguments(); break; } setCommonArguments(); } void K3b::CdrdaoWriter::setWriteArguments() { // device and driver *m_process << "--device" << K3b::externalBinDeviceParameter(burnDevice(), m_cdrdaoBinObject); if( defaultToGenericMMC( burnDevice(), true ) ) { kDebug() << "(K3b::CdrdaoWriter) defaulting to generic-mmc driver for " << burnDevice()->blockDeviceName(); *m_process << "--driver" << "generic-mmc:0x00000010"; } // burn speed if( d->usedSpeed != 0 ) *m_process << "--speed" << QString("%1").arg(d->usedSpeed); //simulate if( simulate() ) *m_process << "--simulate"; // multi if( m_multi ) *m_process << "--multi"; // force if( m_force ) *m_process << "--force"; // burnproof if ( !k3bcore->globalSettings()->burnfree() ) { if( m_cdrdaoBinObject->hasFeature( "disable-burnproof" ) ) *m_process << "--buffer-under-run-protection" << "0"; else emit infoMessage( i18n("Cdrdao %1 does not support disabling burnfree.",m_cdrdaoBinObject->version), WARNING ); } if( k3bcore->globalSettings()->force() ) { *m_process << "--force"; emit infoMessage( i18n("'Force unsafe operations' enabled."), WARNING ); } bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); if( manualBufferSize ) { // // one buffer in cdrdao holds 1 second of audio data = 75 frames = 75 * 2352 bytes // int bufSizeInMb = k3bcore->globalSettings()->bufferSize(); *m_process << "--buffers" << QString::number( bufSizeInMb*1024*1024/(75*2352) ); } bool overburn = k3bcore->globalSettings()->overburn(); if( overburn ) { if( m_cdrdaoBinObject->hasFeature("overburn") ) *m_process << "--overburn"; else emit infoMessage( i18n("Cdrdao %1 does not support overburning.",m_cdrdaoBinObject->version), WARNING ); } } void K3b::CdrdaoWriter::setReadArguments() { // readRaw if ( m_readRaw ) *m_process << "--read-raw"; // subchan if ( m_readSubchan != None ) { *m_process << "--read-subchan"; switch ( m_readSubchan ) { case RW: *m_process << "rw"; break; case RW_RAW: *m_process << "rw_raw"; break; case None: break; } } // TAO Source if ( m_taoSource ) *m_process << "--tao-source"; // TAO Source Adjust if ( m_taoSourceAdjust != -1 ) *m_process << "--tao-source-adjust" << QString("%1").arg(m_taoSourceAdjust); // paranoia Mode if ( m_paranoiaMode != -1 ) *m_process << "--paranoia-mode" << QString("%1").arg(m_paranoiaMode); // session if ( m_session != -1 ) *m_process << "--session" << QString("%1").arg(m_session); // fast TOC if ( m_fastToc ) *m_process << "--fast-toc"; } void K3b::CdrdaoWriter::setCopyArguments() { // source device and source driver *m_process << "--source-device" << K3b::externalBinDeviceParameter(m_sourceDevice, m_cdrdaoBinObject); if( defaultToGenericMMC( m_sourceDevice, false ) ) { kDebug() << "(K3b::CdrdaoWriter) defaulting to generic-mmc driver for " << m_sourceDevice->blockDeviceName(); *m_process << "--source-driver" << "generic-mmc"; } // on-the-fly if ( m_onTheFly ) *m_process << "--on-the-fly"; } void K3b::CdrdaoWriter::setBlankArguments() { // device and driver *m_process << "--device" << K3b::externalBinDeviceParameter(burnDevice(), m_cdrdaoBinObject); if( defaultToGenericMMC( burnDevice(), true ) ) { kDebug() << "(K3b::CdrdaoWriter) defaulting to generic-mmc driver for " << burnDevice()->blockDeviceName(); *m_process << "--driver" << "generic-mmc"; } // burn speed if( d->usedSpeed != 0 ) *m_process << "--speed" << QString("%1").arg(d->usedSpeed); // blank-mode *m_process << "--blank-mode"; switch (m_blankMode) { case FULL: *m_process << "full"; break; case MINIMAL: *m_process << "minimal"; break; } } void K3b::CdrdaoWriter::setCommonArguments() { // additional user parameters from config const QStringList& params = m_cdrdaoBinObject->userParameters(); for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) *m_process << *it; // display debug info *m_process << "-n" << "-v" << "2"; // we have the power to do what ever we want. ;) *m_process << "--force"; // eject if( m_eject ) *m_process << "--eject"; // remote *m_process << "--remote" << QString("%1").arg(m_cdrdaoComm[0]); // data File if ( ! m_dataFile.isEmpty() ) *m_process << "--datafile" << m_dataFile; // BIN/CUE if ( ! m_cueFileLnk.isEmpty() ) *m_process << m_cueFileLnk; // TOC File else if ( ! m_tocFile.isEmpty() ) *m_process << m_tocFile; } K3b::CdrdaoWriter* K3b::CdrdaoWriter::addArgument( const QString& arg ) { *m_process << arg; return this; } void K3b::CdrdaoWriter::start() { jobStarted(); d->speedEst->reset(); delete m_process; // kdelibs want this! m_process = new K3b::Process(); - m_process->setRunPrivileged(true); m_process->setSplitStdout(false); - m_process->setRawStdin(true); - connect( m_process, SIGNAL(stderrLine(const QString&)), + m_process->setOutputChannelMode( KProcess::MergedChannels ); + m_process->setFlags( K3bQProcess::RawStdin ); + connect( m_process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); connect( m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessExited(int, QProcess::ExitStatus)) ); m_canceled = false; m_knownError = false; m_cdrdaoBinObject = k3bcore->externalBinManager()->binObject("cdrdao"); if( !m_cdrdaoBinObject ) { emit infoMessage( i18n("Could not find %1 executable.",QString("cdrdao")), ERROR ); jobFinished(false); return; } emit debuggingOutput( QLatin1String("Used versions"), QLatin1String( "cdrdao: " ) + m_cdrdaoBinObject->version ); if( !m_cdrdaoBinObject->copyright.isEmpty() ) emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3",m_cdrdaoBinObject->name(),m_cdrdaoBinObject->version,m_cdrdaoBinObject->copyright), INFO ); // the message size changed in cdrdao 1.1.8) if( m_cdrdaoBinObject->version >= K3b::Version( 1, 1, 8 ) ) d->progressMsgSize = sizeof(ProgressMsg2); else d->progressMsgSize = sizeof(ProgressMsg); // since the --speed parameter is used several times in this code we // determine the speed in auto once at the beginning d->usedSpeed = burnSpeed(); if( d->usedSpeed == 0 ) { // try to determine the writeSpeed // if it fails determineMaximalWriteSpeed() will return 0 and // the choice is left to cdrdao d->usedSpeed = burnDevice()->determineMaximalWriteSpeed(); } d->usedSpeed /= 175; switch ( m_command ) { case WRITE: case COPY: if (!m_tocFile.isEmpty()) { // if tocfile is a cuesheet than create symlinks to *.cue and the binary listed inside the cuesheet. // now works without the .bin extension too. if ( !cueSheet() ) { m_backupTocFile = m_tocFile + ".k3bbak"; // workaround, cdrdao deletes the tocfile when --remote parameter is set if ( !KIO::NetAccess::file_copy(KUrl(m_tocFile),KUrl(m_backupTocFile), (QWidget*) 0) ) { kDebug() << "(K3b::CdrdaoWriter) could not backup " << m_tocFile << " to " << m_backupTocFile; emit infoMessage( i18n("Could not backup tocfile."), ERROR ); jobFinished(false); return; } } } break; case BLANK: case READ: break; } prepareArgumentList(); // set working dir to dir part of toc file (to allow rel names in toc-file) - m_process->setWorkingDirectory(Q3Url(m_tocFile).path()); + m_process->setWorkingDirectory( QFileInfo( m_tocFile ).absolutePath() ); kDebug() << "***** cdrdao parameters:\n"; QString s = m_process->joinedArgs(); kDebug() << s << flush; emit debuggingOutput("cdrdao command:", s); m_currentTrack = 0; reinitParser(); switch ( m_command ) { case READ: emit newSubTask( i18n("Preparing read process...") ); break; case WRITE: emit newSubTask( i18n("Preparing write process...") ); break; case COPY: emit newSubTask( i18n("Preparing copy process...") ); break; case BLANK: emit newSubTask( i18n("Preparing blanking process...") ); break; } // FIXME: check the return value if( K3b::isMounted( burnDevice() ) ) { emit infoMessage( i18n("Unmounting medium"), INFO ); K3b::unmount( burnDevice() ); } // block the device (including certain checks) k3bcore->blockDevice( burnDevice() ); // lock the device for good in this process since it will // be opened in the growisofs process burnDevice()->close(); burnDevice()->usageLock(); - if( !m_process->start( K3Process::AllOutput ) ) + if( !m_process->start( KProcess::MergedChannels ) ) { // something went wrong when starting the program // it "should" be the executable kDebug() << "(K3b::CdrdaoWriter) could not start cdrdao"; emit infoMessage( i18n("Could not start %1.",QString("cdrdao")), K3b::Job::ERROR ); jobFinished(false); } else { switch ( m_command ) { case WRITE: if( simulate() ) { emit infoMessage(i18n("Starting DAO simulation at %1x speed...",d->usedSpeed), K3b::Job::INFO ); emit newTask( i18n("Simulating") ); } else { emit infoMessage( i18n("Starting DAO writing at %1x speed...",d->usedSpeed), K3b::Job::INFO ); emit newTask( i18n("Writing") ); } break; case READ: emit infoMessage(i18n("Starting reading..."), K3b::Job::INFO ); emit newTask( i18n("Reading") ); break; case COPY: if( simulate() ) { emit infoMessage(i18n("Starting simulation copy at %1x speed...",d->usedSpeed), K3b::Job::INFO ); emit newTask( i18n("Simulating") ); } else { emit infoMessage( i18n("Starting copy at %1x speed...",d->usedSpeed), K3b::Job::INFO ); emit newTask( i18n("Copying") ); } break; case BLANK: emit infoMessage(i18n("Starting blanking..."), K3b::Job::INFO ); emit newTask( i18n("Blanking") ); } } } void K3b::CdrdaoWriter::cancel() { m_canceled = true; if( m_process ) { if( m_process->isRunning() ) { m_process->disconnect(); - m_process->kill(); + m_process->terminate(); // we need to unlock the device because cdrdao locked it while writing // // FIXME: try to determine wheater we are writing or reading and choose // the device to unblock based on that result. // if( m_command == READ ) { // FIXME: this is a hack setBurnDevice( m_sourceDevice ); } // this will unblock and eject the drive and emit the finished/canceled signals K3b::AbstractWriter::cancel(); } } } bool K3b::CdrdaoWriter::cueSheet() { // TODO: do this in the K3b::CueFileParser if ( m_tocFile.toLower().endsWith( ".cue" ) ) { QFile f( m_tocFile ); if ( f.open( QIODevice::ReadOnly ) ) { QTextStream ts( &f ); QString line = ts.readLine(); f.close(); int pos = line.indexOf( "FILE \"" ); if( pos < 0 ) return false; pos += 6; int endPos = line.indexOf( "\" BINARY", pos+1 ); if( endPos < 0 ) return false; line = line.mid( pos, endPos-pos ); QFileInfo fi( QFileInfo( m_tocFile ).path() + "/" + QFileInfo( line ).fileName() ); QString binpath = fi.filePath(); kDebug() << QString("K3b::CdrdaoWriter::cueSheet() BinFilePath from CueFile: %1").arg( line ); kDebug() << QString("K3b::CdrdaoWriter::cueSheet() absolute BinFilePath: %1").arg( binpath ); if ( !fi.exists() ) return false; KTemporaryFile tempF; tempF.open(); QString tempFile = tempF.fileName(); tempF.remove(); if ( symlink(QFile::encodeName( binpath ), QFile::encodeName( tempFile + ".bin") ) == -1 ) return false; if ( symlink(QFile::encodeName( m_tocFile ), QFile::encodeName( tempFile + ".cue") ) == -1 ) return false; kDebug() << QString("K3b::CdrdaoWriter::cueSheet() symlink BinFileName: %1.bin").arg( tempFile ); kDebug() << QString("K3b::CdrdaoWriter::cueSheet() symlink CueFileName: %1.cue").arg( tempFile ); m_binFileLnk = tempFile + ".bin"; m_cueFileLnk = tempFile + ".cue"; return true; } } return false; } void K3b::CdrdaoWriter::slotStdLine( const QString& line ) { parseCdrdaoLine(line); } void K3b::CdrdaoWriter::slotProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { // release the device within this process burnDevice()->usageUnlock(); // unblock the device k3bcore->unblockDevice( burnDevice() ); switch ( m_command ) { case WRITE: case COPY: if ( !m_binFileLnk.isEmpty() ) { KIO::NetAccess::del(KUrl(m_cueFileLnk), (QWidget*) 0); KIO::NetAccess::del(KUrl(m_binFileLnk), (QWidget*) 0); } else if( (!QFile::exists( m_tocFile ) || K3b::filesize( KUrl(m_tocFile) ) == 0 ) && !m_onTheFly ) { // cdrdao removed the tocfile :( // we need to recover it if ( !KIO::NetAccess::file_copy(KUrl(m_backupTocFile), KUrl(m_tocFile), (QWidget*) 0) ) { kDebug() << "(K3b::CdrdaoWriter) restoring tocfile " << m_tocFile << " failed."; emit infoMessage( i18n("Due to a bug in cdrdao the toc/cue file %1 has been deleted. " "K3b was unable to restore it from the backup %2.",m_tocFile,m_backupTocFile), ERROR ); } else if ( !KIO::NetAccess::del(KUrl(m_backupTocFile), (QWidget*) 0) ) { kDebug() << "(K3b::CdrdaoWriter) delete tocfile backkup " << m_backupTocFile << " failed."; } } break; case BLANK: case READ: break; } if( m_canceled ) return; if( exitStatus == QProcess::NormalExit ) { switch( exitCode ) { case 0: if( simulate() ) emit infoMessage( i18n("Simulation successfully completed"), K3b::Job::SUCCESS ); else switch ( m_command ) { case READ: emit infoMessage( i18n("Reading successfully completed"), K3b::Job::SUCCESS ); break; case WRITE: emit infoMessage( i18n("Writing successfully completed"), K3b::Job::SUCCESS ); break; case COPY: emit infoMessage( i18n("Copying successfully completed"), K3b::Job::SUCCESS ); break; case BLANK: emit infoMessage( i18n("Blanking successfully completed"), K3b::Job::SUCCESS ); break; } if( m_command == WRITE || m_command == COPY ) { int s = d->speedEst->average(); emit infoMessage( ki18n("Average overall write speed: %1 KB/s (%2x)").subs(s).subs((double)s/150.0, 0, 'g', 2).toString(), INFO ); } jobFinished( true ); break; default: if( !m_knownError && !wasSourceUnreadable() ) { emit infoMessage( i18n("%1 returned an unknown error (code %2).",m_cdrdaoBinObject->name(), exitCode), K3b::Job::ERROR ); emit infoMessage( i18n("Please include the debugging output in your problem report."), K3b::Job::ERROR ); } jobFinished( false ); break; } } else { - emit infoMessage( i18n("%1 did not exit cleanly.",QString("cdrdao")), K3b::Job::ERROR ); + emit infoMessage( i18n("%1 crashed.", QString("cdrdao")), K3b::Job::ERROR ); jobFinished( false ); } } void K3b::CdrdaoWriter::unknownCdrdaoLine( const QString& line ) { if( line.contains( "at speed" ) ) { // parse the speed and inform the user if cdrdao switched it down int pos = line.indexOf( "at speed" ); int po2 = line.indexOf( QRegExp("\\D"), pos + 9 ); int speed = line.mid( pos+9, po2-pos-9 ).toInt(); if( speed < d->usedSpeed ) { emit infoMessage( i18n("Medium or burner do not support writing at %1x speed",d->usedSpeed), K3b::Job::WARNING ); emit infoMessage( i18n("Switching down burn speed to %1x",speed), K3b::Job::WARNING ); } } } void K3b::CdrdaoWriter::reinitParser() { ::memset( &d->oldMsg, 0, sizeof(ProgressMsg2) ); ::memset( &d->newMsg, 0, sizeof(ProgressMsg2) ); m_currentTrack=0; } void K3b::CdrdaoWriter::parseCdrdaoLine( const QString& str ) { emit debuggingOutput( "cdrdao", str ); // kDebug() << "(cdrdaoparse)" << str; // find some messages from cdrdao // ----------------------------------------------------------------------------------------- if( (str).startsWith( "Warning" ) || (str).startsWith( "WARNING" ) || (str).startsWith( "ERROR" ) ) { parseCdrdaoError( str ); } else if( (str).startsWith( "Wrote" ) && !str.contains("blocks") ) { parseCdrdaoWrote( str ); } else if( (str).startsWith( "Executing power" ) ) { emit newSubTask( i18n("Executing Power calibration") ); } else if( (str).startsWith( "Power calibration successful" ) ) { emit infoMessage( i18n("Power calibration successful"), K3b::Job::INFO ); emit newSubTask( i18n("Preparing burn process...") ); } else if( (str).startsWith( "Flushing cache" ) ) { emit newSubTask( i18n("Flushing cache") ); } else if( (str).startsWith( "Writing CD-TEXT lead" ) ) { emit newSubTask( i18n("Writing CD-Text lead-in...") ); } else if( (str).startsWith( "Turning BURN-Proof on" ) ) { emit infoMessage( i18n("Turning BURN-Proof on"), K3b::Job::INFO ); } else if( str.startsWith( "Copying" ) ) { emit infoMessage( str, K3b::Job::INFO ); } else if( str.startsWith( "Found ISRC" ) ) { emit infoMessage( i18n("Found ISRC code"), K3b::Job::INFO ); } else if( str.startsWith( "Found pre-gap" ) ) { emit infoMessage( i18n("Found pregap: %1", str.mid(str.indexOf(":")+1) ), K3b::Job::INFO ); } else unknownCdrdaoLine(str); } void K3b::CdrdaoWriter::parseCdrdaoError( const QString& line ) { int pos = -1; if( line.contains( "No driver found" ) || line.contains( "use option --driver" ) ) { emit infoMessage( i18n("No cdrdao driver found."), K3b::Job::ERROR ); emit infoMessage( i18n("Please select one manually in the device settings."), K3b::Job::ERROR ); emit infoMessage( i18n("For most current drives this would be 'generic-mmc'."), K3b::Job::ERROR ); m_knownError = true; } else if( line.contains( "Cannot setup device" ) ) { // no nothing... } else if( line.contains( "not ready") ) { emit infoMessage( i18n("Device not ready, waiting."),K3b::Job::WARNING ); } else if( line.contains("Drive does not accept any cue sheet") ) { emit infoMessage( i18n("Cue sheet not accepted."), K3b::Job::ERROR ); m_knownError = true; } else if( (pos = line.indexOf( "Illegal option" )) > 0 ) { // ERROR: Illegal option: -wurst emit infoMessage( i18n("No valid %1 option: %2",m_cdrdaoBinObject->name(),line.mid(pos+16)), ERROR ); m_knownError = true; } else if( line.contains( "exceeds capacity" ) ) { emit infoMessage( i18n("Data does not fit on disk."), ERROR ); if( m_cdrdaoBinObject->hasFeature("overburn") ) emit infoMessage( i18n("Enable overburning in the advanced K3b settings to burn anyway."), INFO ); m_knownError = true; } // else if( !line.contains( "remote progress message" ) ) // emit infoMessage( line, K3b::Job::ERROR ); } void K3b::CdrdaoWriter::parseCdrdaoWrote( const QString& line ) { int pos, po2; pos = line.indexOf( "Wrote" ); po2 = line.indexOf( " ", pos + 6 ); int processed = line.mid( pos+6, po2-pos-6 ).toInt(); pos = line.indexOf( "of" ); po2 = line.indexOf( " ", pos + 3 ); m_size = line.mid( pos+3, po2-pos-3 ).toInt(); d->speedEst->dataWritten( processed*1024 ); emit processedSize( processed, m_size ); } void K3b::CdrdaoWriter::parseCdrdaoMessage() { static const char msgSync[] = { 0xff, 0x00, 0xff, 0x00 }; unsigned int avail = m_comSock->bytesAvailable(); unsigned int msgs = avail / ( sizeof(msgSync)+d->progressMsgSize ); unsigned int count = 0; if ( msgs < 1 ) return; else if ( msgs > 1) { // move the read-index forward to the beginnig of the most recent message count = ( msgs-1 ) * ( sizeof(msgSync)+d->progressMsgSize ); m_comSock->at(count); kDebug() << "(K3b::CdrdaoParser) " << msgs-1 << " message(s) skipped"; } while( count < avail ) { // search for msg sync int state = 0; char buf; while( state < 4 ) { buf = m_comSock->getch(); ++count; if( count == avail ) { // kDebug() << "(K3b::CdrdaoParser) remote message sync not found (" << count << ")"; return; } if( buf == msgSync[state] ) ++state; else state = 0; } if( (avail - count) < d->progressMsgSize ) { kDebug() << "(K3b::CdrdaoParser) could not read complete remote message."; return; } // read one message (the message size changed in cdrdao 1.1.8) ::memset( &d->newMsg, 0, d->progressMsgSize ); int size = m_comSock->read( (char*)&d->newMsg, d->progressMsgSize); if( size == -1 ) { kDebug() << "(K3b::CdrdaoParser) read error"; return; } count += size; // sometimes the progress takes one step back (on my system when using paranoia-level 3) // so we just use messages that are greater than the previous or first messages if( d->oldMsg < d->newMsg || ( d->newMsg.track == 1 && d->newMsg.trackProgress <= 10 )) { if( d->newMsg.track != m_currentTrack ) { switch( d->newMsg.status ) { case PGSMSG_RCD_EXTRACTING: emit nextTrack( d->newMsg.track, d->newMsg.totalTracks ); break; case PGSMSG_WCD_LEADIN: emit newSubTask( i18n("Writing leadin ") ); break; case PGSMSG_WCD_DATA: emit nextTrack( d->newMsg.track, d->newMsg.totalTracks ); break; case PGSMSG_WCD_LEADOUT: emit newSubTask( i18n("Writing leadout ") ); break; } m_currentTrack = d->newMsg.track; } if( d->newMsg.status == PGSMSG_WCD_LEADIN || d->newMsg.status == PGSMSG_WCD_LEADOUT ) { // cdrdao >= 1.1.8 emits progress data when writing the lead-in and lead-out :) emit subPercent( d->newMsg.totalProgress/10 ); } else { emit subPercent( d->newMsg.trackProgress/10 ); emit percent( d->newMsg.totalProgress/10 ); } emit buffer(d->newMsg.bufferFillRate); if( d->progressMsgSize == (unsigned int)sizeof(ProgressMsg2) ) emit deviceBuffer( d->newMsg.writerFillRate ); ::memcpy( &d->oldMsg, &d->newMsg, d->progressMsgSize ); } } } void K3b::CdrdaoWriter::slotThroughput( int t ) { // FIXME: determine sector size emit writeSpeed( t, K3b::Device::SPEED_FACTOR_CD_MODE1 ); } QString K3b::CdrdaoWriter::findDriverFile( const K3b::ExternalBin* bin ) { if( !bin ) return QString(); // cdrdao normally in (prefix)/bin and driver table in (prefix)/share/cdrdao QString path = bin->path; path.truncate( path.lastIndexOf("/") ); path.truncate( path.lastIndexOf("/") ); path += "/share/cdrdao/drivers"; if( QFile::exists(path) ) return path; else { kDebug() << "(K3b::CdrdaoWriter) could not find cdrdao driver table."; return QString(); } } // returns true if the driver file could be opened and no driver could be found // TODO: cache the drivers bool K3b::CdrdaoWriter::defaultToGenericMMC( K3b::Device::Device* dev, bool writer ) { QString driverTable = findDriverFile( m_cdrdaoBinObject ); if( !driverTable.isEmpty() ) { QFile f( driverTable ); if( f.open( QIODevice::ReadOnly ) ) { // read all drivers QStringList drivers; QTextStream fStr( &f ); while( !fStr.atEnd() ) { QString line = fStr.readLine(); if( line.isEmpty() ) continue; if( line[0] == '#' ) continue; if( line[0] == 'R' && writer ) continue; if( line[0] == 'W' && !writer ) continue; drivers.append(line); } // search for the driver for( QStringList::const_iterator it = drivers.constBegin(); it != drivers.constEnd(); ++it ) { if( (*it).section( '|', 1, 1 ) == dev->vendor() && (*it).section( '|', 2, 2 ) == dev->description() ) return false; } // no driver found return true; } else { kDebug() << "(K3b::CdrdaoWriter) could not open driver table " << driverTable; return false; } } else return false; } #include "k3bcdrdaowriter.moc" diff --git a/libk3b/projects/k3bcdrecordwriter.cpp b/libk3b/projects/k3bcdrecordwriter.cpp index a593f37cb..86664bcde 100644 --- a/libk3b/projects/k3bcdrecordwriter.cpp +++ b/libk3b/projects/k3bcdrecordwriter.cpp @@ -1,839 +1,879 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 #include "k3bcdrecordwriter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K3b::CdrecordWriter::Private { public: Private() : cdTextFile(0) { } + const ExternalBin* cdrecordBinObject; + Process process; + + WritingMode writingMode; + bool totalTracksParsed; + bool clone; + bool cue; + + QString cueFile; + QStringList arguments; + + int currentTrack; + int totalTracks; + int totalSize; + int alreadyWritten; + + int lastFifoValue; + + int cdrecordError; + + QByteArray rawCdText; + K3b::ThroughputEstimator* speedEst; bool canceled; bool usingBurnfree; int usedSpeed; struct Track { int size; bool audio; }; QList tracks; KTemporaryFile* cdTextFile; Device::MediaType burnedMediaType; K3b::Device::SpeedMultiplicator usedSpeedFactor; }; K3b::CdrecordWriter::CdrecordWriter( K3b::Device::Device* dev, K3b::JobHandler* hdl, QObject* parent ) - : K3b::AbstractWriter( dev, hdl, parent ), - m_clone(false), - m_cue(false) + : K3b::AbstractWriter( dev, hdl, parent ) { d = new Private(); d->speedEst = new K3b::ThroughputEstimator( this ); connect( d->speedEst, SIGNAL(throughput(int)), this, SLOT(slotThroughput(int)) ); - m_process = 0; - m_writingMode = K3b::WRITING_MODE_TAO; + d->writingMode = K3b::WRITING_MODE_TAO; + d->clone = false; + d->cue = false; + + d->process.setSplitStdout(true); + d->process.setSuppressEmptyLines(true); + d->process.setFlags( K3bQProcess::RawStdin ); + connect( &d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); + connect( &d->process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessExited(int, QProcess::ExitStatus)) ); } K3b::CdrecordWriter::~CdrecordWriter() { delete d->cdTextFile; delete d; - delete m_process; } bool K3b::CdrecordWriter::active() const { - return ( m_process && m_process->isRunning() ); + return d->process.isRunning(); } -int K3b::CdrecordWriter::fd() const +QIODevice* K3b::CdrecordWriter::ioDevice() const { - if( m_process ) - return m_process->stdinFd(); - else - return -1; + return &d->process; +} + + +bool K3b::CdrecordWriter::closeFd() +{ + if ( d->process.isRunning() ) { + d->process.closeWriteChannel(); + return true; + } + else { + return false; + } } void K3b::CdrecordWriter::setDao( bool b ) { - m_writingMode = ( b ? K3b::WRITING_MODE_DAO : K3b::WRITING_MODE_TAO ); + d->writingMode = ( b ? K3b::WRITING_MODE_DAO : K3b::WRITING_MODE_TAO ); } + void K3b::CdrecordWriter::setCueFile( const QString& s) { - m_cue = true; - m_cueFile = s; + d->cue = true; + d->cueFile = s; // cuefile only works in DAO mode setWritingMode( K3b::WRITING_MODE_DAO ); } + void K3b::CdrecordWriter::setClone( bool b ) { - m_clone = b; + d->clone = b; +} + + +void K3b::CdrecordWriter::setRawCdText( const QByteArray& a ) +{ + d->rawCdText = a; } void K3b::CdrecordWriter::setWritingMode( K3b::WritingMode mode ) { - m_writingMode = mode; + d->writingMode = mode; } void K3b::CdrecordWriter::prepareProcess() { - if( m_process ) delete m_process; // kdelibs want this! - m_process = new K3b::Process(); - m_process->setRunPrivileged(true); - // m_process->setPriority( K3Process::PrioHighest ); - m_process->setSplitStdout(true); - m_process->setSuppressEmptyLines(true); - m_process->setRawStdin(true); // we only use stdin when writing on-the-fly - connect( m_process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); - connect( m_process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); - connect( m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessExited(int, QProcess::ExitStatus)) ); - - m_cdrecordBinObject = k3bcore->externalBinManager()->binObject("cdrecord"); - - if( !m_cdrecordBinObject ) + d->cdrecordBinObject = k3bcore->externalBinManager()->binObject("cdrecord"); + + if( !d->cdrecordBinObject ) return; + d->process.clearProgram(); + d->burnedMediaType = burnDevice()->mediaType(); - *m_process << m_cdrecordBinObject; + d->process << d->cdrecordBinObject; // display progress - *m_process << "-v"; + d->process << "-v"; - if( m_cdrecordBinObject->hasFeature( "gracetime") ) - *m_process << "gracetime=2"; // 2 is the lowest allowed value (Joerg, why do you do this to us?) + if( d->cdrecordBinObject->hasFeature( "gracetime") ) + d->process << "gracetime=2"; // 2 is the lowest allowed value (Joerg, why do you do this to us?) // Again we assume the device to be set! - *m_process << QString("dev=%1").arg(K3b::externalBinDeviceParameter(burnDevice(), m_cdrecordBinObject)); + d->process << QString("dev=%1").arg(K3b::externalBinDeviceParameter(burnDevice(), d->cdrecordBinObject)); d->usedSpeed = burnSpeed(); if( d->usedSpeed == 0 ) { // try to determine the writeSpeed // if it fails determineMaximalWriteSpeed() will return 0 and // the choice is left to cdrecord d->usedSpeed = burnDevice()->determineMaximalWriteSpeed(); } if ( d->burnedMediaType & K3b::Device::MEDIA_DVD_ALL ) { d->usedSpeed /= K3b::Device::SPEED_FACTOR_DVD; d->usedSpeedFactor = K3b::Device::SPEED_FACTOR_DVD; } else if ( d->burnedMediaType & K3b::Device::MEDIA_BD_ALL ) { d->usedSpeed /= K3b::Device::SPEED_FACTOR_BD; d->usedSpeedFactor = K3b::Device::SPEED_FACTOR_BD; } else { // cdrecord provides progress and speed as multiple of 150 KB (except for audio tracks) d->usedSpeed /= K3b::Device::SPEED_FACTOR_CD; d->usedSpeedFactor = K3b::Device::SPEED_FACTOR_CD; } if( d->usedSpeed != 0 ) - *m_process << QString("speed=%1").arg(d->usedSpeed); + d->process << QString("speed=%1").arg(d->usedSpeed); if ( K3b::Device::isBdMedia( d->burnedMediaType ) ) { - if ( !m_cdrecordBinObject->hasFeature( "blu-ray" ) ) { - emit infoMessage( i18n( "Cdrecord version %1 does not support Blu-ray writing." ,m_cdrecordBinObject->version ), ERROR ); + if ( !d->cdrecordBinObject->hasFeature( "blu-ray" ) ) { + emit infoMessage( i18n( "Cdrecord version %1 does not support Blu-ray writing." ,d->cdrecordBinObject->version ), ERROR ); // FIXME: add a way to fail the whole thing here } - *m_process << "-sao"; + d->process << "-sao"; } else if ( K3b::Device::isDvdMedia( d->burnedMediaType ) ) { // cdrecord only supports SAo for DVD - *m_process << "-sao"; + d->process << "-sao"; } else if( K3b::Device::isCdMedia( d->burnedMediaType ) ) { - if( m_writingMode == K3b::WRITING_MODE_DAO || m_cue ) { + if( d->writingMode == K3b::WRITING_MODE_DAO || d->cue ) { if( burnDevice()->dao() ) - *m_process << "-sao"; + d->process << "-sao"; else { - if( m_cdrecordBinObject->hasFeature( "tao" ) ) - *m_process << "-tao"; + if( d->cdrecordBinObject->hasFeature( "tao" ) ) + d->process << "-tao"; emit infoMessage( i18n("Writer does not support disk at once (DAO) recording"), WARNING ); } } - else if( m_writingMode == K3b::WRITING_MODE_RAW ) { + else if( d->writingMode == K3b::WRITING_MODE_RAW ) { if( burnDevice()->supportsWritingMode( K3b::Device::RAW_R96R ) ) - *m_process << "-raw96r"; + d->process << "-raw96r"; else if( burnDevice()->supportsWritingMode( K3b::Device::RAW_R16 ) ) - *m_process << "-raw16"; + d->process << "-raw16"; else if( burnDevice()->supportsWritingMode( K3b::Device::RAW_R96P ) ) - *m_process << "-raw96p"; + d->process << "-raw96p"; else { emit infoMessage( i18n("Writer does not support raw writing."), WARNING ); - if( m_cdrecordBinObject->hasFeature( "tao" ) ) - *m_process << "-tao"; + if( d->cdrecordBinObject->hasFeature( "tao" ) ) + d->process << "-tao"; } } - else if( m_cdrecordBinObject->hasFeature( "tao" ) ) - *m_process << "-tao"; + else if( d->cdrecordBinObject->hasFeature( "tao" ) ) + d->process << "-tao"; } else { emit infoMessage( i18n( "Cdrecord does not support writing %1 media." , K3b::Device::mediaTypeString( d->burnedMediaType ) ), ERROR ); // FIXME: add a way to fail the whole thing here } if( simulate() ) - *m_process << "-dummy"; + d->process << "-dummy"; d->usingBurnfree = false; if( k3bcore->globalSettings()->burnfree() ) { if( burnDevice()->burnproof() ) { d->usingBurnfree = true; // with cdrecord 1.11a02 burnproof was renamed to burnfree - if( m_cdrecordBinObject->hasFeature( "burnproof" ) ) - *m_process << "driveropts=burnproof"; + if( d->cdrecordBinObject->hasFeature( "burnproof" ) ) + d->process << "driveropts=burnproof"; else - *m_process << "driveropts=burnfree"; + d->process << "driveropts=burnfree"; } else emit infoMessage( i18n("Writer does not support buffer underrun free recording (Burnfree)"), WARNING ); } if( k3bcore->globalSettings()->force() ) { - *m_process << "-force"; + d->process << "-force"; emit infoMessage( i18n("'Force unsafe operations' enabled."), WARNING ); } - if( m_cue ) { - m_process->setWorkingDirectory( m_cueFile ); - *m_process << QString("cuefile=%1").arg( m_cueFile ); + if( d->cue ) { + d->process.setWorkingDirectory( d->cueFile ); + d->process << QString("cuefile=%1").arg( d->cueFile ); } - if( m_clone ) - *m_process << "-clone"; + if( d->clone ) + d->process << "-clone"; - if( m_rawCdText.size() > 0 ) { + if( d->rawCdText.size() > 0 ) { delete d->cdTextFile; d->cdTextFile = new KTemporaryFile(); d->cdTextFile->setPrefix( "/tmp/" ); // needs to be world readable in case cdrecord runs suid root d->cdTextFile->setSuffix( ".dat" ); - d->cdTextFile->write( m_rawCdText ); - d->cdTextFile->close(); + d->cdTextFile->open(); + d->cdTextFile->write( d->rawCdText ); - *m_process << "textfile=" + d->cdTextFile->fileName(); + d->process << "textfile=" + d->cdTextFile->fileName(); } bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); if( manualBufferSize ) { - *m_process << QString("fs=%1m").arg( k3bcore->globalSettings()->bufferSize() ); + d->process << QString("fs=%1m").arg( k3bcore->globalSettings()->bufferSize() ); } bool overburn = k3bcore->globalSettings()->overburn(); if( overburn ) { - if( m_cdrecordBinObject->hasFeature("overburn") ) { + if( d->cdrecordBinObject->hasFeature("overburn") ) { if ( k3bcore->globalSettings()->force() ) - *m_process << "-ignsize"; + d->process << "-ignsize"; else - *m_process << "-overburn"; + d->process << "-overburn"; } else { - emit infoMessage( i18n("Cdrecord %1 does not support overburning.",m_cdrecordBinObject->version), WARNING ); + emit infoMessage( i18n("Cdrecord %1 does not support overburning.",d->cdrecordBinObject->version), WARNING ); } } // additional user parameters from config - const QStringList& params = m_cdrecordBinObject->userParameters(); + const QStringList& params = d->cdrecordBinObject->userParameters(); for( QStringList::const_iterator it = params.constBegin(); it != params.constEnd(); ++it ) - *m_process << *it; + d->process << *it; // add the user parameters - for( QStringList::const_iterator it = m_arguments.constBegin(); it != m_arguments.constEnd(); ++it ) - *m_process << *it; + for( QStringList::const_iterator it = d->arguments.constBegin(); it != d->arguments.constEnd(); ++it ) + d->process << *it; } K3b::CdrecordWriter* K3b::CdrecordWriter::addArgument( const QString& arg ) { - m_arguments.append( arg ); + d->arguments.append( arg ); return this; } void K3b::CdrecordWriter::clearArguments() { - m_arguments.clear(); + d->arguments.clear(); } void K3b::CdrecordWriter::start() { jobStarted(); d->canceled = false; d->speedEst->reset(); prepareProcess(); - if( !m_cdrecordBinObject ) { + if( !d->cdrecordBinObject ) { emit infoMessage( i18n("Could not find %1 executable.",QString("cdrecord")), ERROR ); jobFinished(false); return; } - emit debuggingOutput( QLatin1String( "Used versions" ), QLatin1String( "cdrecord: " ) + m_cdrecordBinObject->version ); + emit debuggingOutput( QLatin1String( "Used versions" ), QLatin1String( "cdrecord: " ) + d->cdrecordBinObject->version ); - if( !m_cdrecordBinObject->copyright.isEmpty() ) + if( !d->cdrecordBinObject->copyright.isEmpty() ) emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3" - ,(m_cdrecordBinObject->hasFeature( "wodim" ) ? "Wodim" : "Cdrecord" ) - ,m_cdrecordBinObject->version - ,m_cdrecordBinObject->copyright), INFO ); + ,(d->cdrecordBinObject->hasFeature( "wodim" ) ? "Wodim" : "Cdrecord" ) + ,d->cdrecordBinObject->version + ,d->cdrecordBinObject->copyright), INFO ); - kDebug() << "***** " << m_cdrecordBinObject->name() << " parameters:\n"; - QString s = m_process->joinedArgs(); + kDebug() << "***** " << d->cdrecordBinObject->name() << " parameters:\n"; + QString s = d->process.joinedArgs(); kDebug() << s << flush; - emit debuggingOutput( m_cdrecordBinObject->name() + " command:", s); + emit debuggingOutput( d->cdrecordBinObject->name() + " command:", s); - m_currentTrack = 0; - m_cdrecordError = UNKNOWN; - m_totalTracksParsed = false; - m_alreadyWritten = 0; + d->currentTrack = 0; + d->cdrecordError = UNKNOWN; + d->totalTracksParsed = false; + d->alreadyWritten = 0; d->tracks.clear(); - m_totalSize = 0; + d->totalSize = 0; emit newSubTask( i18n("Preparing write process...") ); // FIXME: check the return value if( K3b::isMounted( burnDevice() ) ) { emit infoMessage( i18n("Unmounting medium"), INFO ); K3b::unmount( burnDevice() ); } // block the device (including certain checks) k3bcore->blockDevice( burnDevice() ); // lock the device for good in this process since it will // be opened in the cdrecord process burnDevice()->close(); burnDevice()->usageLock(); - if( !m_process->start( K3Process::All ) ) { + if( !d->process.start( KProcess::MergedChannels ) ) { // something went wrong when starting the program // it "should" be the executable - kDebug() << "(K3b::CdrecordWriter) could not start " << m_cdrecordBinObject->name(); - emit infoMessage( i18n("Could not start %1.",m_cdrecordBinObject->name()), K3b::Job::ERROR ); + kDebug() << "(K3b::CdrecordWriter) could not start " << d->cdrecordBinObject->name(); + emit infoMessage( i18n("Could not start %1.",d->cdrecordBinObject->name()), K3b::Job::ERROR ); jobFinished(false); } else { // FIXME: these messages should also take DVD into account. if( simulate() ) { emit newTask( i18n("Simulating") ); emit infoMessage( i18n("Starting %1 simulation at %2x speed..." - ,K3b::writingModeString(m_writingMode) + ,K3b::writingModeString(d->writingMode) ,d->usedSpeed), K3b::Job::INFO ); } else { emit newTask( i18n("Writing") ); emit infoMessage( i18n("Starting %1 writing at %2x speed..." - ,K3b::writingModeString(m_writingMode) + ,K3b::writingModeString(d->writingMode) ,d->usedSpeed), K3b::Job::INFO ); } } } void K3b::CdrecordWriter::cancel() { if( active() ) { d->canceled = true; - if( m_process && m_process->isRunning() ) - m_process->kill(); + if( d->process.isRunning() ) + d->process.terminate(); } } void K3b::CdrecordWriter::slotStdLine( const QString& line ) { static QRegExp s_burnfreeCounterRx( "^BURN\\-Free\\swas\\s(\\d+)\\stimes\\sused" ); static QRegExp s_burnfreeCounterRxPredict( "^Total\\sof\\s(\\d+)\\s\\spossible\\sbuffer\\sunderruns\\spredicted" ); // tracknumber: cap(1) // done: cap(2) // complete: cap(3) // fifo: cap(4) (it seems as if some patched cdrecord versions do not emit the fifo info but only the buf... :( // buffer: cap(5) static QRegExp s_progressRx( "Track\\s(\\d\\d)\\:\\s*(\\d*)\\sof\\s*(\\d*)\\sMB\\swritten\\s(?:\\(fifo\\s*(\\d*)\\%\\)\\s*)?(?:\\[buf\\s*(\\d*)\\%\\])?.*" ); - emit debuggingOutput( m_cdrecordBinObject->name(), line ); + emit debuggingOutput( d->cdrecordBinObject->name(), line ); // // Progress and toc parsing // if( line.startsWith( "Track " ) ) { - if( !m_totalTracksParsed ) { + if( !d->totalTracksParsed ) { // this is not the progress display but the list of tracks that will get written // we always extract the tracknumber to get the highest at last bool ok; int tt = line.mid( 6, 2 ).toInt(&ok); if( ok ) { struct Private::Track track; track.audio = ( line.mid( 10, 5 ) == "audio" ); - m_totalTracks = tt; + d->totalTracks = tt; int sizeStart = line.indexOf( QRegExp("\\d"), 10 ); int sizeEnd = line.indexOf( "MB", sizeStart ); track.size = line.mid( sizeStart, sizeEnd-sizeStart ).toInt(&ok); if( ok ) { d->tracks.append(track); - m_totalSize += track.size; + d->totalSize += track.size; } else kDebug() << "(K3b::CdrecordWriter) track number parse error: " << line.mid( sizeStart, sizeEnd-sizeStart ); } else kDebug() << "(K3b::CdrecordWriter) track number parse error: " << line.mid( 6, 2 ); } else if( s_progressRx.exactMatch( line ) ) { // int num = s_progressRx.cap(1).toInt(); int made = s_progressRx.cap(2).toInt(); int size = s_progressRx.cap(3).toInt(); int fifo = s_progressRx.cap(4).toInt(); emit buffer( fifo ); - m_lastFifoValue = fifo; + d->lastFifoValue = fifo; if( s_progressRx.numCaptures() > 4 ) emit deviceBuffer( s_progressRx.cap(5).toInt() ); // // cdrecord's output sucks a bit. // we get track sizes that differ from the sizes in the progress // info since these are dependant on the writing mode. // so we just use the track sizes and do a bit of math... // - if( d->tracks.count() > m_currentTrack-1 && size > 0 ) { - double convV = (double)d->tracks[m_currentTrack-1].size/(double)size; + if( d->tracks.count() > d->currentTrack-1 && size > 0 ) { + double convV = (double)d->tracks[d->currentTrack-1].size/(double)size; made = (int)((double)made * convV); - size = d->tracks[m_currentTrack-1].size; + size = d->tracks[d->currentTrack-1].size; } else { kError() << "(K3b::CdrecordWriter) Did not parse all tracks sizes!" << endl; } if( size > 0 ) { emit processedSubSize( made, size ); emit subPercent( 100*made/size ); } - if( m_totalSize > 0 ) { - emit processedSize( m_alreadyWritten+made, m_totalSize ); - emit percent( 100*(m_alreadyWritten+made)/m_totalSize ); + if( d->totalSize > 0 ) { + emit processedSize( d->alreadyWritten+made, d->totalSize ); + emit percent( 100*(d->alreadyWritten+made)/d->totalSize ); } - d->speedEst->dataWritten( (m_alreadyWritten+made)*1024 ); + d->speedEst->dataWritten( (d->alreadyWritten+made)*1024 ); } } // // Cdrecord starts all error and warning messages with it's path // With Debian's script it starts with cdrecord (or /usr/bin/cdrecord or whatever! I hate this script!) // else if( line.startsWith( "cdrecord" ) || - line.startsWith( m_cdrecordBinObject->path ) || - line.startsWith( m_cdrecordBinObject->path.left(m_cdrecordBinObject->path.length()-5) ) ) { + line.startsWith( d->cdrecordBinObject->path ) || + line.startsWith( d->cdrecordBinObject->path.left(d->cdrecordBinObject->path.length()-5) ) ) { // get rid of the path and the following colon and space QString errStr = line.mid( line.indexOf(':') + 2 ); if( errStr.startsWith( "Drive does not support SAO" ) ) { emit infoMessage( i18n("DAO (Disk At Once) recording not supported with this writer"), K3b::Job::ERROR ); emit infoMessage( i18n("Please choose TAO (Track At Once) and try again"), K3b::Job::ERROR ); } else if( errStr.startsWith( "Drive does not support RAW" ) ) { emit infoMessage( i18n("RAW recording not supported with this writer"), K3b::Job::ERROR ); } else if( errStr.startsWith("Input/output error.") ) { emit infoMessage( i18n("Input/output error. Not necessarily serious."), WARNING ); } else if( errStr.startsWith("shmget failed") ) { - m_cdrecordError = SHMGET_FAILED; + d->cdrecordError = SHMGET_FAILED; } else if( errStr.startsWith("OPC failed") ) { - m_cdrecordError = OPC_FAILED; + d->cdrecordError = OPC_FAILED; } else if( errStr.startsWith( "Drive needs to reload the media" ) ) { emit infoMessage( i18n("Reloading of medium required"), K3b::Job::INFO ); } else if( errStr.startsWith( "The current problem looks like a buffer underrun" ) ) { - if( m_cdrecordError == UNKNOWN ) // it is almost never a buffer underrun these days. - m_cdrecordError = BUFFER_UNDERRUN; + if( d->cdrecordError == UNKNOWN ) // it is almost never a buffer underrun these days. + d->cdrecordError = BUFFER_UNDERRUN; } else if( errStr.startsWith("WARNING: Data may not fit") ) { bool overburn = k3bcore->globalSettings()->overburn(); - if( overburn && m_cdrecordBinObject->hasFeature("overburn") ) + if( overburn && d->cdrecordBinObject->hasFeature("overburn") ) emit infoMessage( i18n("Trying to write more than the official disk capacity"), K3b::Job::WARNING ); - m_cdrecordError = OVERSIZE; + d->cdrecordError = OVERSIZE; } else if( errStr.startsWith("Bad Option") ) { - m_cdrecordError = BAD_OPTION; + d->cdrecordError = BAD_OPTION; // parse option int pos = line.indexOf( "Bad Option" ) + 13; int len = line.length() - pos - 1; - emit infoMessage( i18n("No valid %1 option: %2",m_cdrecordBinObject->name(),line.mid(pos, len)), + emit infoMessage( i18n("No valid %1 option: %2",d->cdrecordBinObject->name(),line.mid(pos, len)), ERROR ); } else if( errStr.startsWith("Cannot set speed/dummy") ) { - m_cdrecordError = CANNOT_SET_SPEED; + d->cdrecordError = CANNOT_SET_SPEED; } else if( errStr.startsWith("Cannot open new session") ) { - m_cdrecordError = CANNOT_OPEN_NEW_SESSION; + d->cdrecordError = CANNOT_OPEN_NEW_SESSION; } else if( errStr.startsWith("Cannot send CUE sheet") ) { - m_cdrecordError = CANNOT_SEND_CUE_SHEET; + d->cdrecordError = CANNOT_SEND_CUE_SHEET; } else if( errStr.startsWith( "Trying to use ultra high speed" ) || errStr.startsWith( "Trying to use high speed" ) || errStr.startsWith( "Probably trying to use ultra high speed" ) || errStr.startsWith( "You did use a high speed medium on an improper writer" ) || errStr.startsWith( "You did use a ultra high speed medium on an improper writer" ) ) { - m_cdrecordError = HIGH_SPEED_MEDIUM; + d->cdrecordError = HIGH_SPEED_MEDIUM; } else if( errStr.startsWith( "You may have used an ultra low speed medium" ) ) { - m_cdrecordError = LOW_SPEED_MEDIUM; + d->cdrecordError = LOW_SPEED_MEDIUM; } else if( errStr.startsWith( "Permission denied. Cannot open" ) || errStr.startsWith( "Operation not permitted." ) ) { - m_cdrecordError = PERMISSION_DENIED; + d->cdrecordError = PERMISSION_DENIED; } else if( errStr.startsWith( "Can only copy session # 1") ) { emit infoMessage( i18n("Only session 1 will be cloned."), WARNING ); } else if( errStr == "Cannot fixate disk." ) { emit infoMessage( i18n("Unable to fixate the disk."), ERROR ); - if( m_cdrecordError == UNKNOWN ) - m_cdrecordError = CANNOT_FIXATE_DISK; + if( d->cdrecordError == UNKNOWN ) + d->cdrecordError = CANNOT_FIXATE_DISK; } else if( errStr == "A write error occurred." ) { - m_cdrecordError = WRITE_ERROR; + d->cdrecordError = WRITE_ERROR; } else if( errStr.startsWith( "Try again with cdrecord blank=all." ) ) { - m_cdrecordError = BLANK_FAILED; + d->cdrecordError = BLANK_FAILED; } } // // All other messages // else if( line.contains( "at speed" ) ) { // parse the speed and inform the user if cdrdao switched it down int pos = line.indexOf( "at speed" ); int pos2 = line.indexOf( "in", pos+9 ); int speed = static_cast( line.mid( pos+9, pos2-pos-10 ).toDouble() ); // cdrecord-dvd >= 2.01a25 uses 8.0 and stuff if( speed != d->usedSpeed ) { emit infoMessage( i18n("Medium or burner do not support writing at %1x speed",d->usedSpeed), K3b::Job::WARNING ); if( speed > d->usedSpeed ) emit infoMessage( i18n("Switching burn speed up to %1x",speed), K3b::Job::WARNING ); else emit infoMessage( i18n("Switching burn speed down to %1x",speed), K3b::Job::WARNING ); } } else if( line.startsWith( "Starting new" ) ) { - m_totalTracksParsed = true; - if( m_currentTrack > 0 ) {// nothing has been written at the start of track 1 - if( d->tracks.count() > m_currentTrack-1 ) - m_alreadyWritten += d->tracks[m_currentTrack-1].size; + d->totalTracksParsed = true; + if( d->currentTrack > 0 ) {// nothing has been written at the start of track 1 + if( d->tracks.count() > d->currentTrack-1 ) + d->alreadyWritten += d->tracks[d->currentTrack-1].size; else kError() << "(K3b::CdrecordWriter) Did not parse all tracks sizes!"; } else emit infoMessage( i18n("Starting disc write"), INFO ); - m_currentTrack++; + d->currentTrack++; - if( m_currentTrack > d->tracks.count() ) { + if( d->currentTrack > d->tracks.count() ) { kDebug() << "(K3b::CdrecordWriter) need to add dummy track struct."; struct Private::Track t; t.size = 1; t.audio = false; d->tracks.append(t); } - kDebug() << "(K3b::CdrecordWriter) writing track " << m_currentTrack << " of " << m_totalTracks << " tracks."; - emit nextTrack( m_currentTrack, m_totalTracks ); + kDebug() << "(K3b::CdrecordWriter) writing track " << d->currentTrack << " of " << d->totalTracks << " tracks."; + emit nextTrack( d->currentTrack, d->totalTracks ); } else if( line.startsWith( "Fixating" ) ) { emit newSubTask( i18n("Closing Session") ); } else if( line.startsWith( "Writing lead-in" ) ) { - m_totalTracksParsed = true; + d->totalTracksParsed = true; emit newSubTask( i18n("Writing Leadin") ); } else if( line.startsWith( "Writing Leadout") ) { emit newSubTask( i18n("Writing Leadout") ); } else if( line.startsWith( "Writing pregap" ) ) { emit newSubTask( i18n("Writing pregap") ); } else if( line.startsWith( "Performing OPC" ) ) { emit infoMessage( i18n("Performing Optimum Power Calibration"), K3b::Job::INFO ); } else if( line.startsWith( "Sending" ) ) { emit infoMessage( i18n("Sending CUE sheet"), K3b::Job::INFO ); } else if( line.startsWith( "Turning BURN-Free on" ) || line.startsWith( "BURN-Free is ON") ) { emit infoMessage( i18n("Enabled Burnfree"), K3b::Job::INFO ); } else if( line.startsWith( "Turning BURN-Free off" ) ) { emit infoMessage( i18n("Disabled Burnfree"), K3b::Job::WARNING ); } else if( line.startsWith( "Re-load disk and hit" ) ) { // this happens on some notebooks where cdrecord is not able to close the // tray itself, so we need to ask the user to do so blockingInformation( i18n("Please reload the medium and press 'ok'"), i18n("Unable to close the tray") ); // now send a to cdrecord // hopefully this will do it since I have no possibility to test it! - m_process->write( "\n", 1 ); + d->process.write( "\n", 1 ); } else if( s_burnfreeCounterRx.indexIn( line ) ) { bool ok; int num = s_burnfreeCounterRx.cap(1).toInt(&ok); if( ok ) emit infoMessage( i18np("Burnfree was used 1 time.", "Burnfree was used %1 times.", num), INFO ); } else if( s_burnfreeCounterRxPredict.indexIn( line ) ) { bool ok; int num = s_burnfreeCounterRxPredict.cap(1).toInt(&ok); if( ok ) emit infoMessage( i18np("Buffer was low 1 time.", "Buffer was low %1 times.", num), INFO ); } else if( line.contains("Medium Error") ) { - m_cdrecordError = MEDIUM_ERROR; + d->cdrecordError = MEDIUM_ERROR; } else if( line.startsWith( "Error trying to open" ) && line.contains( "(Device or resource busy)" ) ) { - m_cdrecordError = DEVICE_BUSY; + d->cdrecordError = DEVICE_BUSY; } else { // debugging - kDebug() << "(" << m_cdrecordBinObject->name() << ") " << line; + kDebug() << "(" << d->cdrecordBinObject->name() << ") " << line; } } void K3b::CdrecordWriter::slotProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { // remove temporary cdtext file delete d->cdTextFile; d->cdTextFile = 0; // release the device within this process burnDevice()->usageUnlock(); // unblock the device k3bcore->unblockDevice( burnDevice() ); if( d->canceled ) { // this will unblock and eject the drive and emit the finished/canceled signals K3b::AbstractWriter::cancel(); return; } if( exitStatus == QProcess::NormalExit ) { switch( exitCode ) { case 0: { if( simulate() ) emit infoMessage( i18n("Simulation successfully completed"), K3b::Job::SUCCESS ); else emit infoMessage( i18n("Writing successfully completed"), K3b::Job::SUCCESS ); int s = d->speedEst->average(); emit infoMessage( ki18n("Average overall write speed: %1 KB/s (%2x)").subs(s).subs((double)s/( double )d->usedSpeedFactor, 0, 'g', 2).toString(), INFO ); jobFinished( true ); } break; default: kDebug() << "(K3b::CdrecordWriter) error: " << exitCode; - if( m_cdrecordError == UNKNOWN && m_lastFifoValue <= 3 ) - m_cdrecordError = BUFFER_UNDERRUN; + if( d->cdrecordError == UNKNOWN && d->lastFifoValue <= 3 ) + d->cdrecordError = BUFFER_UNDERRUN; - switch( m_cdrecordError ) { + switch( d->cdrecordError ) { case OVERSIZE: if( k3bcore->globalSettings()->overburn() && - m_cdrecordBinObject->hasFeature("overburn") ) + d->cdrecordBinObject->hasFeature("overburn") ) emit infoMessage( i18n("Data did not fit on disk."), ERROR ); else { emit infoMessage( i18n("Data does not fit on disk."), ERROR ); - if( m_cdrecordBinObject->hasFeature("overburn") ) + if( d->cdrecordBinObject->hasFeature("overburn") ) emit infoMessage( i18n("Enable overburning in the advanced K3b settings to burn anyway."), INFO ); } break; case BAD_OPTION: // error message has already been emitted earlier since we needed the actual line break; case SHMGET_FAILED: - emit infoMessage( i18n("%1 could not reserve shared memory segment of requested size.",m_cdrecordBinObject->name()), ERROR ); + emit infoMessage( i18n("%1 could not reserve shared memory segment of requested size.",d->cdrecordBinObject->name()), ERROR ); emit infoMessage( i18n("Probably you chose a too large buffer size."), ERROR ); break; case OPC_FAILED: emit infoMessage( i18n("OPC failed. Probably the writer does not like the medium."), ERROR ); break; case CANNOT_SET_SPEED: emit infoMessage( i18n("Unable to set write speed to %1.",d->usedSpeed), ERROR ); emit infoMessage( i18n("Probably this is lower than your writer's lowest writing speed."), ERROR ); break; case CANNOT_SEND_CUE_SHEET: emit infoMessage( i18n("Unable to send CUE sheet."), ERROR ); - if( m_writingMode == K3b::WRITING_MODE_DAO ) + if( d->writingMode == K3b::WRITING_MODE_DAO ) emit infoMessage( i18n("Sometimes using TAO writing mode solves this issue."), ERROR ); break; case CANNOT_OPEN_NEW_SESSION: emit infoMessage( i18n("Unable to open new session."), ERROR ); emit infoMessage( i18n("Probably a problem with the medium."), ERROR ); break; case CANNOT_FIXATE_DISK: emit infoMessage( i18n("The disk might still be readable."), ERROR ); - if( m_writingMode == K3b::WRITING_MODE_TAO && burnDevice()->dao() ) + if( d->writingMode == K3b::WRITING_MODE_TAO && burnDevice()->dao() ) emit infoMessage( i18n("Try DAO writing mode."), ERROR ); break; case PERMISSION_DENIED: emit infoMessage( i18n("%1 has no permission to open the device.",QString("cdrecord")), ERROR ); #ifdef HAVE_K3BSETUP emit infoMessage( i18n("You may use K3bsetup2 to solve this problem."), ERROR ); #endif break; case BUFFER_UNDERRUN: emit infoMessage( i18n("Probably a buffer underrun occurred."), ERROR ); if( !d->usingBurnfree && burnDevice()->burnproof() ) emit infoMessage( i18n("Please enable Burnfree or choose a lower burning speed."), ERROR ); else emit infoMessage( i18n("Please choose a lower burning speed."), ERROR ); break; case HIGH_SPEED_MEDIUM: emit infoMessage( i18n("Found a high-speed medium not suitable for the writer being used."), ERROR ); emit infoMessage( i18n("Use the 'force unsafe operations' option to ignore this."), ERROR ); break; case LOW_SPEED_MEDIUM: emit infoMessage( i18n("Found a low-speed medium not suitable for the writer being used."), ERROR ); emit infoMessage( i18n("Use the 'force unsafe operations' option to ignore this."), ERROR ); break; case MEDIUM_ERROR: emit infoMessage( i18n("Most likely the burning failed due to low-quality media."), ERROR ); break; case DEVICE_BUSY: emit infoMessage( i18n("Another application is blocking the device (most likely automounting)."), ERROR ); break; case WRITE_ERROR: emit infoMessage( i18n("A write error occurred."), ERROR ); - if( m_writingMode == K3b::WRITING_MODE_DAO ) + if( d->writingMode == K3b::WRITING_MODE_DAO ) emit infoMessage( i18n("Sometimes using TAO writing mode solves this issue."), ERROR ); break; case BLANK_FAILED: emit infoMessage( i18n("Some drives do not support all erase types."), ERROR ); emit infoMessage( i18n("Try again using 'Complete' erasing."), ERROR ); break; case UNKNOWN: - if( (exitCode == 12) && K3b::kernelVersion() >= K3b::Version( 2, 6, 8 ) && m_cdrecordBinObject->hasFeature( "suidroot" ) ) { + if( (exitCode == 12) && K3b::kernelVersion() >= K3b::Version( 2, 6, 8 ) && d->cdrecordBinObject->hasFeature( "suidroot" ) ) { emit infoMessage( i18n("Since kernel version 2.6.8 cdrecord cannot use SCSI transport when running suid root anymore."), ERROR ); emit infoMessage( i18n("You may use K3b::Setup to solve this problem or remove the suid bit manually."), ERROR ); } else if( !wasSourceUnreadable() ) { emit infoMessage( i18n("%1 returned an unknown error (code %2).", - m_cdrecordBinObject->name(), exitCode), + d->cdrecordBinObject->name(), exitCode), K3b::Job::ERROR ); - if( (exitCode >= 254) && m_writingMode == K3b::WRITING_MODE_DAO ) { + if( (exitCode >= 254) && d->writingMode == K3b::WRITING_MODE_DAO ) { emit infoMessage( i18n("Sometimes using TAO writing mode solves this issue."), ERROR ); } else { emit infoMessage( i18n("If you are running an unpatched cdrecord version..."), ERROR ); emit infoMessage( i18n("...and this error also occurs with high quality media..."), ERROR ); emit infoMessage( i18n("...and the K3b FAQ does not help you..."), ERROR ); emit infoMessage( i18n("...please include the debugging output in your problem report."), ERROR ); } } break; } jobFinished( false ); } } else { - emit infoMessage( i18n("%1 did not exit cleanly.",m_cdrecordBinObject->name()), + emit infoMessage( i18n("%1 crashed.", d->cdrecordBinObject->name()), ERROR ); jobFinished( false ); } } void K3b::CdrecordWriter::slotThroughput( int t ) { - emit writeSpeed( t, d->tracks.count() > m_currentTrack && !d->tracks[m_currentTrack-1].audio + emit writeSpeed( t, d->tracks.count() > d->currentTrack && !d->tracks[d->currentTrack-1].audio ? K3b::Device::SPEED_FACTOR_CD_MODE1 : d->usedSpeedFactor ); } + +qint64 K3b::CdrecordWriter::write( const char* data, qint64 maxSize ) +{ + return d->process.write( data, maxSize ); +} + #include "k3bcdrecordwriter.moc" diff --git a/libk3b/projects/k3bcdrecordwriter.h b/libk3b/projects/k3bcdrecordwriter.h index 7166cfc5f..5b63e79b0 100644 --- a/libk3b/projects/k3bcdrecordwriter.h +++ b/libk3b/projects/k3bcdrecordwriter.h @@ -1,118 +1,102 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3B_CDRECORD_WRITER_H #define K3B_CDRECORD_WRITER_H #include "k3babstractwriter.h" #include #include namespace K3b { class ExternalBin; class Process; namespace Device { class Device; } class CdrecordWriter : public AbstractWriter { Q_OBJECT public: CdrecordWriter( Device::Device*, JobHandler* hdl, - QObject* parent = 0 ); + QObject* parent = 0 ); ~CdrecordWriter(); bool active() const; /** * to be used in chain: addArgument(x)->addArgument(y) */ CdrecordWriter* addArgument( const QString& ); void clearArguments(); - int fd() const; + /** + * Write to the writer process. + * FIXME: make this an overloaded method from AbstractWriter + */ + qint64 write( const char* data, qint64 maxSize ); + + QIODevice* ioDevice() const; + bool closeFd(); public Q_SLOTS: void start(); void cancel(); void setDao( bool b ); void setWritingMode( WritingMode mode ); void setCueFile( const QString& s); void setClone( bool b ); - void setRawCdText( const QByteArray& a ) { m_rawCdText = a; } + void setRawCdText( const QByteArray& a ); protected Q_SLOTS: void slotStdLine( const QString& line ); void slotProcessExited( int exitCode, QProcess::ExitStatus exitStatus ); void slotThroughput( int t ); protected: virtual void prepareProcess(); - const ExternalBin* m_cdrecordBinObject; - Process* m_process; - - WritingMode m_writingMode; - bool m_totalTracksParsed; - bool m_clone; - bool m_cue; - - QString m_cueFile; - enum CdrecordError { UNKNOWN, OVERSIZE, BAD_OPTION, SHMGET_FAILED, OPC_FAILED, CANNOT_SET_SPEED, CANNOT_SEND_CUE_SHEET, CANNOT_OPEN_NEW_SESSION, CANNOT_FIXATE_DISK, WRITE_ERROR, PERMISSION_DENIED, BUFFER_UNDERRUN, HIGH_SPEED_MEDIUM, LOW_SPEED_MEDIUM, MEDIUM_ERROR, DEVICE_BUSY, BLANK_FAILED }; - QStringList m_arguments; - private: - int m_currentTrack; - int m_totalTracks; - int m_totalSize; - int m_alreadyWritten; - - int m_lastFifoValue; - - int m_cdrecordError; - - QByteArray m_rawCdText; - class Private; Private* d; }; } #endif diff --git a/libk3b/projects/k3bdoc.cpp b/libk3b/projects/k3bdoc.cpp index 039e46f56..2c54d7edc 100644 --- a/libk3b/projects/k3bdoc.cpp +++ b/libk3b/projects/k3bdoc.cpp @@ -1,222 +1,222 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 files for Qt #include #include #include // include files for KDE #include #include // application specific includes #include "k3bdoc.h" #include #include #include #include #include K3b::Doc::Doc( QObject* parent ) : QObject( parent ), m_modified(false), m_view(0) { connect( this, SIGNAL(changed()), this, SLOT(slotChanged()) ); } K3b::Doc::~Doc() { } void K3b::Doc::slotChanged() { setModified( true ); emit changed( this ); } void K3b::Doc::setModified( bool m ) { if( m != m_modified ) { m_modified = m; if( m ) emit changed(); } } void K3b::Doc::setDummy( bool b ) { m_dummy = b; } void K3b::Doc::setSpeed( int speed ) { m_speed = speed; } void K3b::Doc::setBurner( K3b::Device::Device* dev ) { m_burner = dev; } void K3b::Doc::addUrl( const KUrl& url ) { KUrl::List urls(url); addUrls( urls ); } void K3b::Doc::setURL( const KUrl& url ) { doc_url = url; emit changed(); } const KUrl& K3b::Doc::URL() const { return doc_url; } QString K3b::Doc::name() const { return URL().path().section( '/', -1 ); } bool K3b::Doc::newDocument() { setModified( false ); m_copies = 1; m_burner = 0; m_onTheFly = true; m_speed = 0; // Auto m_onlyCreateImages = false; m_removeImages = true; m_dummy = false; m_writingApp = K3b::WRITING_APP_DEFAULT; m_writingMode = K3b::WRITING_MODE_AUTO; m_saved = false; return true; } bool K3b::Doc::saveGeneralDocumentData( QDomElement* part ) { QDomDocument doc = part->ownerDocument(); QDomElement mainElem = doc.createElement( "general" ); QDomElement propElem = doc.createElement( "writing_mode" ); switch( writingMode() ) { case K3b::WRITING_MODE_DAO: propElem.appendChild( doc.createTextNode( "dao" ) ); break; case K3b::WRITING_MODE_TAO: propElem.appendChild( doc.createTextNode( "tao" ) ); break; case K3b::WRITING_MODE_RAW: propElem.appendChild( doc.createTextNode( "raw" ) ); break; default: propElem.appendChild( doc.createTextNode( "auto" ) ); break; } mainElem.appendChild( propElem ); propElem = doc.createElement( "dummy" ); propElem.setAttribute( "activated", dummy() ? "yes" : "no" ); mainElem.appendChild( propElem ); propElem = doc.createElement( "on_the_fly" ); propElem.setAttribute( "activated", onTheFly() ? "yes" : "no" ); mainElem.appendChild( propElem ); propElem = doc.createElement( "only_create_images" ); propElem.setAttribute( "activated", onlyCreateImages() ? "yes" : "no" ); mainElem.appendChild( propElem ); propElem = doc.createElement( "remove_images" ); propElem.setAttribute( "activated", removeImages() ? "yes" : "no" ); mainElem.appendChild( propElem ); part->appendChild( mainElem ); return true; } bool K3b::Doc::readGeneralDocumentData( const QDomElement& elem ) { if( elem.nodeName() != "general" ) return false; QDomNodeList nodes = elem.childNodes(); for( int i = 0; i < nodes.count(); i++ ) { QDomElement e = nodes.item(i).toElement(); if( e.isNull() ) return false; if( e.nodeName() == "writing_mode") { QString mode = e.text(); if( mode == "dao" ) setWritingMode( K3b::WRITING_MODE_DAO ); else if( mode == "tao" ) setWritingMode( K3b::WRITING_MODE_TAO ); else if( mode == "raw" ) setWritingMode( K3b::WRITING_MODE_RAW ); else setWritingMode( K3b::WRITING_MODE_AUTO ); } if( e.nodeName() == "dummy") setDummy( e.attributeNode( "activated" ).value() == "yes" ); if( e.nodeName() == "on_the_fly") setOnTheFly( e.attributeNode( "activated" ).value() == "yes" ); if( e.nodeName() == "only_create_images") setOnlyCreateImages( e.attributeNode( "activated" ).value() == "yes" ); if( e.nodeName() == "remove_images") setRemoveImages( e.attributeNode( "activated" ).value() == "yes" ); } return true; } -int K3b::Doc::supportedMediaTypes() const +K3b::Device::MediaTypes K3b::Doc::supportedMediaTypes() const { return K3b::Device::MEDIA_WRITABLE; } KIO::filesize_t K3b::Doc::burningSize() const { return size(); } #include "k3bdoc.moc" diff --git a/libk3b/projects/k3bdoc.h b/libk3b/projects/k3bdoc.h index ce2048b2f..608fed72e 100644 --- a/libk3b/projects/k3bdoc.h +++ b/libk3b/projects/k3bdoc.h @@ -1,237 +1,237 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3BDOC_H #define K3BDOC_H #include "config-k3b.h" #include "k3bglobals.h" // include files for QT #include #include // include files for KDE #include #include #include "k3b_export.h" class QDomElement; namespace K3b { class BurnJob; class JobHandler; class Msf; namespace Device { class Device; } /** * Doc is the base document class. * It handles some general settings. */ class LIBK3B_EXPORT Doc : public QObject { Q_OBJECT public: Doc( QObject* = 0 ); virtual ~Doc(); enum DocType { AUDIO = 1, DATA, MIXED, VCD, MOVIX, VIDEODVD }; virtual int type() const { return m_docType; } /** * \return A name for the project which might for example be used as a suggestion for a file name * when saving. The default implementation extracts a name from the URL. */ virtual QString name() const; /** * \return A string representation of the document type. */ virtual QString typeString() const = 0; /** * The media types that are supported by this project type. * The default implementation returns all writable media types. * This should NOT take into accout settings like the writing mode * or anything that can be changed in the burn dialog. */ - virtual int supportedMediaTypes() const; + virtual Device::MediaTypes supportedMediaTypes() const; /** * returns the view widget set with setView() or null if none has been set. */ QWidget* view() const { return m_view; } /** * Just for convenience to make an easy mapping from doc to GUI possible. */ void setView( QWidget* v ) { m_view = v; } /** * sets the modified flag for the document after a modifying action on the view connected to the document. */ virtual void setModified( bool m = true ); /** * returns if the document is modified or not. Use this to determine * if your document needs saving by the user on closing. */ virtual bool isModified() const { return m_modified; } /** * Subclasses should call this when reimplementing. * Sets some defaults. * FIXME: this method is completely useless. Just do it all in the constructor */ virtual bool newDocument(); /** * Clear project, i.e. remove all data that has ben added */ virtual void clear() = 0; /** * Load a project from an xml stream. * * This is used to load/save k3b projects. */ virtual bool loadDocumentData( QDomElement* root ) = 0; /** * Save a project to an xml stream. * * This is used to load/save k3b projects. */ virtual bool saveDocumentData( QDomElement* docElem ) = 0; /** returns the KUrl of the document */ const KUrl& URL() const; /** sets the URL of the document */ virtual void setURL( const KUrl& url ); WritingMode writingMode() const { return m_writingMode; } bool dummy() const { return m_dummy; } bool onTheFly() const { return m_onTheFly; } bool removeImages() const { return m_removeImages; } bool onlyCreateImages() const { return m_onlyCreateImages; } int copies() const { return m_copies; } int speed() const { return m_speed; } Device::Device* burner() const { return m_burner; } /** * \return the size that will actually be burnt to the medium. * This only differs from size() for multisession projects. */ virtual KIO::filesize_t burningSize() const; virtual KIO::filesize_t size() const = 0; virtual Msf length() const = 0; // FIXME: rename this to something like imagePath const QString& tempDir() const { return m_tempDir; } virtual int numOfTracks() const { return 1; } /** * Create a new BurnJob to burn this project. It is not mandatory to use this * method. You may also just create the BurnJob you need manually. It is just * easier this way since you don't need to distinguish between the different * project types. */ virtual BurnJob* newBurnJob( JobHandler*, QObject* parent = 0 ) = 0; WritingApp writingApp() const { return m_writingApp; } void setWritingApp( WritingApp a ) { m_writingApp = a; } /** * @return true if the document has successfully been saved to a file */ bool isSaved() const { return m_saved; } /** * Used for session management. Use with care. */ void setSaved( bool s ) { m_saved = s; } Q_SIGNALS: void changed(); void changed( K3b::Doc* ); public Q_SLOTS: void setDummy( bool d ); void setWritingMode( WritingMode m ) { m_writingMode = m; } void setOnTheFly( bool b ) { m_onTheFly = b; } void setSpeed( int speed ); void setBurner( Device::Device* dev ); void setTempDir( const QString& dir ) { m_tempDir = dir; } void setRemoveImages( bool b ) { m_removeImages = b; } void setOnlyCreateImages( bool b ) { m_onlyCreateImages = b; } void setCopies( int c ) { m_copies = c; } /** * the default implementation just calls addUrls with * list containing the url */ virtual void addUrl( const KUrl& url ); virtual void addUrls( const KUrl::List& urls ) = 0; protected: int m_docType; bool saveGeneralDocumentData( QDomElement* ); bool readGeneralDocumentData( const QDomElement& ); private Q_SLOTS: void slotChanged(); private: /** the modified flag of the current document */ bool m_modified; KUrl doc_url; QWidget* m_view; QString m_tempDir; Device::Device* m_burner; bool m_dummy; bool m_onTheFly; bool m_removeImages; bool m_onlyCreateImages; int m_speed; /** see k3bglobals.h */ WritingApp m_writingApp; WritingMode m_writingMode; int m_copies; bool m_saved; }; } #endif // K3BDOC_H diff --git a/libk3b/projects/k3bgrowisofswriter.cpp b/libk3b/projects/k3bgrowisofswriter.cpp index 3f9c4f85a..2319c0f37 100644 --- a/libk3b/projects/k3bgrowisofswriter.cpp +++ b/libk3b/projects/k3bgrowisofswriter.cpp @@ -1,612 +1,617 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bgrowisofswriter.h" #include #include #include #include #include #include #include #include #include #include "k3bgrowisofshandler.h" #include #include #include #include #include #include #include #include class K3b::GrowisofsWriter::Private { public: Private() : writingMode( K3b::WRITING_MODE_AUTO ), closeDvd(false), multiSession(false), - process( 0 ), growisofsBin( 0 ), trackSize(-1), - layerBreak(0), - usingRingBuffer(false), - ringBuffer(0) { + layerBreak(0) +// , usingRingBuffer(false), +// ringBuffer(0) + { } K3b::WritingMode writingMode; bool closeDvd; bool multiSession; - K3b::Process* process; + K3b::Process process; const K3b::ExternalBin* growisofsBin; QString image; bool success; bool canceled; bool finished; QTime lastSpeedCalculationTime; int lastSpeedCalculationBytes; int lastProgress; unsigned int lastProgressed; double lastWritingSpeed; bool writingStarted; K3b::ThroughputEstimator* speedEst; K3b::GrowisofsHandler* gh; // used in DAO with growisofs >= 5.15 long trackSize; long layerBreak; unsigned long long overallSizeFromOutput; long long firstSizeFromOutput; QFile inputFile; - bool usingRingBuffer; - K3b::PipeBuffer* ringBuffer; +// bool usingRingBuffer; +// K3b::PipeBuffer* ringBuffer; QString multiSessionInfo; int burnedMediumType; K3b::Device::SpeedMultiplicator speedMultiplicator() const { return ( burnedMediumType & K3b::Device::MEDIA_BD_ALL ? K3b::Device::SPEED_FACTOR_BD : K3b::Device::SPEED_FACTOR_DVD ); } }; K3b::GrowisofsWriter::GrowisofsWriter( K3b::Device::Device* dev, K3b::JobHandler* hdl, QObject* parent ) : K3b::AbstractWriter( dev, hdl, parent ) { d = new Private; d->speedEst = new K3b::ThroughputEstimator( this ); connect( d->speedEst, SIGNAL(throughput(int)), this, SLOT(slotThroughput(int)) ); d->gh = new K3b::GrowisofsHandler( this ); connect( d->gh, SIGNAL(infoMessage(const QString&, int)), this,SIGNAL(infoMessage(const QString&, int)) ); connect( d->gh, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( d->gh, SIGNAL(buffer(int)), this, SIGNAL(buffer(int)) ); connect( d->gh, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); connect( d->gh, SIGNAL(flushingCache()), this, SLOT(slotFlushingCache()) ); + + d->process.setSplitStdout(true); + d->process.setSuppressEmptyLines(true); + d->process.setFlags( K3bQProcess::RawStdin ); + connect( &d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotReceivedStderr(const QString&)) ); + connect( &d->process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessExited(int, QProcess::ExitStatus)) ); } K3b::GrowisofsWriter::~GrowisofsWriter() { - delete d->process; delete d; } bool K3b::GrowisofsWriter::active() const { - return (d->process ? d->process->isRunning() : false); + return d->process.isRunning(); +} + + +QIODevice* K3b::GrowisofsWriter::ioDevice() const +{ + return &d->process; } bool K3b::GrowisofsWriter::closeFd() { - if( d->process ) { - if( d->usingRingBuffer ) - return !::close( d->ringBuffer->inFd() ); - else { - d->process->closeWriteChannel(); - return true; - } + if ( d->process.isRunning() ) { + d->process.closeWriteChannel(); + return true; } - else + else { return false; + } } bool K3b::GrowisofsWriter::prepareProcess() { d->growisofsBin = k3bcore->externalBinManager()->binObject( "growisofs" ); if( !d->growisofsBin ) { emit infoMessage( i18n("Could not find %1 executable.",QString("growisofs")), ERROR ); return false; } if( d->growisofsBin->version < K3b::Version( 5, 10 ) ) { emit infoMessage( i18n("Growisofs version %1 is too old. " "K3b needs at least version 5.10.",d->growisofsBin->version), ERROR ); return false; } emit debuggingOutput( QLatin1String( "Used versions" ), QLatin1String( "growisofs: " ) + d->growisofsBin->version ); if( !d->growisofsBin->copyright.isEmpty() ) emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3",QString("growisofs") ,d->growisofsBin->version,d->growisofsBin->copyright), INFO ); // // The growisofs bin is ready. Now we add the parameters // - delete d->process; - d->process = new K3b::Process(); - d->process->setRunPrivileged(true); - // d->process->setPriority( K3Process::PrioHighest ); - d->process->setSplitStdout(true); - d->process->setRawStdin(true); - connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotReceivedStderr(const QString&)) ); - connect( d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotReceivedStderr(const QString&)) ); - connect( d->process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessExited(int, QProcess::ExitStatus)) ); - + d->process.clearProgram(); // // growisofs < 5.20 wants the tracksize to be a multiple of 16 (1 ECC block: 16*2048 bytes) // we simply pad ourselves. // // But since the writer itself properly pads or writes a longer lead-out we don't really need // to write zeros. We just tell growisofs to reserve a multiple of 16 blocks. // This is only releveant in DAO mode anyway. // // FIXME: seems as we also need this for double layer writing. Better make it the default and // actually write the pad bytes. The only possibility I see right now is to add a padding option // to the pipebuffer. int trackSizePadding = 0; if( d->trackSize > 0 && d->growisofsBin->version < K3b::Version( 5, 20 ) ) { if( d->trackSize % 16 ) { trackSizePadding = (16 - d->trackSize%16); kDebug() << "(K3b::GrowisofsWriter) need to pad " << trackSizePadding << " blocks."; } } - *d->process << d->growisofsBin; + d->process << d->growisofsBin; // set this var to true to enable the ringbuffer - d->usingRingBuffer = !d->growisofsBin->hasFeature( "buffer" ); +// d->usingRingBuffer = !d->growisofsBin->hasFeature( "buffer" ); QString s = burnDevice()->blockDeviceName() + "="; - if( d->usingRingBuffer || d->image.isEmpty() ) { + if( /*d->usingRingBuffer || */d->image.isEmpty() ) { // we always read from stdin since the ringbuffer does the actual reading from the source s += "/dev/fd/0"; } else s += d->image; if( d->multiSession && !d->multiSessionInfo.isEmpty() ) - *d->process << "-C" << d->multiSessionInfo; + d->process << "-C" << d->multiSessionInfo; if( d->multiSession ) - *d->process << "-M"; + d->process << "-M"; else - *d->process << "-Z"; - *d->process << s; + d->process << "-Z"; + d->process << s; - if( !d->image.isEmpty() && d->usingRingBuffer ) { + if( !d->image.isEmpty() /*&& d->usingRingBuffer*/ ) { d->inputFile.setFileName( d->image ); d->trackSize = (K3b::filesize( d->image ) + 1024) / 2048; if( !d->inputFile.open( QIODevice::ReadOnly ) ) { emit infoMessage( i18n("Could not open file %1.",d->image), ERROR ); return false; } } // now we use the force (luke ;) do not reload the dvd, K3b does that. - *d->process << "-use-the-force-luke=notray"; + d->process << "-use-the-force-luke=notray"; // we check for existing filesystems ourselves, so we always force the overwrite... - *d->process << "-use-the-force-luke=tty"; + d->process << "-use-the-force-luke=tty"; // we do the 4GB boundary check ourselves - *d->process << "-use-the-force-luke=4gms"; + d->process << "-use-the-force-luke=4gms"; bool dvdCompat = d->closeDvd; // DL writing with forced layer break if( d->layerBreak > 0 ) { - *d->process << "-use-the-force-luke=break:" + QString::number(d->layerBreak); + d->process << "-use-the-force-luke=break:" + QString::number(d->layerBreak); dvdCompat = true; } // the tracksize parameter takes priority over the dao:tracksize parameter since growisofs 5.18 else if( d->growisofsBin->hasFeature( "tracksize" ) && d->trackSize > 0 ) - *d->process << "-use-the-force-luke=tracksize:" + QString::number(d->trackSize + trackSizePadding); + d->process << "-use-the-force-luke=tracksize:" + QString::number(d->trackSize + trackSizePadding); if( simulate() ) - *d->process << "-use-the-force-luke=dummy"; + d->process << "-use-the-force-luke=dummy"; if( d->writingMode == K3b::WRITING_MODE_DAO ) { dvdCompat = true; if( d->growisofsBin->hasFeature( "daosize" ) && d->trackSize > 0 ) - *d->process << "-use-the-force-luke=dao:" + QString::number(d->trackSize + trackSizePadding); + d->process << "-use-the-force-luke=dao:" + QString::number(d->trackSize + trackSizePadding); else - *d->process << "-use-the-force-luke=dao"; + d->process << "-use-the-force-luke=dao"; d->gh->reset( burnDevice(), true ); } else d->gh->reset( burnDevice(), false ); d->burnedMediumType = burnDevice()->mediaType(); // // Never use the -dvd-compat parameter with DVD+RW media // because the only thing it does is creating problems. // Normally this should be done in growisofs // int mediaType = burnDevice()->mediaType(); if( dvdCompat && mediaType != K3b::Device::MEDIA_DVD_PLUS_RW && mediaType != K3b::Device::MEDIA_DVD_RW_OVWR ) - *d->process << "-dvd-compat"; + d->process << "-dvd-compat"; // // Some DVD writers do not allow changing the writing speed so we allow // the user to ignore the speed setting // int speed = burnSpeed(); if( speed >= 0 ) { if( speed == 0 ) { // try to determine the writeSpeed // if it fails determineOptimalWriteSpeed() will return 0 and // the choice is left to growisofs which means that the choice is // really left to the drive since growisofs does not change the speed // if no option is given speed = burnDevice()->determineMaximalWriteSpeed(); } if( speed != 0 ) { if ( d->burnedMediumType & K3b::Device::MEDIA_DVD_ALL ) { // speed may be a float number. example: DVD+R(W): 2.4x - *d->process << QString("-speed=%1").arg( speed%1385 > 0 + d->process << QString("-speed=%1").arg( speed%1385 > 0 ? QString::number( (float)speed/1385.0, 'f', 1 ) : QString::number( speed/1385 ) ); } else if ( d->burnedMediumType & K3b::Device::MEDIA_BD_ALL ) { - *d->process << QString("-speed=%1").arg( QString::number( speed/4496 ) ); + d->process << QString("-speed=%1").arg( QString::number( speed/4496 ) ); } } } if( k3bcore->globalSettings()->overburn() ) - *d->process << "-overburn"; + d->process << "-overburn"; - if( !d->usingRingBuffer && d->growisofsBin->hasFeature( "buffer" ) ) { + if( /*!d->usingRingBuffer && */d->growisofsBin->hasFeature( "buffer" ) ) { bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); int bufSize = ( manualBufferSize ? k3bcore->globalSettings()->bufferSize() : 32 ); - *d->process << QString("-use-the-force-luke=bufsize:%1m").arg(bufSize); + d->process << QString("-use-the-force-luke=bufsize:%1m").arg(bufSize); } // additional user parameters from config const QStringList& params = d->growisofsBin->userParameters(); for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) - *d->process << *it; + d->process << *it; emit debuggingOutput( "Burned media", K3b::Device::mediaTypeString(mediaType) ); return true; } void K3b::GrowisofsWriter::start() { jobStarted(); d->lastWritingSpeed = 0; d->lastProgressed = 0; d->lastProgress = 0; d->firstSizeFromOutput = -1; d->lastSpeedCalculationTime = QTime::currentTime(); d->lastSpeedCalculationBytes = 0; d->writingStarted = false; d->canceled = false; d->speedEst->reset(); d->finished = false; if( !prepareProcess() ) { jobFinished( false ); } else { kDebug() << "***** " << d->growisofsBin->name() << " parameters:\n"; - QString s = d->process->joinedArgs(); + QString s = d->process.joinedArgs(); kDebug() << s << flush; emit debuggingOutput( d->growisofsBin->name() + " command:", s); emit newSubTask( i18n("Preparing write process...") ); // FIXME: check the return value if( K3b::isMounted( burnDevice() ) ) { emit infoMessage( i18n("Unmounting medium"), INFO ); K3b::unmount( burnDevice() ); } // block the device (including certain checks) k3bcore->blockDevice( burnDevice() ); // lock the device for good in this process since it will // be opened in the growisofs process burnDevice()->close(); burnDevice()->usageLock(); - if( !d->process->start( K3Process::All ) ) { + if( !d->process.start( KProcess::MergedChannels ) ) { // something went wrong when starting the program // it "should" be the executable kDebug() << "(K3b::GrowisofsWriter) could not start " << d->growisofsBin->path; emit infoMessage( i18n("Could not start %1.",d->growisofsBin->name()), K3b::Job::ERROR ); jobFinished(false); } else { if( simulate() ) { emit newTask( i18n("Simulating") ); emit infoMessage( i18n("Starting simulation..."), K3b::Job::INFO ); } else { emit newTask( i18n("Writing") ); emit infoMessage( i18n("Starting disc write..."), K3b::Job::INFO ); } d->gh->handleStart(); // create the ring buffer - if( d->usingRingBuffer ) { - if( !d->ringBuffer ) { - d->ringBuffer = new K3b::PipeBuffer( this, this ); - connect( d->ringBuffer, SIGNAL(percent(int)), this, SIGNAL(buffer(int)) ); - connect( d->ringBuffer, SIGNAL(finished(bool)), this, SLOT(slotRingBufferFinished(bool)) ); - } - - d->ringBuffer->writeToFd( d->process->stdinFd() ); - bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); - int bufSize = ( manualBufferSize ? k3bcore->globalSettings()->bufferSize() : 20 ); - d->ringBuffer->setBufferSize( bufSize ); - - if( !d->image.isEmpty() ) - d->ringBuffer->readFromFd( d->inputFile.handle() ); - - d->ringBuffer->start(); - } +// if( d->usingRingBuffer ) { +// if( !d->ringBuffer ) { +// d->ringBuffer = new K3b::PipeBuffer( this, this ); +// connect( d->ringBuffer, SIGNAL(percent(int)), this, SIGNAL(buffer(int)) ); +// connect( d->ringBuffer, SIGNAL(finished(bool)), this, SLOT(slotRingBufferFinished(bool)) ); +// } + +// d->ringBuffer->writeTo( d->process ); +// bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); +// int bufSize = ( manualBufferSize ? k3bcore->globalSettings()->bufferSize() : 20 ); +// d->ringBuffer->setBufferSize( bufSize ); + +// if( !d->image.isEmpty() ) +// d->ringBuffer->readFrom( &d->inputFile ); + +// d->ringBuffer->start(); +// } } } } void K3b::GrowisofsWriter::cancel() { if( active() ) { d->canceled = true; closeFd(); - if( d->usingRingBuffer && d->ringBuffer ) - d->ringBuffer->cancel(); - d->process->kill(); +// if( d->usingRingBuffer && d->ringBuffer ) +// d->ringBuffer->cancel(); + d->process.terminate(); } } void K3b::GrowisofsWriter::setWritingMode( K3b::WritingMode m ) { d->writingMode = m; } void K3b::GrowisofsWriter::setTrackSize( long size ) { d->trackSize = size; } void K3b::GrowisofsWriter::setLayerBreak( long lb ) { d->layerBreak = lb; } void K3b::GrowisofsWriter::setCloseDvd( bool b ) { d->closeDvd = b; } void K3b::GrowisofsWriter::setMultiSession( bool b ) { d->multiSession = b; } void K3b::GrowisofsWriter::setImageToWrite( const QString& filename ) { d->image = filename; } void K3b::GrowisofsWriter::slotReceivedStderr( const QString& line ) { emit debuggingOutput( d->growisofsBin->name(), line ); if( line.contains( "remaining" ) ) { if( !d->writingStarted ) { d->writingStarted = true; emit newSubTask( i18n("Writing data") ); } // parse progress int pos = line.indexOf( '/' ); unsigned long long done = line.left( pos ).toULongLong(); bool ok = true; d->overallSizeFromOutput = line.mid( pos+1, line.indexOf( '(', pos ) - pos - 1 ).toULongLong( &ok ); if( d->firstSizeFromOutput == -1 ) d->firstSizeFromOutput = done; done -= d->firstSizeFromOutput; d->overallSizeFromOutput -= d->firstSizeFromOutput; if( ok ) { int p = (int)(100 * done / d->overallSizeFromOutput); if( p > d->lastProgress ) { emit percent( p ); emit subPercent( p ); d->lastProgress = p; } if( (unsigned int)(done/1024/1024) > d->lastProgressed ) { d->lastProgressed = (unsigned int)(done/1024/1024); emit processedSize( d->lastProgressed, (int)(d->overallSizeFromOutput/1024/1024) ); emit processedSubSize( d->lastProgressed, (int)(d->overallSizeFromOutput/1024/1024) ); } // try parsing write speed (since growisofs 5.11) pos = line.indexOf( '@' ); if( pos != -1 ) { pos += 1; double speed = line.mid( pos, line.indexOf( 'x', pos ) - pos ).toDouble(&ok); if( ok ) { if( d->lastWritingSpeed != speed ) emit writeSpeed( (int)(speed*d->speedMultiplicator()), d->speedMultiplicator() ); d->lastWritingSpeed = speed; } else kDebug() << "(K3b::GrowisofsWriter) speed parsing failed: '" << line.mid( pos, line.indexOf( 'x', pos ) - pos ) << "'" << endl; } else { d->speedEst->dataWritten( done/1024 ); } } else kDebug() << "(K3b::GrowisofsWriter) progress parsing failed: '" << line.mid( pos+1, line.indexOf( '(', pos ) - pos - 1 ).trimmed() << "'" << endl; } // else // to be able to parse the ring buffer fill in growisofs 6.0 we need to do this all the time // FIXME: get rid of the K3b::GrowisofsHandler once it is sure that we do not need the K3b::GrowisofsImager anymore d->gh->handleLine( line ); } void K3b::GrowisofsWriter::slotProcessExited( int exitCode, QProcess::ExitStatus ) { d->inputFile.close(); // release the device within this process burnDevice()->usageUnlock(); // unblock the device k3bcore->unblockDevice( burnDevice() ); if( d->canceled ) { if( !d->finished ) { d->finished = true; // this will unblock and eject the drive and emit the finished/canceled signals K3b::AbstractWriter::cancel(); } return; } d->finished = true; // it seems that growisofs sometimes exits with a valid exit code while a write error occurred if( (exitCode == 0) && d->gh->error() != K3b::GrowisofsHandler::ERROR_WRITE_FAILED ) { int s = d->speedEst->average(); if( s > 0 ) emit infoMessage( ki18n("Average overall write speed: %1 KB/s (%2x)") .subs( s ) .subs( ( double )s/( double )d->speedMultiplicator(), 0, 'g', 2 ).toString(), INFO ); if( simulate() ) emit infoMessage( i18n("Simulation successfully completed"), K3b::Job::SUCCESS ); else emit infoMessage( i18n("Writing successfully completed"), K3b::Job::SUCCESS ); d->success = true; } else { if( !wasSourceUnreadable() ) d->gh->handleExit( exitCode ); d->success = false; } jobFinished(d->success); } -void K3b::GrowisofsWriter::slotRingBufferFinished( bool ) -{ - if( !d->finished ) { - d->finished = true; - // this will unblock and eject the drive and emit the finished/canceled signals - K3b::AbstractWriter::cancel(); - } -} +// void K3b::GrowisofsWriter::slotRingBufferFinished( bool ) +// { +// if( !d->finished ) { +// d->finished = true; +// // this will unblock and eject the drive and emit the finished/canceled signals +// K3b::AbstractWriter::cancel(); +// } +// } void K3b::GrowisofsWriter::slotThroughput( int t ) { emit writeSpeed( t, d->speedMultiplicator() ); } void K3b::GrowisofsWriter::slotFlushingCache() { if( !d->canceled ) { // // growisofs's progress output stops before 100%, so we do it manually // emit percent( 100 ); emit processedSize( d->overallSizeFromOutput/1024/1024, d->overallSizeFromOutput/1024/1024 ); } } void K3b::GrowisofsWriter::setMultiSessionInfo( const QString& info ) { d->multiSessionInfo = info; } + +qint64 K3b::GrowisofsWriter::write( const char* data, qint64 maxSize ) +{ + return d->process.write( data, maxSize ); +} + #include "k3bgrowisofswriter.moc" diff --git a/libk3b/projects/k3bgrowisofswriter.h b/libk3b/projects/k3bgrowisofswriter.h index b5825ec10..5ee6eb5ad 100644 --- a/libk3b/projects/k3bgrowisofswriter.h +++ b/libk3b/projects/k3bgrowisofswriter.h @@ -1,103 +1,111 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_GROWISOFS_WRITER_H_ #define _K3B_GROWISOFS_WRITER_H_ #include #include "k3babstractwriter.h" namespace K3b { namespace Device { class Device; } class GrowisofsWriter : public AbstractWriter { Q_OBJECT public: GrowisofsWriter( Device::Device*, JobHandler*, - QObject* parent = 0 ); + QObject* parent = 0 ); ~GrowisofsWriter(); bool active() const; + /** + * Write to the writer process. + * FIXME: make this an overloaded method from AbstractWriter + */ + qint64 write( const char* data, qint64 maxSize ); + + QIODevice* ioDevice() const; + public Q_SLOTS: void start(); void cancel(); void setWritingMode( K3b::WritingMode mode ); /** * If true the growisofs parameter -M is used in favor of -Z. */ void setMultiSession( bool b ); /** * Only used in DAO mode and only supported with growisofs >= 5.15 * @param size size in blocks */ void setTrackSize( long size ); /** * Use this in combination with setTrackSize when writing double layer media. * @param lb The number of data sectors in the first layer. It needs to be less or equal * to tracksize/2. The writer will pad the second layer with zeros if * break < tracksize/2. * If set to 0 this setting will be ignored. */ void setLayerBreak( long lb ); /** * Close the DVD to enable max DVD compatibility (uses the growisofs --dvd-compat parameter) * This will also be used in DAO mode and when the layerBreak has been set. */ void setCloseDvd( bool ); /** * set this to QString() or an empty string to let the writer * read it's data from fd() */ void setImageToWrite( const QString& ); /** * While reading the image from stdin growisofs needs * a valid -C parameter for multisession. */ void setMultiSessionInfo( const QString& ); protected: bool prepareProcess(); protected Q_SLOTS: void slotReceivedStderr( const QString& ); void slotProcessExited( int, QProcess::ExitStatus ); void slotThroughput( int t ); void slotFlushingCache(); - void slotRingBufferFinished( bool ); +// void slotRingBufferFinished( bool ); private: bool closeFd(); class Private; Private* d; }; } #endif diff --git a/libk3b/projects/k3bpipebuffer.cpp b/libk3b/projects/k3bpipebuffer.cpp index 264a0c1ab..e84928d85 100644 --- a/libk3b/projects/k3bpipebuffer.cpp +++ b/libk3b/projects/k3bpipebuffer.cpp @@ -1,268 +1,268 @@ /* * - * Copyright (C) 2003-2008 Sebastian Trueg + * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bpipebuffer.h" #include #include #include #include #include #include #include #include #include #include // // This one is based on the little pipebuf2 program by Peter Osterlund // class K3b::PipeBuffer::Private { public: Private() : buffer(0), bufSize(4*1024*1024) { outFd = inFd = -1; inFdPair[0] = inFdPair[1] = -1; } ~Private() { delete [] buffer; } bool initFds() { if( inFd == -1 ) { if( ::socketpair(AF_UNIX, SOCK_STREAM, 0, inFdPair) ) { // if( ::pipe( inFdPair ) ) { kDebug() << "(K3b::PipeBuffer::WorkThread) unable to create socketpair"; inFdPair[0] = inFdPair[1] = -1; return false; } else { ::fcntl(inFdPair[0], F_SETFL, O_NONBLOCK); ::fcntl(outFd, F_SETFL, O_NONBLOCK); } } else { ::fcntl(inFd, F_SETFL, O_NONBLOCK); } delete [] buffer; buffer = new char[bufSize]; return (buffer != 0); } char* buffer; unsigned int bufSize; - int outFd; - int inFd; + QIODevice* outIoDevice; + QIODevice* inIoDevice; int inFdPair[2]; }; K3b::PipeBuffer::PipeBuffer( K3b::JobHandler* jh, QObject* parent ) : K3b::ThreadJob( jh, parent ), d( new Private() ) { } K3b::PipeBuffer::~PipeBuffer() { delete d; } void K3b::PipeBuffer::start() { // // Create the socketpair in the gui thread to be sure it's available after // this method returns. // if( !d->initFds() ) jobFinished(false); else K3b::ThreadJob::start(); } void K3b::PipeBuffer::setBufferSize( int mb ) { d->bufSize = mb * 1024 * 1024; } void K3b::PipeBuffer::readFromFd( int fd ) { d->inFd = fd; } void K3b::PipeBuffer::writeToFd( int fd ) { d->outFd = fd; } int K3b::PipeBuffer::inFd() const { if( d->inFd == -1 ) return d->inFdPair[1]; else return d->inFd; } bool K3b::PipeBuffer::run() { int usedInFd = -1; if( d->inFd > 0 ) usedInFd = d->inFd; else usedInFd = d->inFdPair[0]; kDebug() << "(K3b::PipeBuffer::WorkThread) reading from " << usedInFd << " and writing to " << d->outFd << endl; kDebug() << "(K3b::PipeBuffer::WorkThread) using buffer size of " << d->bufSize; // start the buffering unsigned int bufPos = 0; unsigned int dataLen = 0; bool eof = false; bool error = false; int oldPercent = 0; static const unsigned int MAX_BUFFER_READ = 2048*3; while( !canceled() && !error && (!eof || dataLen > 0) ) { // // create two fd sets // fd_set readFds, writeFds; FD_ZERO(&readFds); FD_ZERO(&writeFds); // // fill the fd sets // if( !eof && dataLen < d->bufSize ) FD_SET(usedInFd, &readFds); if( dataLen > 0 ) FD_SET(d->outFd, &writeFds); // // wait for data // int ret = select( qMax(usedInFd, d->outFd) + 1, &readFds, &writeFds, NULL, NULL); // // Do the buffering // if( !canceled() && ret > 0 ) { int currentPercent = -1; // // Read from the buffer and write to the output // if( FD_ISSET(d->outFd, &writeFds) ) { unsigned int maxLen = qMin(d->bufSize - bufPos, dataLen); ret = ::write( d->outFd, &d->buffer[bufPos], maxLen ); if( ret < 0 ) { if( (errno != EINTR) && (errno != EAGAIN) ) { kDebug() << "(K3b::PipeBuffer::WorkThread) error while writing to " << d->outFd; error = true; } } else { // // we always emit before the reading from the buffer since // it makes way more sense to show the buffer before the reading. // currentPercent = (int)((double)dataLen*100.0/(double)d->bufSize); bufPos = (bufPos + ret) % d->bufSize; dataLen -= ret; } } // // Read into the buffer // else if( FD_ISSET(usedInFd, &readFds) ) { unsigned int readPos = (bufPos + dataLen) % d->bufSize; unsigned int maxLen = qMin(d->bufSize - readPos, d->bufSize - dataLen); // // never read more than xxx bytes // This is some tuning to prevent the reading from blocking the whole thread // if( maxLen > MAX_BUFFER_READ ) // some dummy value below 1 MB maxLen = MAX_BUFFER_READ; ret = ::read( usedInFd, &d->buffer[readPos], maxLen ); if( ret < 0 ) { if( (errno != EINTR) && (errno != EAGAIN) ) { kDebug() << "(K3b::PipeBuffer::WorkThread) error while reading from " << usedInFd; error = true; } } else if( ret == 0 ) { kDebug() << "(K3b::PipeBuffer::WorkThread) end of input."; eof = true; } else { dataLen += ret; currentPercent = (int)((double)dataLen*100.0/(double)d->bufSize); } } // A little hack to keep the buffer display from flickering if( currentPercent == 99 ) currentPercent = 100; if( currentPercent != -1 && currentPercent != oldPercent ) { emit percent( currentPercent ); oldPercent = currentPercent; } } else if( !canceled() ) { error = true; kDebug() << "(K3b::PipeBuffer::WorkThread) select: " << ::strerror(errno); } } if( d->inFd == -1 ) { ::close( d->inFdPair[0] ); ::close( d->inFdPair[1] ); d->inFdPair[0] = d->inFdPair[1] = -1; } // // close the fd we are writing to (this is need to make growisofs happy // TODO: perhaps make this configurable // ::close( d->outFd ); return !error && !canceled(); } #include "k3bpipebuffer.moc" diff --git a/libk3b/projects/k3bpipebuffer.h b/libk3b/projects/k3bpipebuffer.h index 7f7b5b8c6..fc54996f7 100644 --- a/libk3b/projects/k3bpipebuffer.h +++ b/libk3b/projects/k3bpipebuffer.h @@ -1,63 +1,63 @@ /* * - * Copyright (C) 2003-2008 Sebastian Trueg + * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_PIPE_BUFFER_H_ #define _K3B_PIPE_BUFFER_H_ #include namespace K3b { /** * the pipebuffer uses the signal percent to show it's status. */ class PipeBuffer : public ThreadJob { Q_OBJECT public: PipeBuffer( JobHandler*, QObject* parent = 0 ); ~PipeBuffer(); /** * Set the buffer size in MB. The default value is 4 MB. */ void setBufferSize( int ); /** * If this is set to -1 (which is the default) the pipebuffer * will create a fd pair which can be obtained by inFd() after * the buffer has been started. */ - void readFromFd( int fd ); - void writeToFd( int fd ); + void readFrom( QIODevice* dev ); + void writeTo( QIODevice* dev ); /** * This is only valid after the piepbuffer has been started and no fd * has been set with readFromFd. */ int inFd() const; public Q_SLOTS: void start(); private: bool run(); class Private; Private* const d; }; } #endif diff --git a/libk3b/projects/mixedcd/k3bmixeddoc.cpp b/libk3b/projects/mixedcd/k3bmixeddoc.cpp index 8357b91ca..558561586 100644 --- a/libk3b/projects/mixedcd/k3bmixeddoc.cpp +++ b/libk3b/projects/mixedcd/k3bmixeddoc.cpp @@ -1,261 +1,261 @@ /* * * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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 "k3bmixeddoc.h" #include "k3bmixedjob.h" #include #include #include #include #include #include #include #include #include #include K3b::MixedDoc::MixedDoc( QObject* parent ) : K3b::Doc( parent ) { m_dataDoc = new K3b::DataDoc( this ); m_audioDoc = new K3b::AudioDoc( this ); connect( m_dataDoc, SIGNAL(changed()), this, SIGNAL(changed()) ); connect( m_audioDoc, SIGNAL(changed()), this, SIGNAL(changed()) ); } K3b::MixedDoc::~MixedDoc() { } bool K3b::MixedDoc::newDocument() { m_dataDoc->newDocument(); m_audioDoc->newDocument(); return K3b::Doc::newDocument(); } void K3b::MixedDoc::clear() { m_dataDoc->clear(); m_audioDoc->clear(); } QString K3b::MixedDoc::name() const { return m_dataDoc->name(); } void K3b::MixedDoc::setURL( const KUrl& url ) { K3b::Doc::setURL( url ); m_audioDoc->setURL( url ); m_dataDoc->setURL( url ); } void K3b::MixedDoc::setModified( bool m ) { m_audioDoc->setModified( m ); m_dataDoc->setModified( m ); } bool K3b::MixedDoc::isModified() const { return ( m_audioDoc->isModified() || m_dataDoc->isModified() ); } KIO::filesize_t K3b::MixedDoc::size() const { return m_dataDoc->size() + m_audioDoc->size(); } K3b::Msf K3b::MixedDoc::length() const { return m_dataDoc->length() + m_audioDoc->length(); } int K3b::MixedDoc::numOfTracks() const { return m_audioDoc->numOfTracks() + 1; } K3b::BurnJob* K3b::MixedDoc::newBurnJob( K3b::JobHandler* hdl, QObject* parent ) { return new K3b::MixedJob( this, hdl, parent ); } void K3b::MixedDoc::addUrls( const KUrl::List& urls ) { dataDoc()->addUrls( urls ); } bool K3b::MixedDoc::loadDocumentData( QDomElement* rootElem ) { QDomNodeList nodes = rootElem->childNodes(); if( nodes.length() < 4 ) return false; if( nodes.item(0).nodeName() != "general" ) return false; if( !readGeneralDocumentData( nodes.item(0).toElement() ) ) return false; if( nodes.item(1).nodeName() != "audio" ) return false; QDomElement audioElem = nodes.item(1).toElement(); if( !m_audioDoc->loadDocumentData( &audioElem ) ) return false; if( nodes.item(2).nodeName() != "data" ) return false; QDomElement dataElem = nodes.item(2).toElement(); if( !m_dataDoc->loadDocumentData( &dataElem ) ) return false; if( nodes.item(3).nodeName() != "mixed" ) return false; QDomNodeList optionList = nodes.item(3).childNodes(); for( int i = 0; i < optionList.count(); i++ ) { QDomElement e = optionList.item(i).toElement(); if( e.isNull() ) return false; if( e.nodeName() == "remove_buffer_files" ) setRemoveImages( e.toElement().text() == "yes" ); else if( e.nodeName() == "image_path" ) setTempDir( e.toElement().text() ); else if( e.nodeName() == "mixed_type" ) { QString mt = e.toElement().text(); if( mt == "last_track" ) setMixedType( DATA_LAST_TRACK ); else if( mt == "second_session" ) setMixedType( DATA_SECOND_SESSION ); else setMixedType( DATA_FIRST_TRACK ); } } return true; } bool K3b::MixedDoc::saveDocumentData( QDomElement* docElem ) { QDomDocument doc = docElem->ownerDocument(); saveGeneralDocumentData( docElem ); QDomElement audioElem = doc.createElement( "audio" ); m_audioDoc->saveDocumentData( &audioElem ); docElem->appendChild( audioElem ); QDomElement dataElem = doc.createElement( "data" ); m_dataDoc->saveDocumentData( &dataElem ); docElem->appendChild( dataElem ); QDomElement mixedElem = doc.createElement( "mixed" ); docElem->appendChild( mixedElem ); QDomElement bufferFilesElem = doc.createElement( "remove_buffer_files" ); bufferFilesElem.appendChild( doc.createTextNode( removeImages() ? "yes" : "no" ) ); mixedElem.appendChild( bufferFilesElem ); QDomElement imagePathElem = doc.createElement( "image_path" ); imagePathElem.appendChild( doc.createTextNode( tempDir() ) ); mixedElem.appendChild( imagePathElem ); QDomElement mixedTypeElem = doc.createElement( "mixed_type" ); switch( mixedType() ) { case DATA_FIRST_TRACK: mixedTypeElem.appendChild( doc.createTextNode( "first_track" ) ); break; case DATA_LAST_TRACK: mixedTypeElem.appendChild( doc.createTextNode( "last_track" ) ); break; case DATA_SECOND_SESSION: mixedTypeElem.appendChild( doc.createTextNode( "second_session" ) ); break; } mixedElem.appendChild( mixedTypeElem ); setModified( false ); return true; } K3b::Device::Toc K3b::MixedDoc::toToc( K3b::Device::Track::DataMode dataMode, const K3b::Msf& dataTrackLength ) const { // !inaccurate datatrack size! K3b::Device::Track dataTrack( 0, dataTrackLength > 0 ? dataTrackLength-1 : m_dataDoc->length()-1, K3b::Device::Track::TYPE_DATA, dataMode ); K3b::Device::Toc toc = audioDoc()->toToc(); if( mixedType() == DATA_FIRST_TRACK ) { // fix the audio tracks' sectors for( K3b::Device::Toc::iterator it = toc.begin(); it != toc.end(); ++it ) { (*it).setLastSector( (*it).lastSector() + dataTrack.length() ); (*it).setFirstSector( (*it).firstSector() + dataTrack.length() ); } toc.insert( toc.begin(), dataTrack ); } else { // fix the datatrack's sectors dataTrack.setLastSector( dataTrack.lastSector() + toc.back().lastSector()+1 ); dataTrack.setFirstSector( toc.back().lastSector()+1 ); toc.append( dataTrack ); if( mixedType() == DATA_SECOND_SESSION ) { // fix the session numbers for( K3b::Device::Toc::iterator it = toc.begin(); it != toc.end(); ++it ) { if( (*it).type() == K3b::Device::Track::TYPE_DATA ) (*it).setSession( 2 ); else (*it).setSession( 1 ); } } } return toc; } -int K3b::MixedDoc::supportedMediaTypes() const +K3b::Device::MediaTypes K3b::MixedDoc::supportedMediaTypes() const { return K3b::Device::MEDIA_WRITABLE_CD; } #include "k3bmixeddoc.moc" diff --git a/libk3b/projects/mixedcd/k3bmixeddoc.h b/libk3b/projects/mixedcd/k3bmixeddoc.h index 23bd27e0d..872bd9ea2 100644 --- a/libk3b/projects/mixedcd/k3bmixeddoc.h +++ b/libk3b/projects/mixedcd/k3bmixeddoc.h @@ -1,93 +1,93 @@ /* * * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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. */ #ifndef K3B_MIXED_DOC_H #define K3B_MIXED_DOC_H #include "k3bdoc.h" #include "k3bdatadoc.h" #include "k3baudiodoc.h" #include "k3btoc.h" #include "k3b_export.h" class QDomElement; namespace K3b { class BurnJob; class LIBK3B_EXPORT MixedDoc : public Doc { Q_OBJECT public: MixedDoc( QObject* parent = 0 ); ~MixedDoc(); QString name() const; - int supportedMediaTypes() const; + Device::MediaTypes supportedMediaTypes() const; bool newDocument(); void clear(); void setModified( bool m = true ); bool isModified() const; KIO::filesize_t size() const; Msf length() const; int numOfTracks() const; BurnJob* newBurnJob( JobHandler*, QObject* parent = 0 ); AudioDoc* audioDoc() const { return m_audioDoc; } DataDoc* dataDoc() const { return m_dataDoc; } enum MixedType { DATA_FIRST_TRACK, DATA_LAST_TRACK, DATA_SECOND_SESSION }; int mixedType() const { return m_mixedType; } int type() const { return MIXED; } void setURL( const KUrl& url ); /** * Represent the structure of the doc as CD Table of Contents. * Be aware that the length of the data track is just an estimate * and needs to be corrected if not specified here. * * @param dataMode mode of the data track (MODE1 or XA_FORM1) * @param dataTrackLength exact length of the dataTrack */ Device::Toc toToc( K3b::Device::Track::DataMode dataMode, const Msf& dataTrackLength = 0 ) const; public Q_SLOTS: void setMixedType( MixedType t ) { m_mixedType = t; } void addUrls( const KUrl::List& urls ); protected: bool loadDocumentData( QDomElement* ); bool saveDocumentData( QDomElement* ); QString typeString() const { return "mixed"; } private: DataDoc* m_dataDoc; AudioDoc* m_audioDoc; int m_mixedType; }; } #endif diff --git a/libk3b/projects/mixedcd/k3bmixedjob.cpp b/libk3b/projects/mixedcd/k3bmixedjob.cpp index 2498da0cd..f0a226565 100644 --- a/libk3b/projects/mixedcd/k3bmixedjob.cpp +++ b/libk3b/projects/mixedcd/k3bmixedjob.cpp @@ -1,1345 +1,1344 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bmixedjob.h" #include "k3bmixeddoc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QString createNonExistingFilesString( const QList& items, int max ) { QString s; int cnt = 0; for( QList::const_iterator it = items.begin(); it != items.end(); ++it ) { s += KStringHandler::csqueeze( (*it)->filename(), 60 ); ++cnt; if( cnt >= max || it == items.end() ) break; s += "
"; } if( items.count() > max ) s += "..."; return s; } class K3b::MixedJob::Private { public: Private() : maxSpeedJob(0) { } int copies; int copiesDone; K3b::AudioMaxSpeedJob* maxSpeedJob; bool maxSpeed; }; K3b::MixedJob::MixedJob( K3b::MixedDoc* doc, K3b::JobHandler* hdl, QObject* parent ) : K3b::BurnJob( hdl, parent ), m_doc( doc ), m_normalizeJob(0) { d = new Private; m_isoImager = new K3b::IsoImager( doc->dataDoc(), this, this ); connect( m_isoImager, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_isoImager, SIGNAL(percent(int)), this, SLOT(slotIsoImagerPercent(int)) ); connect( m_isoImager, SIGNAL(finished(bool)), this, SLOT(slotIsoImagerFinished(bool)) ); connect( m_isoImager, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); m_audioImager = new K3b::AudioImager( doc->audioDoc(), this, this ); connect( m_audioImager, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_audioImager, SIGNAL(percent(int)), this, SLOT(slotAudioDecoderPercent(int)) ); connect( m_audioImager, SIGNAL(subPercent(int)), this, SLOT(slotAudioDecoderSubPercent(int)) ); connect( m_audioImager, SIGNAL(finished(bool)), this, SLOT(slotAudioDecoderFinished(bool)) ); connect( m_audioImager, SIGNAL(nextTrack(int, int)), this, SLOT(slotAudioDecoderNextTrack(int, int)) ); m_msInfoFetcher = new K3b::MsInfoFetcher( this, this ); connect( m_msInfoFetcher, SIGNAL(finished(bool)), this, SLOT(slotMsInfoFetched(bool)) ); connect( m_msInfoFetcher, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); m_writer = 0; m_tocFile = 0; m_tempData = new K3b::AudioJobTempData( m_doc->audioDoc(), this ); } K3b::MixedJob::~MixedJob() { delete m_tocFile; delete d; } K3b::Device::Device* K3b::MixedJob::writer() const { if( m_doc->onlyCreateImages() ) return 0; else return m_doc->burner(); } K3b::Doc* K3b::MixedJob::doc() const { return m_doc; } void K3b::MixedJob::start() { jobStarted(); m_canceled = false; m_errorOccuredAndAlreadyReported = false; d->copiesDone = 0; d->copies = m_doc->copies(); m_currentAction = PREPARING_DATA; d->maxSpeed = false; if( m_doc->dummy() ) d->copies = 1; prepareProgressInformation(); // // Check if all files exist // QList nonExistingFiles; K3b::AudioTrack* track = m_doc->audioDoc()->firstTrack(); while( track ) { K3b::AudioDataSource* source = track->firstSource(); while( source ) { if( K3b::AudioFile* file = dynamic_cast( source ) ) { if( !QFile::exists( file->filename() ) ) nonExistingFiles.append( file ); } source = source->next(); } track = track->next(); } if( !nonExistingFiles.isEmpty() ) { if( questionYesNo( "

" + i18n("The following files could not be found. Do you want to remove them from the " "project and continue without adding them to the image?") + "

" + createNonExistingFilesString( nonExistingFiles, 10 ), i18n("Warning"), i18n("Remove missing files and continue"), i18n("Cancel and go back") ) ) { for( QList::const_iterator it = nonExistingFiles.constBegin(); it != nonExistingFiles.constEnd(); ++it ) { delete *it; } } else { m_canceled = true; emit canceled(); jobFinished(false); return; } } // // Make sure the project is not empty // if( m_doc->audioDoc()->numOfTracks() == 0 ) { emit infoMessage( i18n("Please add files to your project first."), ERROR ); jobFinished(false); return; } // set some flags that are needed m_doc->audioDoc()->setOnTheFly( m_doc->onTheFly() ); // for the toc writer m_doc->audioDoc()->setHideFirstTrack( false ); // unsupported m_doc->dataDoc()->setBurner( m_doc->burner() ); // so the isoImager can read ms data emit newTask( i18n("Preparing data") ); determineWritingMode(); // // First we make sure the data portion is valid // // we do not have msinfo yet m_currentAction = INITIALIZING_IMAGER; m_isoImager->setMultiSessionInfo( QString() ); m_isoImager->init(); } void K3b::MixedJob::startFirstCopy() { // // if not onthefly create the iso image and then the wavs // and write then // if onthefly calculate the iso size // if( m_doc->onTheFly() ) { if( m_doc->speed() == 0 ) { emit newSubTask( i18n("Determining maximum writing speed") ); // // try to determine the max possible speed // no need to check the data track's max speed. Most current systems are able // to handle the maximum possible // if( !d->maxSpeedJob ) { // the maxspeed job gets the device from the doc: m_doc->audioDoc()->setBurner( m_doc->burner() ); d->maxSpeedJob = new K3b::AudioMaxSpeedJob( m_doc->audioDoc(), this, this ); connect( d->maxSpeedJob, SIGNAL(percent(int)), this, SIGNAL(subPercent(int)) ); connect( d->maxSpeedJob, SIGNAL(finished(bool)), this, SLOT(slotMaxSpeedJobFinished(bool)) ); } d->maxSpeedJob->start(); } else if( m_doc->mixedType() != K3b::MixedDoc::DATA_SECOND_SESSION ) { m_currentAction = PREPARING_DATA; m_isoImager->calculateSize(); } else { // we cannot calculate the size since we don't have the msinfo yet // so first write the audio session writeNextCopy(); } } else { emit burning(false); emit infoMessage( i18n("Creating audio image files in %1",m_doc->tempDir()), INFO ); m_tempFilePrefix = K3b::findUniqueFilePrefix( ( !m_doc->audioDoc()->title().isEmpty() ? m_doc->audioDoc()->title() : m_doc->dataDoc()->isoOptions().volumeID() ), m_doc->tempDir() ); m_tempData->prepareTempFileNames( m_doc->tempDir() ); QStringList filenames; for( K3b::AudioTrack* track = m_doc->audioDoc()->firstTrack(); track; track = track->next() ) filenames += m_tempData->bufferFileName( track ); m_audioImager->setImageFilenames( filenames ); if( m_doc->mixedType() != K3b::MixedDoc::DATA_SECOND_SESSION ) { createIsoImage(); } else { emit newTask( i18n("Creating audio image files") ); m_currentAction = CREATING_AUDIO_IMAGE; m_audioImager->start(); } } } void K3b::MixedJob::slotMaxSpeedJobFinished( bool success ) { d->maxSpeed = success; if( !success ) emit infoMessage( i18n("Unable to determine maximum speed for some reason. Ignoring."), WARNING ); if( m_doc->mixedType() != K3b::MixedDoc::DATA_SECOND_SESSION ) { m_currentAction = PREPARING_DATA; m_isoImager->calculateSize(); } else { // we cannot calculate the size since we don't have the msinfo yet // so first write the audio session writeNextCopy(); } } void K3b::MixedJob::writeNextCopy() { if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { m_currentAction = WRITING_AUDIO_IMAGE; if( !prepareWriter() || !startWriting() ) { cleanupAfterError(); jobFinished(false); } else if( m_doc->onTheFly() ) m_audioImager->start(); } else { // the prepareWriter method needs the action to be set if( m_doc->mixedType() == K3b::MixedDoc::DATA_LAST_TRACK ) m_currentAction = WRITING_AUDIO_IMAGE; else m_currentAction = WRITING_ISO_IMAGE; if( !prepareWriter() || !startWriting() ) { cleanupAfterError(); jobFinished(false); } else if( m_doc->onTheFly() ) { if( m_doc->mixedType() == K3b::MixedDoc::DATA_LAST_TRACK ) m_audioImager->start(); else m_isoImager->start(); } } } void K3b::MixedJob::cancel() { m_canceled = true; if( d->maxSpeedJob ) d->maxSpeedJob->cancel(); if( m_writer ) m_writer->cancel(); m_isoImager->cancel(); m_audioImager->cancel(); m_msInfoFetcher->cancel(); emit infoMessage( i18n("Writing canceled."), K3b::Job::ERROR ); removeBufferFiles(); emit canceled(); jobFinished(false); } void K3b::MixedJob::slotMsInfoFetched( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( success ) { if( m_usedDataWritingApp == K3b::WRITING_APP_CDRECORD ) m_isoImager->setMultiSessionInfo( m_msInfoFetcher->msInfo() ); else // cdrdao seems to write a 150 blocks pregap that is not used by cdrecord m_isoImager->setMultiSessionInfo( QString("%1,%2") .arg(m_msInfoFetcher->lastSessionStart()) .arg(m_msInfoFetcher->nextSessionStart()+150) ); if( m_doc->onTheFly() ) { m_currentAction = PREPARING_DATA; m_isoImager->calculateSize(); } else { createIsoImage(); } } else { // the MsInfoFetcher already emitted failure info cleanupAfterError(); jobFinished(false); } } void K3b::MixedJob::slotIsoImagerFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; // // Initializing imager before the first copy // if( m_currentAction == INITIALIZING_IMAGER ) { if( success ) { m_currentAction = PREPARING_DATA; // check the size m_projectSize = m_isoImager->size() + m_doc->audioDoc()->length(); if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) m_projectSize += 11400; // the session gap startFirstCopy(); } else { cleanupAfterError(); jobFinished( false ); } } // // Recalculated iso image size // else if( m_currentAction == PREPARING_DATA ) { if( success ) { // 1. data in first track: // start isoimager and writer // when isoimager finishes start audiodecoder // 2. data in last track // start audiodecoder and writer // when audiodecoder finishes start isoimager // 3. data in second session // start audiodecoder and writer // start isoimager and writer if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { m_currentAction = WRITING_ISO_IMAGE; if( !prepareWriter() || !startWriting() ) { cleanupAfterError(); jobFinished(false); } else m_isoImager->start(); } else writeNextCopy(); } else { cleanupAfterError(); jobFinished( false ); } } // // Image creation finished // else { if( !success ) { emit infoMessage( i18n("Error while creating ISO image."), ERROR ); cleanupAfterError(); jobFinished( false ); return; } if( m_doc->onTheFly() ) { if( m_doc->mixedType() == K3b::MixedDoc::DATA_FIRST_TRACK ) { m_currentAction = WRITING_AUDIO_IMAGE; m_audioImager->start(); } } else { emit infoMessage( i18n("ISO image successfully created."), SUCCESS ); if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { m_currentAction = WRITING_ISO_IMAGE; if( !prepareWriter() || !startWriting() ) { cleanupAfterError(); jobFinished(false); } } else { emit newTask( i18n("Creating audio image files") ); m_currentAction = CREATING_AUDIO_IMAGE; m_audioImager->start(); } } } } void K3b::MixedJob::slotWriterFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( !success ) { cleanupAfterError(); jobFinished(false); return; } emit burning(false); if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION && m_currentAction == WRITING_AUDIO_IMAGE ) { // many drives need to reload the medium to return to a proper state if ( ( int )m_doc->burner()->readToc().count() < m_doc->numOfTracks()-1 ) { emit infoMessage( i18n( "Need to reload medium to return to proper state." ), INFO ); connect( K3b::Device::reload( m_doc->burner() ), SIGNAL(finished(K3b::Device::DeviceHandler*)), this, SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) ); } else { slotMediaReloadedForSecondSession( true ); } } else { d->copiesDone++; if( d->copiesDone < d->copies ) { if( !m_doc->burner()->eject() ) { blockingInformation( i18n("K3b was unable to eject the written disk. Please do so manually.") ); } writeNextCopy(); } else { if( !m_doc->onTheFly() && m_doc->removeImages() ) removeBufferFiles(); if ( k3bcore->globalSettings()->ejectMedia() ) { K3b::Device::eject( m_doc->burner() ); } jobFinished(true); } } } void K3b::MixedJob::slotMediaReloadedForSecondSession( bool success ) { if( !success ) blockingInformation( i18n("Please reload the medium and press 'ok'"), i18n("Unable to close the tray") ); // start the next session m_currentAction = WRITING_ISO_IMAGE; if( d->copiesDone > 0 ) { // we only create the image once. This should not be a problem??? if( !prepareWriter() || !startWriting() ) { cleanupAfterError(); jobFinished(false); } else if( m_doc->onTheFly() ) { m_isoImager->start(); } } else if( m_doc->dummy() ) { // do not try to get ms info in simulation mode since the cd is empty! if( m_doc->onTheFly() ) { m_currentAction = PREPARING_DATA; m_isoImager->calculateSize(); } else createIsoImage(); } else { m_currentAction = FETCHING_MSINFO; m_msInfoFetcher->setDevice( m_doc->burner() ); m_msInfoFetcher->start(); } } void K3b::MixedJob::slotAudioDecoderFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( !success ) { emit infoMessage( i18n("Error while decoding audio tracks."), ERROR ); cleanupAfterError(); jobFinished(false); return; } if( m_doc->onTheFly() ) { if( m_doc->mixedType() == K3b::MixedDoc::DATA_LAST_TRACK ) { m_currentAction = WRITING_ISO_IMAGE; m_isoImager->start(); } } else { emit infoMessage( i18n("Audio images successfully created."), SUCCESS ); if( m_doc->audioDoc()->normalize() ) { normalizeFiles(); } else { if( m_doc->mixedType() == K3b::MixedDoc::DATA_FIRST_TRACK ) m_currentAction = WRITING_ISO_IMAGE; else m_currentAction = WRITING_AUDIO_IMAGE; if( !prepareWriter() || !startWriting() ) { cleanupAfterError(); jobFinished(false); } } } } void K3b::MixedJob::slotAudioDecoderNextTrack( int t, int tt ) { if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) { K3b::AudioTrack* track = m_doc->audioDoc()->getTrack(t); emit newSubTask( i18n("Decoding audio track %1 of %2%3" ,t ,tt , track->title().isEmpty() || track->artist().isEmpty() ? QString() : " (" + track->artist() + " - " + track->title() + ")" ) ); } } bool K3b::MixedJob::prepareWriter() { if( m_writer ) delete m_writer; if( ( m_currentAction == WRITING_ISO_IMAGE && m_usedDataWritingApp == K3b::WRITING_APP_CDRECORD ) || ( m_currentAction == WRITING_AUDIO_IMAGE && m_usedAudioWritingApp == K3b::WRITING_APP_CDRECORD ) ) { if( !writeInfFiles() ) { kDebug() << "(K3b::MixedJob) could not write inf-files."; emit infoMessage( i18n("IO Error"), ERROR ); return false; } K3b::CdrecordWriter* writer = new K3b::CdrecordWriter( m_doc->burner(), this, this ); // only write the audio tracks in DAO mode if( m_currentAction == WRITING_ISO_IMAGE ) writer->setWritingMode( m_usedDataWritingMode ); else writer->setWritingMode( m_usedAudioWritingMode ); writer->setSimulate( m_doc->dummy() ); writer->setBurnSpeed( m_doc->speed() ); if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { if( m_currentAction == WRITING_ISO_IMAGE ) { if( m_doc->onTheFly() ) writer->addArgument("-waiti"); addDataTrack( writer ); } else { writer->addArgument("-multi"); addAudioTracks( writer ); } } else { if( m_doc->mixedType() == K3b::MixedDoc::DATA_FIRST_TRACK ) addDataTrack( writer ); addAudioTracks( writer ); if( m_doc->mixedType() == K3b::MixedDoc::DATA_LAST_TRACK ) addDataTrack( writer ); } m_writer = writer; } else { if( !writeTocFile() ) { kDebug() << "(K3b::DataJob) could not write tocfile."; emit infoMessage( i18n("IO Error"), ERROR ); return false; } // create the writer // create cdrdao job K3b::CdrdaoWriter* writer = new K3b::CdrdaoWriter( m_doc->burner(), this, this ); writer->setSimulate( m_doc->dummy() ); writer->setBurnSpeed( m_doc->speed() ); // multisession only for the first session writer->setMulti( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION && m_currentAction == WRITING_AUDIO_IMAGE ); writer->setTocFile( m_tocFile->fileName() ); m_writer = writer; } connect( m_writer, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_writer, SIGNAL(percent(int)), this, SLOT(slotWriterJobPercent(int)) ); connect( m_writer, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); connect( m_writer, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) ); connect( m_writer, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); connect( m_writer, SIGNAL(nextTrack(int, int)), this, SLOT(slotWriterNextTrack(int, int)) ); connect( m_writer, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); connect( m_writer, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); connect( m_writer, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)), this, SIGNAL(writeSpeed(int, K3b::Device::SpeedMultiplicator)) ); connect( m_writer, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) ); // connect( m_writer, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); connect( m_writer, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( m_writer, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); return true; } bool K3b::MixedJob::writeInfFiles() { K3b::InfFileWriter infFileWriter; K3b::AudioTrack* track = m_doc->audioDoc()->firstTrack(); while( track ) { infFileWriter.setTrack( track->toCdTrack() ); infFileWriter.setTrackNumber( track->trackNumber() ); if( !m_doc->onTheFly() ) infFileWriter.setBigEndian( false ); if( !infFileWriter.save( m_tempData->infFileName(track) ) ) return false; track = track->next(); } return true; } bool K3b::MixedJob::writeTocFile() { // FIXME: create the tocfile in the same directory like all the other files. if( m_tocFile ) delete m_tocFile; m_tocFile = new KTemporaryFile(); m_tocFile->setSuffix( ".toc" ); m_tocFile->open(); // write the toc-file QTextStream s( m_tocFile ); K3b::TocFileWriter tocFileWriter; // // TOC // tocFileWriter.setData( m_doc->toToc( m_usedDataMode == K3b::DATA_MODE_2 ? K3b::Device::Track::XA_FORM1 : K3b::Device::Track::MODE1, m_doc->onTheFly() ? m_isoImager->size() : m_doc->dataDoc()->length() ) ); // // CD-Text // if( m_doc->audioDoc()->cdText() ) { K3b::Device::CdText text = m_doc->audioDoc()->cdTextData(); // if data in first track we need to add a dummy cdtext if( m_doc->mixedType() == K3b::MixedDoc::DATA_FIRST_TRACK ) text.insert( 0, K3b::Device::TrackCdText() ); tocFileWriter.setCdText( text ); } // // Session to write // tocFileWriter.setSession( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION && m_currentAction == WRITING_ISO_IMAGE ? 2 : 1 ); // // image filenames // if( !m_doc->onTheFly() ) { QStringList files; K3b::AudioTrack* track = m_doc->audioDoc()->firstTrack(); while( track ) { files += m_tempData->bufferFileName( track ); track = track->next(); } if( m_doc->mixedType() == K3b::MixedDoc::DATA_FIRST_TRACK ) files.prepend( m_isoImageFilePath ); else files.append( m_isoImageFilePath ); tocFileWriter.setFilenames( files ); } bool success = tocFileWriter.save( s ); m_tocFile->close(); // backup for debugging // KIO::NetAccess::del("/tmp/trueg/tocfile_debug_backup.toc",0L); // KIO::NetAccess::copy( m_tocFile->name(), "/tmp/trueg/tocfile_debug_backup.toc",0L ); return success; } void K3b::MixedJob::addAudioTracks( K3b::CdrecordWriter* writer ) { writer->addArgument( "-useinfo" ); // add raw cdtext data if( m_doc->audioDoc()->cdText() ) { writer->setRawCdText( m_doc->audioDoc()->cdTextData().rawPackData() ); } writer->addArgument( "-audio" ); // we always pad because although K3b makes sure all tracks' length are multiples of 2352 // it seems that normalize sometimes corrupts these lengths // FIXME: see K3b::AudioJob for the whole less4secs and zeroPregap handling writer->addArgument( "-pad" ); // Allow tracks shorter than 4 seconds writer->addArgument( "-shorttrack" ); // add all the audio tracks K3b::AudioTrack* track = m_doc->audioDoc()->firstTrack(); while( track ) { if( m_doc->onTheFly() ) { // this is only supported by cdrecord versions >= 2.01a13 writer->addArgument( QFile::encodeName( m_tempData->infFileName( track ) ) ); } else { writer->addArgument( QFile::encodeName( m_tempData->bufferFileName( track ) ) ); } track = track->next(); } } void K3b::MixedJob::addDataTrack( K3b::CdrecordWriter* writer ) { // add data track if( m_usedDataMode == K3b::DATA_MODE_2 ) { if( k3bcore->externalBinManager()->binObject("cdrecord") && k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) ) writer->addArgument( "-xa" ); else writer->addArgument( "-xa1" ); } else writer->addArgument( "-data" ); if( m_doc->onTheFly() ) writer->addArgument( QString("-tsize=%1s").arg(m_isoImager->size()) )->addArgument("-"); else writer->addArgument( m_isoImageFilePath ); } void K3b::MixedJob::slotWriterNextTrack( int t, int ) { K3b::AudioTrack* track = 0; if( m_doc->mixedType() == K3b::MixedDoc::DATA_FIRST_TRACK ) { if( t > 1 ) track = m_doc->audioDoc()->getTrack(t-1); } else if( m_doc->mixedType() == K3b::MixedDoc::DATA_LAST_TRACK ) { if( t < m_doc->audioDoc()->numOfTracks()+1 ) track = m_doc->audioDoc()->getTrack(t); } else if( m_currentAction == WRITING_AUDIO_IMAGE ) track = m_doc->audioDoc()->getTrack(t); else t = m_doc->numOfTracks(); if( track ) emit newSubTask( i18n("Writing track %1 of %2%3" ,t ,m_doc->numOfTracks() , track->title().isEmpty() || track->artist().isEmpty() ? QString() : " (" + track->artist() + " - " + track->title() + ")" ) ); else emit newSubTask( i18n("Writing track %1 of %2 (%3)",t,m_doc->numOfTracks(),i18n("ISO9660 data")) ); } void K3b::MixedJob::slotWriterJobPercent( int p ) { double totalTasks = d->copies; double tasksDone = d->copiesDone; if( m_doc->audioDoc()->normalize() ) { totalTasks+=1.0; tasksDone+=1.0; } if( !m_doc->onTheFly() ) { totalTasks+=1.0; } if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { if( m_currentAction == WRITING_AUDIO_IMAGE ) { // the audio imager has finished in all cases // the iso imager only if this is not the first copy if( d->copiesDone > 0 ) tasksDone += 1.0; else if( !m_doc->onTheFly() ) tasksDone += m_audioDocPartOfProcess; p = (int)((double)p*m_audioDocPartOfProcess); } else { // all images have been created if( !m_doc->onTheFly() ) tasksDone += 1.0; p = (int)(100.0*m_audioDocPartOfProcess + (double)p*(1.0-m_audioDocPartOfProcess)); } } else if( !m_doc->onTheFly() ) tasksDone += 1.0; emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } void K3b::MixedJob::slotAudioDecoderPercent( int p ) { // the only thing finished here might be the isoimager which is part of this task if( !m_doc->onTheFly() ) { double totalTasks = d->copies+1; if( m_doc->audioDoc()->normalize() ) totalTasks+=1.0; if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) p = (int)((double)p*m_audioDocPartOfProcess); else p = (int)(100.0*(1.0-m_audioDocPartOfProcess) + (double)p*m_audioDocPartOfProcess); emit percent( (int)((double)p / totalTasks) ); } } void K3b::MixedJob::slotAudioDecoderSubPercent( int p ) { if( !m_doc->onTheFly() ) { emit subPercent( p ); } } void K3b::MixedJob::slotIsoImagerPercent( int p ) { if( !m_doc->onTheFly() ) { emit subPercent( p ); if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { double totalTasks = d->copies+1.0; double tasksDone = d->copiesDone; if( m_doc->audioDoc()->normalize() ) { totalTasks+=1.0; // the normalizer finished tasksDone+=1.0; } // the writing of the audio part finished tasksDone += m_audioDocPartOfProcess; // the audio decoder finished (which is part of this task in terms of progress) p = (int)(100.0*m_audioDocPartOfProcess + (double)p*(1.0-m_audioDocPartOfProcess)); emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } else { double totalTasks = d->copies+1.0; if( m_doc->audioDoc()->normalize() ) totalTasks+=1.0; emit percent( (int)((double)(p*(1.0-m_audioDocPartOfProcess)) / totalTasks) ); } } } bool K3b::MixedJob::startWriting() { if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { if( m_currentAction == WRITING_ISO_IMAGE) { if( m_doc->dummy() ) emit newTask( i18n("Simulating second session") ); else if( d->copies > 1 ) emit newTask( i18n("Writing second session of copy %1",d->copiesDone+1) ); else emit newTask( i18n("Writing second session") ); } else { if( m_doc->dummy() ) emit newTask( i18n("Simulating first session") ); else if( d->copies > 1 ) emit newTask( i18n("Writing first session of copy %1",d->copiesDone+1) ); else emit newTask( i18n("Writing first session") ); } } else if( m_doc->dummy() ) emit newTask( i18n("Simulating") ); else emit newTask( i18n("Writing Copy %1",d->copiesDone+1) ); // if we append the second session the cd is already in the drive if( !(m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION && m_currentAction == WRITING_ISO_IMAGE) ) { emit newSubTask( i18n("Waiting for media") ); if( waitForMedia( m_doc->burner() ) < 0 ) { cancel(); return false; } // just to be sure we did not get canceled during the async discWaiting if( m_canceled ) return false; // check if the project will fit on the CD if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { // the media is in and has been checked so this should be fast (hopefully) K3b::Msf mediaSize = m_doc->burner()->diskInfo().capacity(); if( mediaSize < m_projectSize ) { if( k3bcore->globalSettings()->overburn() ) { emit infoMessage( i18n("Trying to write more than the official disk capacity"), K3b::Job::WARNING ); } else { emit infoMessage( i18n("Data does not fit on disk."), ERROR ); return false; } } } } // in case we determined the max possible writing speed we have to reset the speed on the writer job // here since an inserted media is necessary // the Max speed job will compare the max speed value with the supported values of the writer if( d->maxSpeed ) m_writer->setBurnSpeed( d->maxSpeedJob->maxSpeed() ); emit burning(true); m_writer->start(); if( m_doc->onTheFly() ) { // now the writer is running and we can get it's stdin // we only use this method when writing on-the-fly since // we cannot easily change the audioDecode fd while it's working // which we would need to do since we write into several // image files. - m_audioImager->writeToFd( m_writer->fd() ); - m_isoImager->writeToFd( m_writer->fd() ); + m_audioImager->writeTo( m_writer->ioDevice() ); +// m_isoImager->writeTo( m_writer->ioDevice() ); +#warning FIXME } return true; } void K3b::MixedJob::createIsoImage() { m_currentAction = CREATING_ISO_IMAGE; // prepare iso image file m_isoImageFilePath = m_tempFilePrefix + "_datatrack.iso"; if( !m_doc->onTheFly() ) emit newTask( i18n("Creating ISO image file") ); emit newSubTask( i18n("Creating ISO image in %1",m_isoImageFilePath) ); emit infoMessage( i18n("Creating ISO image in %1",m_isoImageFilePath), INFO ); - m_isoImager->writeToImageFile( m_isoImageFilePath ); +// m_isoImager->writeToImageFile( m_isoImageFilePath ); +#warning FIXME m_isoImager->start(); } void K3b::MixedJob::cleanupAfterError() { m_errorOccuredAndAlreadyReported = true; // m_audioImager->cancel(); m_isoImager->cancel(); if( m_writer ) m_writer->cancel(); delete m_tocFile; m_tocFile = 0; // remove the temp files removeBufferFiles(); } void K3b::MixedJob::removeBufferFiles() { if ( !m_doc->onTheFly() ) { emit infoMessage( i18n("Removing buffer files."), INFO ); } if( QFile::exists( m_isoImageFilePath ) ) if( !QFile::remove( m_isoImageFilePath ) ) emit infoMessage( i18n("Could not delete file %1.",m_isoImageFilePath), ERROR ); // removes buffer images and temp toc or inf files m_tempData->cleanup(); } void K3b::MixedJob::determineWritingMode() { // we don't need this when only creating image and it is possible // that the burn device is null if( m_doc->onlyCreateImages() ) return; // at first we determine the data mode // -------------------------------------------------------------- if( m_doc->dataDoc()->dataMode() == K3b::DATA_MODE_AUTO ) { if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) m_usedDataMode = K3b::DATA_MODE_2; else m_usedDataMode = K3b::DATA_MODE_1; } else m_usedDataMode = m_doc->dataDoc()->dataMode(); // we try to use cdrecord if possible bool cdrecordOnTheFly = false; bool cdrecordCdText = false; bool cdrecordUsable = false; if( k3bcore->externalBinManager()->binObject("cdrecord") ) { cdrecordOnTheFly = k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "audio-stdin" ); cdrecordCdText = k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "cdtext" ); cdrecordUsable = !( !cdrecordOnTheFly && m_doc->onTheFly() ) && !( m_doc->audioDoc()->cdText() && !cdrecordCdText ); } // Writing Application // -------------------------------------------------------------- // cdrecord seems to have problems writing xa 1 disks in dao mode? At least on my system! if( writingApp() == K3b::WRITING_APP_DEFAULT ) { if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { if( m_doc->writingMode() == K3b::WRITING_MODE_DAO || ( m_doc->writingMode() == K3b::WRITING_MODE_AUTO && !cdrecordUsable ) ) { m_usedAudioWritingApp = K3b::WRITING_APP_CDRDAO; m_usedDataWritingApp = K3b::WRITING_APP_CDRDAO; } else { m_usedAudioWritingApp = K3b::WRITING_APP_CDRECORD; m_usedDataWritingApp = K3b::WRITING_APP_CDRECORD; } } else { if( cdrecordUsable ) { m_usedAudioWritingApp = K3b::WRITING_APP_CDRECORD; m_usedDataWritingApp = K3b::WRITING_APP_CDRECORD; } else { m_usedAudioWritingApp = K3b::WRITING_APP_CDRDAO; m_usedDataWritingApp = K3b::WRITING_APP_CDRDAO; } } } else { m_usedAudioWritingApp = writingApp(); m_usedDataWritingApp = writingApp(); } // TODO: use K3b::Exceptions::brokenDaoAudio // Writing Mode (TAO/DAO/RAW) // -------------------------------------------------------------- if( m_doc->writingMode() == K3b::WRITING_MODE_AUTO ) { if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { if( m_usedDataWritingApp == K3b::WRITING_APP_CDRECORD ) m_usedDataWritingMode = K3b::WRITING_MODE_TAO; else m_usedDataWritingMode = K3b::WRITING_MODE_DAO; // default to Session at once for the audio part m_usedAudioWritingMode = K3b::WRITING_MODE_DAO; } else if( writer()->dao() ) { m_usedDataWritingMode = K3b::WRITING_MODE_DAO; m_usedAudioWritingMode = K3b::WRITING_MODE_DAO; } else { m_usedDataWritingMode = K3b::WRITING_MODE_TAO; m_usedAudioWritingMode = K3b::WRITING_MODE_TAO; } } else { m_usedAudioWritingMode = m_doc->writingMode(); m_usedDataWritingMode = m_doc->writingMode(); } if( m_usedDataWritingApp == K3b::WRITING_APP_CDRECORD ) { if( !cdrecordOnTheFly && m_doc->onTheFly() ) { m_doc->setOnTheFly( false ); emit infoMessage( i18n("On-the-fly writing with cdrecord < 2.01a13 not supported."), ERROR ); } if( m_doc->audioDoc()->cdText() ) { if( !cdrecordCdText ) { m_doc->audioDoc()->writeCdText( false ); emit infoMessage( i18n("Cdrecord %1 does not support CD-Text writing.",k3bcore->externalBinManager()->binObject("cdrecord")->version), ERROR ); } else if( m_usedAudioWritingMode == K3b::WRITING_MODE_TAO ) { emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode. Try DAO or RAW."), WARNING ); } } } } void K3b::MixedJob::normalizeFiles() { if( !m_normalizeJob ) { m_normalizeJob = new K3b::AudioNormalizeJob( this, this ); connect( m_normalizeJob, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); connect( m_normalizeJob, SIGNAL(percent(int)), this, SLOT(slotNormalizeProgress(int)) ); connect( m_normalizeJob, SIGNAL(subPercent(int)), this, SLOT(slotNormalizeSubProgress(int)) ); connect( m_normalizeJob, SIGNAL(finished(bool)), this, SLOT(slotNormalizeJobFinished(bool)) ); connect( m_normalizeJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); connect( m_normalizeJob, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); } // add all the files QList files; K3b::AudioTrack* track = m_doc->audioDoc()->firstTrack(); while( track ) { files.append( m_tempData->bufferFileName(track) ); track = track->next(); } m_normalizeJob->setFilesToNormalize( files ); emit newTask( i18n("Normalizing volume levels") ); m_normalizeJob->start(); } void K3b::MixedJob::slotNormalizeJobFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( success ) { if( m_doc->mixedType() == K3b::MixedDoc::DATA_FIRST_TRACK ) m_currentAction = WRITING_ISO_IMAGE; else m_currentAction = WRITING_AUDIO_IMAGE; if( !prepareWriter() || !startWriting() ) { cleanupAfterError(); jobFinished(false); } } else { cleanupAfterError(); jobFinished(false); } } void K3b::MixedJob::slotNormalizeProgress( int p ) { double totalTasks = d->copies+2.0; double tasksDone = 0; if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) { // the audio imager finished (m_audioDocPartOfProcess*1 task) // plus the normalize progress tasksDone = m_audioDocPartOfProcess; } else { // the iso and audio imagers already finished (one task) // plus the normalize progress tasksDone = 1.0; } emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } void K3b::MixedJob::slotNormalizeSubProgress( int p ) { emit subPercent( p ); } void K3b::MixedJob::prepareProgressInformation() { // calculate percentage of audio and data // this is also used in on-the-fly mode double ds = (double)m_doc->dataDoc()->length().totalFrames(); double as = (double)m_doc->audioDoc()->length().totalFrames(); m_audioDocPartOfProcess = as/(ds+as); } QString K3b::MixedJob::jobDescription() const { if( m_doc->mixedType() == K3b::MixedDoc::DATA_SECOND_SESSION ) return i18n("Writing Enhanced Audio CD") + ( m_doc->audioDoc()->title().isEmpty() ? QString() : QString( " (%1)" ).arg(m_doc->audioDoc()->title()) ); else return i18n("Writing Mixed Mode CD") + ( m_doc->audioDoc()->title().isEmpty() ? QString() : QString( " (%1)" ).arg(m_doc->audioDoc()->title()) ); } QString K3b::MixedJob::jobDetails() const { return ( i18n("%1 tracks (%2 minutes audio data, %3 ISO9660 data)" ,m_doc->numOfTracks() ,m_doc->audioDoc()->length().toString() ,KIO::convertSize(m_doc->dataDoc()->size())) + ( m_doc->copies() > 1 && !m_doc->dummy() ? i18np(" - %1 copy", " - %1 copies", m_doc->copies()) : QString() ) ); } #include "k3bmixedjob.moc" diff --git a/libk3b/projects/movixcd/k3bmovixprogram.cpp b/libk3b/projects/movixcd/k3bmovixprogram.cpp index 890012f08..fdb6c4de5 100644 --- a/libk3b/projects/movixcd/k3bmovixprogram.cpp +++ b/libk3b/projects/movixcd/k3bmovixprogram.cpp @@ -1,341 +1,341 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3bmovixprogram.h" #include #include #include #include #include #include K3b::MovixProgram::MovixProgram() : K3b::ExternalProgram( "eMovix" ) { } bool K3b::MovixProgram::scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; if( path[path.length()-1] != '/' ) path.append("/"); // first test if we have a version info (eMovix >= 0.8.0pre3) if( !QFile::exists( path + "movix-version" ) ) return false; K3b::MovixBin* bin = 0; // // probe version and data dir // KProcess vp, dp; vp << path + "movix-version"; dp << path + "movix-conf"; vp.setOutputChannelMode( KProcess::MergedChannels ); dp.setOutputChannelMode( KProcess::MergedChannels ); vp.start(); dp.start(); if( vp.waitForFinished( -1 ) && dp.waitForFinished( -1 ) ) { QByteArray vout = vp.readAll(); QByteArray dout = dp.readAll(); // movix-version just gives us the version number on stdout if( !vout.isEmpty() && !dout.isEmpty() ) { bin = new K3b::MovixBin( this ); bin->version = vout.trimmed(); bin->path = path; bin->m_movixPath = dout.trimmed(); } } else { kDebug() << "(K3b::MovixProgram) could not start " << path << "movix-version"; return false; } if( bin->version >= K3b::Version( 0, 9, 0 ) ) return scanNewEMovix( bin, path ); else return scanOldEMovix( bin, path ); } bool K3b::MovixProgram::scanNewEMovix( K3b::MovixBin* bin, const QString& path ) { QStringList files = bin->files(); for( QStringList::iterator it = files.begin(); it != files.end(); ++it ) { if( (*it).contains( "isolinux.cfg" ) ) { bin->m_supportedBootLabels = determineSupportedBootLabels( QString(*it).split( ' ' )[1] ); break; } } // here we simply check for the movix-conf program if( QFile::exists( path + "movix-conf" ) ) { bin->addFeature( "newfiles" ); addBin(bin); return true; } else { delete bin; return false; } } bool K3b::MovixProgram::scanOldEMovix( K3b::MovixBin* bin, const QString& path ) { // // first check if all necessary directories are present // QDir dir( bin->movixDataDir() ); QStringList subdirs = dir.entryList( QDir::Dirs ); if( !subdirs.contains( "boot-messages" ) ) { kDebug() << "(K3b::MovixProgram) could not find subdir 'boot-messages'"; delete bin; return false; } if( !subdirs.contains( "isolinux" ) ) { kDebug() << "(K3b::MovixProgram) could not find subdir 'isolinux'"; delete bin; return false; } if( !subdirs.contains( "movix" ) ) { kDebug() << "(K3b::MovixProgram) could not find subdir 'movix'"; delete bin; return false; } if( !subdirs.contains( "mplayer-fonts" ) ) { kDebug() << "(K3b::MovixProgram) could not find subdir 'mplayer-fonts'"; delete bin; return false; } // // check if we have a version of eMovix which contains the movix-files script // if( QFile::exists( path + "movix-files" ) ) { bin->addFeature( "files" ); KProcess p; p << bin->path + "movix-files"; p.setOutputChannelMode( KProcess::MergedChannels ); p.start(); if( p.waitForFinished( -1 ) ) { bin->m_movixFiles = QString(p.readAll()).split( '\n' ); } } // // fallback: to be compatible with 0.8.0rc2 we just add all files in the movix directory // if( bin->m_movixFiles.isEmpty() ) { QDir dir( bin->movixDataDir() + "/movix" ); bin->m_movixFiles = dir.entryList(QDir::Files); } // // these files are fixed. That should not be a problem // since Isolinux is quite stable as far as I know. // bin->m_isolinuxFiles.append( "initrd.gz" ); bin->m_isolinuxFiles.append( "isolinux.bin" ); bin->m_isolinuxFiles.append( "isolinux.cfg" ); bin->m_isolinuxFiles.append( "kernel/vmlinuz" ); bin->m_isolinuxFiles.append( "movix.lss" ); bin->m_isolinuxFiles.append( "movix.msg" ); // // check every single necessary file :( // for( QStringList::const_iterator it = bin->m_isolinuxFiles.constBegin(); it != bin->m_isolinuxFiles.constEnd(); ++it ) { if( !QFile::exists( bin->movixDataDir() + "/isolinux/" + *it ) ) { kDebug() << "(K3b::MovixProgram) Could not find file " << *it; delete bin; return false; } } // // now check the boot-messages languages // dir.cd( "boot-messages" ); bin->m_supportedLanguages = dir.entryList( QDir::Dirs|QDir::NoDotAndDotDot ); bin->m_supportedLanguages.removeAll("CVS"); // the eMovix makefile stuff seems not perfect ;) bin->m_supportedLanguages.prepend( i18n("default") ); dir.cdUp(); // // now check the supported mplayer-fontsets // FIXME: every font dir needs to contain the "font.desc" file! // dir.cd( "mplayer-fonts" ); bin->m_supportedSubtitleFonts = dir.entryList( QDir::Dirs|QDir::NoDotAndDotDot ); bin->m_supportedSubtitleFonts.removeAll("CVS"); // the eMovix makefile stuff seems not perfect ;) // new ttf fonts in 0.8.0rc2 bin->m_supportedSubtitleFonts += dir.entryList( QStringList() << "*.ttf", QDir::Files ); bin->m_supportedSubtitleFonts.prepend( i18n("none") ); dir.cdUp(); // // now check the supported boot labels // dir.cd( "isolinux" ); bin->m_supportedBootLabels = determineSupportedBootLabels( dir.filePath("isolinux.cfg") ); // // This seems to be a valid eMovix installation. :) // addBin(bin); return true; } QStringList K3b::MovixProgram::determineSupportedBootLabels( const QString& isoConfigFile ) const { QStringList list( i18n("default") ); QFile f( isoConfigFile ); if( !f.open( QIODevice::ReadOnly ) ) { kDebug() << "(K3b::MovixProgram) could not open file '" << f.fileName() << "'"; } else { QTextStream fs( &f ); QString line = fs.readLine(); while( !line.isNull() ) { if( line.startsWith( "label" ) ) list.append( line.mid( 5 ).trimmed() ); line = fs.readLine(); } f.close(); } return list; } QString K3b::MovixBin::subtitleFontDir( const QString& font ) const { if( font == i18n("none" ) ) return ""; else if( m_supportedSubtitleFonts.contains( font ) ) return path + "/mplayer-fonts/" + font; else return ""; } QString K3b::MovixBin::languageDir( const QString& lang ) const { if( lang == i18n("default") ) return languageDir( "en" ); else if( m_supportedLanguages.contains( lang ) ) return path + "/boot-messages/" + lang; else return ""; } QStringList K3b::MovixBin::supportedSubtitleFonts() const { if( version >= K3b::Version( 0, 9, 0 ) ) return QStringList( i18n("default") ) += supported( "font" ); else return m_supportedSubtitleFonts; } QStringList K3b::MovixBin::supportedLanguages() const { if( version >= K3b::Version( 0, 9, 0 ) ) return QStringList( i18n("default") ) += supported( "lang" ); else return m_supportedLanguages; } // only used for eMovix >= 0.9.0 QStringList K3b::MovixBin::supportedKbdLayouts() const { return QStringList( i18n("default") ) += supported( "kbd" ); } // only used for eMovix >= 0.9.0 QStringList K3b::MovixBin::supportedBackgrounds() const { return QStringList( i18n("default") ) += supported( "background" ); } // only used for eMovix >= 0.9.0 QStringList K3b::MovixBin::supportedCodecs() const { return supported( "codecs" ); } QStringList K3b::MovixBin::supported( const QString& type ) const { KProcess p; p << path + "movix-conf" << "--supported=" + type; p.setOutputChannelMode( KProcess::MergedChannels ); p.start(); if( p.waitForFinished( -1 ) ) return QString(p.readAll()).split( '\n', QString::SkipEmptyParts ); else return QStringList(); } QStringList K3b::MovixBin::files( const QString& kbd, - const QString& font, - const QString& bg, - const QString& lang, - const QStringList& codecs ) const + const QString& font, + const QString& bg, + const QString& lang, + const QStringList& codecs ) const { KProcess p; p << path + "movix-conf" << "--files"; p.setOutputChannelMode( KProcess::MergedChannels ); if( !kbd.isEmpty() && kbd != i18n("default") ) p << "--kbd" << kbd; if( !font.isEmpty() && font != i18n("default") ) p << "--font" << font; if( !bg.isEmpty() && bg != i18n("default") ) p << "--background" << bg; if( !lang.isEmpty() && lang != i18n("default") ) p << "--lang" << lang; if( !codecs.isEmpty() ) p << "--codecs" << codecs.join( "," ); p.start(); if( p.waitForFinished( -1 ) ) return QString(p.readAll()).split( '\n' ); else return QStringList(); } diff --git a/libk3b/projects/videocd/k3bvcddoc.cpp b/libk3b/projects/videocd/k3bvcddoc.cpp index c7f68292d..6794740a2 100644 --- a/libk3b/projects/videocd/k3bvcddoc.cpp +++ b/libk3b/projects/videocd/k3bvcddoc.cpp @@ -1,911 +1,911 @@ /* * * Copyright (C) 2003-2005 Christian Kvasny * Copyright (C) 2007-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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. */ // QT-includes #include #include #include #include #include #include #include // KDE-includes #include #include #include #include #include #include #include #include #include #include // K3b-includes #include "k3bvcddoc.h" #include "k3bvcdtrack.h" #include "k3bvcdjob.h" #include #include #if 0 bool desperate_mode = false; bool preserve_header = false; bool print_progress = true; bool aspect_correction = false; byte forced_sequence_header = 0; #endif K3b::VcdDoc::VcdDoc( QObject* parent ) : K3b::Doc( parent ) { m_tracks = 0L; m_vcdOptions = new K3b::VcdOptions(); m_docType = VCD; m_vcdType = NONE; m_urlAddingTimer = new QTimer( this ); connect( m_urlAddingTimer, SIGNAL( timeout() ), this, SLOT( slotWorkUrlQueue() ) ); // FIXME: remove the newTracks() signal and replace it with the changed signal connect( this, SIGNAL( newTracks() ), this, SIGNAL( changed() ) ); connect( this, SIGNAL( trackRemoved( K3b::VcdTrack* ) ), this, SIGNAL( changed() ) ); } K3b::VcdDoc::~VcdDoc() { if ( m_tracks ) { qDeleteAll( *m_tracks ); delete m_tracks; } delete m_vcdOptions; } bool K3b::VcdDoc::newDocument() { clear(); if ( !m_tracks ) m_tracks = new QList; return K3b::Doc::newDocument(); } void K3b::VcdDoc::clear() { if ( m_tracks ) while ( m_tracks->first() ) removeTrack( m_tracks->first() ); } QString K3b::VcdDoc::name() const { return m_vcdOptions->volumeId(); } KIO::filesize_t K3b::VcdDoc::calcTotalSize() const { unsigned long long sum = 0; if ( m_tracks ) { Q_FOREACH( K3b::VcdTrack* track, *m_tracks ) { sum += track->size(); } } return sum; } KIO::filesize_t K3b::VcdDoc::size() const { // mode2 -> mode1 int(( n+2047 ) / 2048) * 2352 // mode1 -> mode2 int(( n+2351 ) / 2352) * 2048 long tracksize = long( ( calcTotalSize() + 2351 ) / 2352 ) * 2048; return tracksize + ISOsize(); } KIO::filesize_t K3b::VcdDoc::ISOsize() const { // 136000b for vcd iso reseved long long iso_size = 136000; if ( vcdOptions() ->CdiSupport() ) { iso_size += vcdOptions() ->CDIsize(); } return iso_size; } K3b::Msf K3b::VcdDoc::length() const { return K3b::Msf( size() / 2048 ); } bool K3b::VcdDoc::isImage( const KUrl& url ) { QImage p; return p.load( QFile::encodeName( url.path() ) ); } void K3b::VcdDoc::addUrls( const KUrl::List& urls ) { // make sure we add them at the end even if urls are in the queue addTracks( urls, 99 ); } void K3b::VcdDoc::addTracks( const KUrl::List& urls, uint position ) { KUrl::List::ConstIterator end( urls.end() ); for ( KUrl::List::ConstIterator it = urls.begin(); it != end; ++it ) { urlsToAdd.enqueue( new PrivateUrlToAdd( K3b::convertToLocalUrl(*it), position++ ) ); } m_urlAddingTimer->start( 0 ); } void K3b::VcdDoc::slotWorkUrlQueue() { if ( !urlsToAdd.isEmpty() ) { PrivateUrlToAdd * item = urlsToAdd.dequeue(); lastAddedPosition = item->position; // append at the end by default if ( lastAddedPosition > m_tracks->count() ) lastAddedPosition = m_tracks->count(); if ( !item->url.isLocalFile() ) { kDebug() << item->url.path() << " no local file"; return ; } if ( !QFile::exists( item->url.path() ) ) { kDebug() << "(K3b::VcdDoc) file not found: " << item->url.path(); m_notFoundFiles.append( item->url.path() ); return ; } if ( K3b::VcdTrack * newTrack = createTrack( item->url ) ) addTrack( newTrack, lastAddedPosition ); delete item; emit newTracks(); } else { m_urlAddingTimer->stop(); emit newTracks(); // reorder pbc tracks setPbcTracks(); informAboutNotFoundFiles(); } } K3b::VcdTrack* K3b::VcdDoc::createTrack( const KUrl& url ) { char filename[ 255 ]; QString error_string = ""; strcpy( filename, QFile::encodeName( url.path() ) ); K3b::MpegInfo* Mpeg = new K3b::MpegInfo( filename ); if ( Mpeg ) { int mpegVersion = Mpeg->version(); if ( mpegVersion > 0 ) { if ( vcdType() == NONE && mpegVersion < 2 ) { m_urlAddingTimer->stop(); setVcdType( vcdTypes( mpegVersion ) ); // FIXME: properly convert the mpeg version vcdOptions() ->setMpegVersion( ( K3b::VcdOptions::MPEGVersion )mpegVersion ); KMessageBox::information( kapp->activeWindow(), i18n( "K3b will create a %1 image from the given MPEG " "files, but these files must already be in %1 " "format. K3b does not yet resample MPEG files.", i18n( "VCD" ) ), i18n( "Information" ) ); m_urlAddingTimer->start( 0 ); } else if ( vcdType() == NONE ) { m_urlAddingTimer->stop(); vcdOptions() ->setMpegVersion( ( K3b::VcdOptions::MPEGVersion )mpegVersion ); bool force = KMessageBox::questionYesNo( kapp->activeWindow(), i18n( "K3b will create a %1 image from the given MPEG " "files, but these files must already be in %1 " "format. K3b does not yet resample MPEG files.", i18n( "SVCD" ) ) + "\n\n" + i18n( "Note: Forcing MPEG2 as VCD is not supported by " "some standalone DVD players." ), i18n( "Information" ), KGuiItem( i18n( "Force VCD" ) ), KGuiItem( i18n( "Do not force VCD" ) ) ) == KMessageBox::Yes; if ( force ) { setVcdType( vcdTypes( 1 ) ); vcdOptions() ->setAutoDetect( false ); } else setVcdType( vcdTypes( mpegVersion ) ); m_urlAddingTimer->start( 0 ); } if ( numOfTracks() > 0 && vcdOptions() ->mpegVersion() != mpegVersion ) { KMessageBox::error( kapp->activeWindow(), "(" + url.path() + ")\n" + i18n( "You cannot mix MPEG1 and MPEG2 video files.\nPlease start a new Project for this filetype.\nResample not implemented in K3b yet." ), i18n( "Wrong File Type for This Project" ) ); delete Mpeg; return 0; } K3b::VcdTrack* newTrack = new K3b::VcdTrack( m_tracks, url.path() ); *( newTrack->mpeg_info ) = *( Mpeg->mpeg_info ); if ( newTrack->isSegment() && !vcdOptions()->PbcEnabled() ) { KMessageBox::information( kapp->activeWindow(), i18n( "PBC (Playback control) enabled.\n" "Videoplayers can not reach Segments (Mpeg Still Pictures) without Playback control ." ) , i18n( "Information" ) ); vcdOptions()->setPbcEnabled( true ); } // set defaults; newTrack->setPlayTime( vcdOptions() ->PbcPlayTime() ); newTrack->setWaitTime( vcdOptions() ->PbcWaitTime() ); newTrack->setPbcNumKeys( vcdOptions() ->PbcNumkeysEnabled() ); delete Mpeg; // debugging output newTrack->PrintInfo(); return newTrack; } } else if ( isImage( url ) ) { // image track // woking on ... // for future use // photoalbum starts here // return here the new photoalbum track } if ( Mpeg ) { error_string = Mpeg->error_string(); delete Mpeg; } // error (unsupported files) KMessageBox::error( kapp->activeWindow(), "(" + url.path() + ")\n" + i18n( "Only MPEG1 and MPEG2 video files are supported.\n" ) + error_string , i18n( "Wrong File Format" ) ); return 0; } void K3b::VcdDoc::addTrack( const KUrl& url, uint position ) { urlsToAdd.enqueue( new PrivateUrlToAdd( url, position ) ); m_urlAddingTimer->start( 0 ); } void K3b::VcdDoc::addTrack( K3b::VcdTrack* track, uint position ) { if ( m_tracks->count() >= 98 ) { kDebug() << "(K3b::VcdDoc) VCD Green Book only allows 98 tracks."; // TODO: show some messagebox delete track; return ; } lastAddedPosition = position; emit aboutToAddVCDTracks( position, 1); m_tracks->insert( position, track ); if ( track->isSegment() ) vcdOptions() ->increaseSegments( ); else vcdOptions() ->increaseSequence( ); emit addedVCDTracks(); emit newTracks(); setModified( true ); } void K3b::VcdDoc::removeTrack( K3b::VcdTrack* track ) { if ( !track ) { return ; } // set the current item to track if ( m_tracks->lastIndexOf( track ) >= 0 ) { // take the current item int removedPos = m_tracks->lastIndexOf( track ); emit aboutToRemoveVCDTracks(removedPos, 1); track = m_tracks->takeAt( removedPos ); emit removedVCDTracks(); // remove all pbc references to us? if ( track->hasRevRef() ) track->delRefToUs(); // remove all pbc references from us? track->delRefFromUs(); // emit signal before deleting the track to avoid crashes // when the view tries to call some of the tracks' methods emit trackRemoved( track ); if ( track->isSegment() ) vcdOptions() ->decreaseSegments( ); else vcdOptions() ->decreaseSequence( ); delete track; if ( numOfTracks() == 0 ) { setVcdType( NONE ); vcdOptions() ->setAutoDetect( true ); } // reorder pbc tracks setPbcTracks(); } } void K3b::VcdDoc::moveTrack( K3b::VcdTrack* track, K3b::VcdTrack* after ) { if ( track == after ) return ; // take the current item int removedPos = m_tracks->lastIndexOf( track ); emit aboutToRemoveVCDTracks(removedPos, 1); m_tracks->removeAt(removedPos); emit removedVCDTracks(); // if after == 0 lastIndexOf returnes -1 int pos = (m_tracks->lastIndexOf( after ) + 1); emit aboutToAddVCDTracks(pos, 1); m_tracks->insert( pos, track ); emit addedVCDTracks(); // reorder pbc tracks setPbcTracks(); emit changed(); } QString K3b::VcdDoc::typeString() const { return "vcd"; } K3b::BurnJob* K3b::VcdDoc::newBurnJob( K3b::JobHandler* hdl, QObject* parent ) { return new K3b::VcdJob( this, hdl, parent ); } void K3b::VcdDoc::informAboutNotFoundFiles() { if ( !m_notFoundFiles.isEmpty() ) { KMessageBox::informationList( view(), i18n( "Could not find the following files:" ), m_notFoundFiles, i18n( "Not Found" ) ); m_notFoundFiles.clear(); } } void K3b::VcdDoc::setVcdType( int type ) { m_vcdType = type; switch ( type ) { case 0: //vcd 1.1 vcdOptions() ->setVcdClass( "vcd" ); vcdOptions() ->setVcdVersion( "1.1" ); break; case 1: //vcd 2.0 vcdOptions() ->setVcdClass( "vcd" ); vcdOptions() ->setVcdVersion( "2.0" ); break; case 2: //svcd 1.0 vcdOptions() ->setVcdClass( "svcd" ); vcdOptions() ->setVcdVersion( "1.0" ); break; case 3: //hqvcd 1.0 vcdOptions() ->setVcdClass( "hqvcd" ); vcdOptions() ->setVcdVersion( "1.0" ); break; } } void K3b::VcdDoc::setPbcTracks() { // reorder pbc tracks /* if ( !vcdOptions()->PbcEnabled() ) return; */ if ( m_tracks ) { int count = m_tracks->count(); kDebug() << QString( "K3b::VcdDoc::setPbcTracks() - we have %1 tracks in list." ).arg( count ); Q_FOREACH( K3b::VcdTrack* track, *m_tracks ) { for ( int i = 0; i < K3b::VcdTrack::_maxPbcTracks; i++ ) { // do not change userdefined tracks if ( !track->isPbcUserDefined( i ) ) { if ( track->getPbcTrack( i ) ) track->getPbcTrack( i ) ->delFromRevRefList( track ); K3b::VcdTrack* t = 0L; int index = track->index(); // we are the last track if ( index == count - 1 ) { switch ( i ) { case K3b::VcdTrack::PREVIOUS: // we are not alone :) if ( count > 1 ) { t = m_tracks->at( index - 1 ); t->addToRevRefList( track ); track->setPbcTrack( i, t ); } else { track->setPbcTrack( i ); track->setPbcNonTrack( i, K3b::VcdTrack::VIDEOEND ); } break; case K3b::VcdTrack::AFTERTIMEOUT: case K3b::VcdTrack::NEXT: track->setPbcTrack( i ); track->setPbcNonTrack( i, K3b::VcdTrack::VIDEOEND ); break; case K3b::VcdTrack::RETURN: track->setPbcTrack( i ); track->setPbcNonTrack( i, K3b::VcdTrack::VIDEOEND ); break; case K3b::VcdTrack::DEFAULT: track->setPbcTrack( i ); track->setPbcNonTrack( i, K3b::VcdTrack::DISABLED ); break; } } // we are the first track else if ( index == 0 ) { switch ( i ) { case K3b::VcdTrack::PREVIOUS: track->setPbcTrack( i ); track->setPbcNonTrack( i, K3b::VcdTrack::VIDEOEND ); break; case K3b::VcdTrack::AFTERTIMEOUT: case K3b::VcdTrack::NEXT: t = m_tracks->at( index + 1 ); t->addToRevRefList( track ); track->setPbcTrack( i, t ); break; case K3b::VcdTrack::RETURN: track->setPbcTrack( i ); track->setPbcNonTrack( i, K3b::VcdTrack::VIDEOEND ); break; case K3b::VcdTrack::DEFAULT: track->setPbcTrack( i ); track->setPbcNonTrack( i, K3b::VcdTrack::DISABLED ); break; } } // we are one of the other tracks and have PREVIOUS and NEXT Track else { switch ( i ) { case K3b::VcdTrack::PREVIOUS: t = m_tracks->at( index - 1 ); t->addToRevRefList( track ); track->setPbcTrack( i, t ); break; case K3b::VcdTrack::AFTERTIMEOUT: case K3b::VcdTrack::NEXT: t = m_tracks->at( index + 1 ); t->addToRevRefList( track ); track->setPbcTrack( i, t ); break; case K3b::VcdTrack::RETURN: track->setPbcTrack( i ); track->setPbcNonTrack( i, K3b::VcdTrack::VIDEOEND ); break; case K3b::VcdTrack::DEFAULT: track->setPbcTrack( i ); track->setPbcNonTrack( i, K3b::VcdTrack::DISABLED ); break; } } } } } } } bool K3b::VcdDoc::loadDocumentData( QDomElement* root ) { newDocument(); QDomNodeList nodes = root->childNodes(); if ( nodes.length() < 3 ) return false; if ( nodes.item( 0 ).nodeName() != "general" ) return false; if ( !readGeneralDocumentData( nodes.item( 0 ).toElement() ) ) return false; if ( nodes.item( 1 ).nodeName() != "vcd" ) return false; if ( nodes.item( 2 ).nodeName() != "contents" ) return false; // vcd Label QDomNodeList vcdNodes = nodes.item( 1 ).childNodes(); for ( int i = 0; i < vcdNodes.count(); i++ ) { QDomNode item = vcdNodes.item( i ); QString name = item.nodeName(); kDebug() << QString( "(K3b::VcdDoc::loadDocumentData) nodeName = '%1'" ).arg( name ); if ( name == "volumeId" ) vcdOptions() ->setVolumeId( item.toElement().text() ); else if ( name == "albumId" ) vcdOptions() ->setAlbumId( item.toElement().text() ); else if ( name == "volumeSetId" ) vcdOptions() ->setVolumeSetId( item.toElement().text() ); else if ( name == "preparer" ) vcdOptions() ->setPreparer( item.toElement().text() ); else if ( name == "publisher" ) vcdOptions() ->setPublisher( item.toElement().text() ); else if ( name == "vcdType" ) setVcdType( vcdTypes( item.toElement().text().toInt() ) ); else if ( name == "mpegVersion" ) vcdOptions() ->setMpegVersion( ( K3b::VcdOptions::MPEGVersion )item.toElement().text().toInt() ); else if ( name == "PreGapLeadout" ) vcdOptions() ->setPreGapLeadout( item.toElement().text().toInt() ); else if ( name == "PreGapTrack" ) vcdOptions() ->setPreGapTrack( item.toElement().text().toInt() ); else if ( name == "FrontMarginTrack" ) vcdOptions() ->setFrontMarginTrack( item.toElement().text().toInt() ); else if ( name == "RearMarginTrack" ) vcdOptions() ->setRearMarginTrack( item.toElement().text().toInt() ); else if ( name == "FrontMarginTrackSVCD" ) vcdOptions() ->setFrontMarginTrackSVCD( item.toElement().text().toInt() ); else if ( name == "RearMarginTrackSVCD" ) vcdOptions() ->setRearMarginTrackSVCD( item.toElement().text().toInt() ); else if ( name == "volumeCount" ) vcdOptions() ->setVolumeCount( item.toElement().text().toInt() ); else if ( name == "volumeNumber" ) vcdOptions() ->setVolumeNumber( item.toElement().text().toInt() ); else if ( name == "AutoDetect" ) vcdOptions() ->setAutoDetect( item.toElement().text().toInt() ); else if ( name == "CdiSupport" ) vcdOptions() ->setCdiSupport( item.toElement().text().toInt() ); else if ( name == "NonCompliantMode" ) vcdOptions() ->setNonCompliantMode( item.toElement().text().toInt() ); else if ( name == "Sector2336" ) vcdOptions() ->setSector2336( item.toElement().text().toInt() ); else if ( name == "UpdateScanOffsets" ) vcdOptions() ->setUpdateScanOffsets( item.toElement().text().toInt() ); else if ( name == "RelaxedAps" ) vcdOptions() ->setRelaxedAps( item.toElement().text().toInt() ); else if ( name == "UseGaps" ) vcdOptions() ->setUseGaps( item.toElement().text().toInt() ); else if ( name == "PbcEnabled" ) vcdOptions() ->setPbcEnabled( item.toElement().text().toInt() ); else if ( name == "SegmentFolder" ) vcdOptions() ->setSegmentFolder( item.toElement().text().toInt() ); else if ( name == "Restriction" ) vcdOptions() ->setRestriction( item.toElement().text().toInt() ); } // vcd Tracks QDomNodeList trackNodes = nodes.item( 2 ).childNodes(); for ( uint i = 0; i < trackNodes.length(); i++ ) { // check if url is available QDomElement trackElem = trackNodes.item( i ).toElement(); QString url = trackElem.attributeNode( "url" ).value(); if ( !QFile::exists( url ) ) m_notFoundFiles.append( url ); else { KUrl k; k.setPath( url ); if ( K3b::VcdTrack * track = createTrack( k ) ) { track ->setPlayTime( trackElem.attribute( "playtime", "1" ).toInt() ); track ->setWaitTime( trackElem.attribute( "waittime", "2" ).toInt() ); track ->setReactivity( trackElem.attribute( "reactivity", "0" ).toInt() ); track -> setPbcNumKeys( ( trackElem.attribute( "numkeys", "yes" ).contains( "yes" ) ) ? true : false ); track -> setPbcNumKeysUserdefined( ( trackElem.attribute( "userdefinednumkeys", "no" ).contains( "yes" ) ) ? true : false ); addTrack( track, m_tracks->count() ); } } } emit newTracks(); // do not add saved pbcTrack links when one ore more files missing. // TODO: add info message to informAboutNotFoundFiles(); if ( m_notFoundFiles.isEmpty() ) { int type; int val; bool pbctrack; for ( uint trackId = 0; trackId < trackNodes.length(); trackId++ ) { QDomElement trackElem = trackNodes.item( trackId ).toElement(); QDomNodeList trackNodes = trackElem.childNodes(); for ( uint i = 0; i < trackNodes.length(); i++ ) { QDomElement trackElem = trackNodes.item( i ).toElement(); QString name = trackElem.tagName(); if ( name.contains( "pbc" ) ) { if ( trackElem.hasAttribute ( "type" ) ) { type = trackElem.attribute ( "type" ).toInt(); if ( trackElem.hasAttribute ( "pbctrack" ) ) { pbctrack = ( trackElem.attribute ( "pbctrack" ) == "yes" ); if ( trackElem.hasAttribute ( "val" ) ) { val = trackElem.attribute ( "val" ).toInt(); K3b::VcdTrack* track = m_tracks->at( trackId ); K3b::VcdTrack* pbcTrack = m_tracks->at( val ); if ( pbctrack ) { pbcTrack->addToRevRefList( track ); track->setPbcTrack( type, pbcTrack ); track->setUserDefined( type, true ); } else { track->setPbcTrack( type ); track->setPbcNonTrack( type, val ); track->setUserDefined( type, true ); } } } } } else if ( name.contains( "numkeys" ) ) { if ( trackElem.hasAttribute ( "key" ) ) { int key = trackElem.attribute ( "key" ).toInt(); if ( trackElem.hasAttribute ( "val" ) ) { int val = trackElem.attribute ( "val" ).toInt() - 1; K3b::VcdTrack* track = m_tracks->at( trackId ); if ( val >= 0 ) { K3b::VcdTrack * numkeyTrack = m_tracks->at( val ); track->setDefinedNumKey( key, numkeyTrack ); } else { track->setDefinedNumKey( key, 0L ); } } } } } } setPbcTracks(); setModified( false ); } informAboutNotFoundFiles(); return true; } bool K3b::VcdDoc::saveDocumentData( QDomElement * docElem ) { QDomDocument doc = docElem->ownerDocument(); saveGeneralDocumentData( docElem ); // save Vcd Label QDomElement vcdMain = doc.createElement( "vcd" ); QDomElement vcdElem = doc.createElement( "volumeId" ); vcdElem.appendChild( doc.createTextNode( vcdOptions() ->volumeId() ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "albumId" ); vcdElem.appendChild( doc.createTextNode( vcdOptions() ->albumId() ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "volumeSetId" ); vcdElem.appendChild( doc.createTextNode( vcdOptions() ->volumeSetId() ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "preparer" ); vcdElem.appendChild( doc.createTextNode( vcdOptions() ->preparer() ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "publisher" ); vcdElem.appendChild( doc.createTextNode( vcdOptions() ->publisher() ) ); vcdMain.appendChild( vcdElem ); // applicationId() // systemId() vcdElem = doc.createElement( "vcdType" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdType() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "mpegVersion" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->mpegVersion() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "PreGapLeadout" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->PreGapLeadout() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "PreGapTrack" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->PreGapTrack() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "FrontMarginTrack" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->FrontMarginTrack() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "RearMarginTrack" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->RearMarginTrack() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "FrontMarginTrackSVCD" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->FrontMarginTrackSVCD() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "RearMarginTrackSVCD" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->RearMarginTrackSVCD() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "volumeCount" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->volumeCount() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "volumeNumber" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->volumeNumber() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "AutoDetect" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->AutoDetect() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "CdiSupport" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->CdiSupport() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "NonCompliantMode" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->NonCompliantMode() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "Sector2336" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->Sector2336() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "UpdateScanOffsets" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->UpdateScanOffsets() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "RelaxedAps" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->RelaxedAps() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "UseGaps" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->UseGaps() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "PbcEnabled" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->PbcEnabled() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "SegmentFolder" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->SegmentFolder() ) ) ); vcdMain.appendChild( vcdElem ); vcdElem = doc.createElement( "Restriction" ); vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->Restriction() ) ) ); vcdMain.appendChild( vcdElem ); docElem->appendChild( vcdMain ); // save the tracks // ------------------------------------------------------------- QDomElement contentsElem = doc.createElement( "contents" ); Q_FOREACH( K3b::VcdTrack* track, *m_tracks ) { QDomElement trackElem = doc.createElement( "track" ); trackElem.setAttribute( "url", KIO::decodeFileName( track->absolutePath() ) ); trackElem.setAttribute( "playtime", track->getPlayTime() ); trackElem.setAttribute( "waittime", track->getWaitTime() ); trackElem.setAttribute( "reactivity", track->Reactivity() ); trackElem.setAttribute( "numkeys", ( track->PbcNumKeys() ) ? "yes" : "no" ); trackElem.setAttribute( "userdefinednumkeys", ( track->PbcNumKeysUserdefined() ) ? "yes" : "no" ); for ( int i = 0; i < K3b::VcdTrack::_maxPbcTracks; i++ ) { if ( track->isPbcUserDefined( i ) ) { // save pbcTracks QDomElement pbcElem = doc.createElement( "pbc" ); pbcElem.setAttribute( "type", i ); if ( track->getPbcTrack( i ) ) { pbcElem.setAttribute( "pbctrack", "yes" ); pbcElem.setAttribute( "val", track->getPbcTrack( i ) ->index() ); } else { pbcElem.setAttribute( "pbctrack", "no" ); pbcElem.setAttribute( "val", track->getNonPbcTrack( i ) ); } trackElem.appendChild( pbcElem ); } } QMap numKeyMap = track->DefinedNumKey(); QMap::const_iterator trackIt; for ( trackIt = numKeyMap.constBegin(); trackIt != numKeyMap.constEnd(); ++trackIt ) { QDomElement numElem = doc.createElement( "numkeys" ); if ( *trackIt ) { numElem.setAttribute( "key", trackIt.key() ); numElem.setAttribute( "val", trackIt.value() ->index() + 1 ); } else { numElem.setAttribute( "key", trackIt.key() ); numElem.setAttribute( "val", 0 ); } trackElem.appendChild( numElem ); } contentsElem.appendChild( trackElem ); } // ------------------------------------------------------------- docElem->appendChild( contentsElem ); return true; } -int K3b::VcdDoc::supportedMediaTypes() const +K3b::Device::MediaTypes K3b::VcdDoc::supportedMediaTypes() const { return K3b::Device::MEDIA_WRITABLE_CD; } #include "k3bvcddoc.moc" diff --git a/libk3b/projects/videocd/k3bvcddoc.h b/libk3b/projects/videocd/k3bvcddoc.h index 839ee4581..e7afc9710 100644 --- a/libk3b/projects/videocd/k3bvcddoc.h +++ b/libk3b/projects/videocd/k3bvcddoc.h @@ -1,196 +1,196 @@ /* * * Copyright (C) 2003-2004 Christian Kvasny * Copyright (C) 2007 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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. */ #ifndef K3BVCDDOC_H #define K3BVCDDOC_H // Qt Includes #include #include #include #include #include #include // Kde Includes #include // K3b Includes #include "k3bvcdoptions.h" #include "mpeginfo/k3bmpeginfo.h" #include "k3bdoc.h" #include "k3b_export.h" class QTimer; class QDomElement; namespace K3b { class VcdTrack; class LIBK3B_EXPORT VcdDoc : public Doc { Q_OBJECT public: VcdDoc( QObject* ); ~VcdDoc(); int type() const { return VCD; } - int supportedMediaTypes() const; + Device::MediaTypes supportedMediaTypes() const; QString name() const; enum vcdTypes { VCD11, VCD20, SVCD10, HQVCD, NONE}; bool newDocument(); void clear(); int numOfTracks() const { return m_tracks->count(); } const QString& vcdImage() const { return m_vcdImage; } void setVcdImage( const QString& s ) { m_vcdImage = s; } /* VcdTrack* first() */ /* { */ /* return m_tracks->first(); */ /* } */ /* VcdTrack* current() const */ /* { */ /* return m_tracks->current(); */ /* } */ /* VcdTrack* next() */ /* { */ /* return m_tracks->next(); */ /* } */ /* VcdTrack* prev() */ /* { */ /* return m_tracks->prev(); */ /* } */ VcdTrack* at( uint i ) { return m_tracks->at( i ); } /* VcdTrack* take( uint i ) */ /* { */ /* return m_tracks->take( i ); */ /* } */ const QList* tracks() const { return m_tracks; } /** get the current size of the project */ KIO::filesize_t size() const; Msf length() const; BurnJob* newBurnJob( JobHandler* hdl, QObject* parent ); VcdOptions* vcdOptions() const { return m_vcdOptions; } int vcdType() const { return m_vcdType; } void setVcdType( int type ); void setPbcTracks(); public Q_SLOTS: /** * will test the file and add it to the project. * connect to at least result() to know when * the process is finished and check error() * to know about the result. **/ void addUrls( const KUrl::List& ); void addTrack( const KUrl&, uint ); void addTracks( const KUrl::List&, uint ); /** adds a track without any testing */ void addTrack( K3b::VcdTrack* track, uint position = 0 ); // --- TODO: this should read: removeTrack( VcdTrack* ) void removeTrack( K3b::VcdTrack* ); void moveTrack( K3b::VcdTrack* track, K3b::VcdTrack* after ); protected Q_SLOTS: /** processes queue "urlsToAdd" **/ void slotWorkUrlQueue(); Q_SIGNALS: void aboutToAddVCDTracks( int pos, int count ); void addedVCDTracks(); void aboutToRemoveVCDTracks( int pos, int count ); void removedVCDTracks(); void newTracks(); void trackRemoved( K3b::VcdTrack* ); protected: /** reimplemented from Doc */ bool loadDocumentData( QDomElement* root ); /** reimplemented from Doc */ bool saveDocumentData( QDomElement* ); QString typeString() const; private: VcdTrack* createTrack( const KUrl& url ); void informAboutNotFoundFiles(); QStringList m_notFoundFiles; QString m_vcdImage; class PrivateUrlToAdd { public: PrivateUrlToAdd( const KUrl& u, int _pos ) : url( u ), position( _pos ) {} KUrl url; int position; }; /** Holds all the urls that have to be added to the list of tracks. **/ QQueue urlsToAdd; QTimer* m_urlAddingTimer; QList* m_tracks; KIO::filesize_t calcTotalSize() const; KIO::filesize_t ISOsize() const; bool isImage( const KUrl& url ); VcdTrack* m_lastAddedTrack; VcdOptions* m_vcdOptions; int m_vcdType; int lastAddedPosition; }; } #endif diff --git a/libk3b/projects/videocd/k3bvcdjob.cpp b/libk3b/projects/videocd/k3bvcdjob.cpp index af4ed5cc8..81aba0a65 100644 --- a/libk3b/projects/videocd/k3bvcdjob.cpp +++ b/libk3b/projects/videocd/k3bvcdjob.cpp @@ -1,570 +1,562 @@ /* * * Copyright (C) 2003-2004 Christian Kvasny - * Copyright (C) 2007-2008 Sebastian Trueg + * Copyright (C) 2007-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "k3bvcdjob.h" // K3b Includes #include "k3bvcddoc.h" #include "k3bvcdtrack.h" #include "k3bvcdxmlview.h" #include #include #include #include #include #include #include #include #include #include K3b::VcdJob::VcdJob( K3b::VcdDoc* doc, K3b::JobHandler* jh, QObject* parent ) : K3b::BurnJob( jh, parent ) { m_doc = doc; m_doc->setCopies( m_doc->dummy() || m_doc->onlyCreateImages() ? 1 : m_doc->copies() ); m_process = 0; m_currentWrittenTrackNumber = 0; m_bytesFinishedTracks = 0; m_writerJob = 0; // m_createimageonlypercent = 33.3; m_createimageonlypercent = 100 / ( m_doc->copies() + 2 ); m_currentcopy = 1; m_imageFinished = false; } K3b::VcdJob::~VcdJob() { delete m_process; delete m_writerJob; } K3b::Doc* K3b::VcdJob::doc() const { return m_doc; } K3b::Device::Device* K3b::VcdJob::writer() const { if( doc()->onlyCreateImages() ) return 0; else return doc() ->burner(); } void K3b::VcdJob::cancel() { cancelAll(); emit canceled(); jobFinished( false ); } void K3b::VcdJob::cancelAll() { m_canceled = true; if ( m_writerJob ) m_writerJob->cancel(); if ( m_process->isRunning() ) { m_process->disconnect( this ); - m_process->kill(); + m_process->terminate(); } // remove bin-file if it is unfinished or the user selected to remove image if ( QFile::exists( m_doc->vcdImage() ) ) { if ( (!m_doc->onTheFly() && m_doc->removeImages()) || !m_imageFinished ) { emit infoMessage( i18n( "Removing Binary file %1" , m_doc->vcdImage() ), K3b::Job::SUCCESS ); QFile::remove ( m_doc->vcdImage() ); m_doc->setVcdImage( "" ); } } // remove cue-file if it is unfinished or the user selected to remove image if ( QFile::exists( m_cueFile ) ) { if ( (!m_doc->onTheFly() && m_doc->removeImages()) || !m_imageFinished ) { emit infoMessage( i18n( "Removing Cue file %1" , m_cueFile ), K3b::Job::SUCCESS ); QFile::remove ( m_cueFile ); m_cueFile = ""; } } } void K3b::VcdJob::start() { kDebug() << "(K3b::VcdJob) starting job"; jobStarted(); emit burning( false ); m_canceled = false; int pos = QString( m_doc->vcdImage() ).indexOf( ".bin", QString( m_doc->vcdImage() ).length() - 4 ); if ( pos > 0 ) { m_cueFile = m_doc->vcdImage().left( pos ) + ".cue"; } else { m_cueFile = m_doc->vcdImage() + ".cue"; m_doc->setVcdImage( m_doc->vcdImage() + ".bin" ); } if ( vcdDoc() ->onlyCreateImages() ) m_createimageonlypercent = 50.0; // vcdxGen(); xmlGen(); } void K3b::VcdJob::xmlGen() { KTemporaryFile tempF; tempF.open(); m_xmlFile = tempF.fileName(); tempF.remove(); K3b::VcdXmlView xmlView( m_doc ); if ( !xmlView.write( m_xmlFile ) ) { kDebug() << "(K3b::VcdJob) could not write xmlfile."; emit infoMessage( i18n( "Could not write correct XML-file." ), K3b::Job::ERROR ); cancelAll(); jobFinished( false ); } // emit infoMessage( i18n( "XML-file successfully created" ), K3b::Job::SUCCESS ); emit debuggingOutput( "K3b::VcdXml:", xmlView.xmlString() ); vcdxBuild(); } void K3b::VcdJob::vcdxBuild() { emit newTask( i18n( "Creating image files" ) ); m_stage = stageUnknown; firstTrack = true; delete m_process; m_process = new K3b::Process(); + m_process->setSplitStdout( true ); emit infoMessage( i18n( "Creating Cue/Bin files ..." ), K3b::Job::INFO ); const K3b::ExternalBin* bin = k3bcore ->externalBinManager() ->binObject( "vcdxbuild" ); if ( !bin ) { kDebug() << "(K3b::VcdJob) could not find vcdxbuild executable"; emit infoMessage( i18n( "Could not find %1 executable." , QString("vcdxbuild") ), K3b::Job::ERROR ); emit infoMessage( i18n( "To create VideoCDs you must install VcdImager Version %1." ,QString( ">= 0.7.12") ), K3b::Job::INFO ); emit infoMessage( i18n( "You can find this on your distribution disks or download it from http://www.vcdimager.org" ), K3b::Job::INFO ); cancelAll(); jobFinished( false ); return ; } if ( bin->version < K3b::Version( "0.7.12" ) ) { kDebug() << "(K3b::VcdJob) vcdxbuild executable too old!"; emit infoMessage( i18n( "%1 executable too old: need version %2 or greater." ,QString( "Vcdxbuild" ),QString( "0.7.12" )), K3b::Job::ERROR ); emit infoMessage( i18n( "You can find this on your distribution disks or download it from http://www.vcdimager.org" ), K3b::Job::INFO ); cancelAll(); jobFinished( false ); return ; } if ( !bin->copyright.isEmpty() ) emit infoMessage( i18n( "Using %1 %2 - Copyright (C) %3" , bin->name() , bin->version ,bin->copyright ), INFO ); *m_process << bin; // additional user parameters from config const QStringList& params = k3bcore->externalBinManager() ->program( "vcdxbuild" ) ->userParameters(); for ( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) *m_process << *it; if ( vcdDoc() ->vcdOptions() ->Sector2336() ) { kDebug() << "(K3b::VcdJob) Write 2336 Sectors = on"; *m_process << "--sector-2336"; } *m_process << "--progress" << "--gui"; *m_process << QString( "--cue-file=%1" ).arg( m_cueFile ); *m_process << QString( "--bin-file=%1" ).arg( m_doc->vcdImage() ); *m_process << m_xmlFile; - connect( m_process, SIGNAL( receivedStderr( K3Process*, char*, int ) ), - this, SLOT( slotParseVcdxBuildOutput( K3Process*, char*, int ) ) ); - connect( m_process, SIGNAL( receivedStdout( K3Process*, char*, int ) ), - this, SLOT( slotParseVcdxBuildOutput( K3Process*, char*, int ) ) ); + connect( m_process, SIGNAL(stdoutLine(QString)), + this, SLOT(slotParseVcdxBuildOutput(QString)) ); connect( m_process, SIGNAL( finished( int, QProcess::ExitStatus ) ), this, SLOT( slotVcdxBuildFinished( int, QProcess::ExitStatus ) ) ); // vcdxbuild commandline parameters kDebug() << "***** vcdxbuild parameters:"; QString s = m_process->joinedArgs(); kDebug() << s << flush; emit debuggingOutput( "vcdxbuild command:", s ); - if ( !m_process->start( K3Process::AllOutput ) ) { + if ( !m_process->start( KProcess::MergedChannels ) ) { kDebug() << "(K3b::VcdJob) could not start vcdxbuild"; emit infoMessage( i18n( "Could not start %1." , QString("vcdxbuild") ), K3b::Job::ERROR ); cancelAll(); jobFinished( false ); } } -void K3b::VcdJob::slotParseVcdxBuildOutput( K3Process*, char* output, int len ) +void K3b::VcdJob::slotParseVcdxBuildOutput( const QString& line ) { - QString buffer = QString::fromLocal8Bit( output, len ); - - // split to lines - QStringList lines = buffer.split( '\n' ); - QDomDocument xml_doc; QDomElement xml_root; - // do every line - for ( QStringList::Iterator str = lines.begin(); str != lines.end(); ++str ) { - *str = ( *str ).trimmed(); + QString str = line.trimmed(); - emit debuggingOutput( "vcdxbuild", *str ); + emit debuggingOutput( "vcdxbuild", str ); - xml_doc.setContent( QString( "" ) + *str + "" ); + xml_doc.setContent( QString( "" ) + str + "" ); - xml_root = xml_doc.documentElement(); + xml_root = xml_doc.documentElement(); - // There should be only one... but ... - for ( QDomNode node = xml_root.firstChild(); !node.isNull(); node = node.nextSibling() ) { - QDomElement el = node.toElement(); - if ( el.isNull() ) - continue; + // There should be only one... but ... + for ( QDomNode node = xml_root.firstChild(); !node.isNull(); node = node.nextSibling() ) { + QDomElement el = node.toElement(); + if ( el.isNull() ) + continue; - const QString tagName = el.tagName().toLower(); + const QString tagName = el.tagName().toLower(); - if ( tagName == "progress" ) { - const QString oper = el.attribute( "operation" ).toLower(); - const unsigned long long pos = el.attribute( "position" ).toLong(); - const long long size = el.attribute( "size" ).toLong(); + if ( tagName == "progress" ) { + const QString oper = el.attribute( "operation" ).toLower(); + const unsigned long long pos = el.attribute( "position" ).toLong(); + const long long size = el.attribute( "size" ).toLong(); - if ( oper == "scan" ) { - // Scan Video Files - if ( m_stage == stageUnknown || pos < m_bytesFinished ) { - const uint index = el.attribute( "id" ).replace( QRegExp( "sequence-" ), "" ).toUInt(); + if ( oper == "scan" ) { + // Scan Video Files + if ( m_stage == stageUnknown || pos < m_bytesFinished ) { + const uint index = el.attribute( "id" ).replace( QRegExp( "sequence-" ), "" ).toUInt(); - m_currentWrittenTrack = m_doc->at( m_currentWrittenTrackNumber ); - emit newSubTask( i18n( "Scanning video file %1 of %2 (%3)" , index + 1 , doc() ->numOfTracks() , m_currentWrittenTrack->fileName() ) ); - m_bytesFinished = 0; + m_currentWrittenTrack = m_doc->at( m_currentWrittenTrackNumber ); + emit newSubTask( i18n( "Scanning video file %1 of %2 (%3)" , index + 1 , doc() ->numOfTracks() , m_currentWrittenTrack->fileName() ) ); + m_bytesFinished = 0; - if ( !firstTrack ) { - m_bytesFinishedTracks += m_doc->at( m_currentWrittenTrackNumber ) ->size(); - m_currentWrittenTrackNumber++; - } else - firstTrack = false; - } - emit subPercent( ( int ) ( 100.0 * ( double ) pos / ( double ) size ) ); - emit processedSubSize( pos / 1024 / 1024, size / 1024 / 1024 ); + if ( !firstTrack ) { + m_bytesFinishedTracks += m_doc->at( m_currentWrittenTrackNumber ) ->size(); + m_currentWrittenTrackNumber++; + } else + firstTrack = false; + } + emit subPercent( ( int ) ( 100.0 * ( double ) pos / ( double ) size ) ); + emit processedSubSize( pos / 1024 / 1024, size / 1024 / 1024 ); - // this is the first of three processes. - double relOverallWritten = ( ( double ) m_bytesFinishedTracks + ( double ) pos ) / ( double ) doc() ->size(); - emit percent( ( int ) ( m_createimageonlypercent * relOverallWritten ) ); + // this is the first of three processes. + double relOverallWritten = ( ( double ) m_bytesFinishedTracks + ( double ) pos ) / ( double ) doc() ->size(); + emit percent( ( int ) ( m_createimageonlypercent * relOverallWritten ) ); - m_bytesFinished = pos; - m_stage = stageScan; + m_bytesFinished = pos; + m_stage = stageScan; - } else if ( oper == "write" ) { - emit subPercent( ( int ) ( 100.0 * ( double ) pos / ( double ) size ) ); - emit processedSubSize( ( pos * 2048 ) / 1024 / 1024, ( size * 2048 ) / 1024 / 1024 ); - emit percent( ( int ) ( m_createimageonlypercent + ( m_createimageonlypercent * ( double ) pos / ( double ) size ) ) ); + } else if ( oper == "write" ) { + emit subPercent( ( int ) ( 100.0 * ( double ) pos / ( double ) size ) ); + emit processedSubSize( ( pos * 2048 ) / 1024 / 1024, ( size * 2048 ) / 1024 / 1024 ); + emit percent( ( int ) ( m_createimageonlypercent + ( m_createimageonlypercent * ( double ) pos / ( double ) size ) ) ); - m_stage = stageWrite; - } else { - return ; - } - } else if ( tagName == "log" ) { - QDomText tel = el.firstChild().toText(); - const QString level = el.attribute( "level" ).toLower(); - if ( tel.isText() ) { - const QString text = tel.data(); - if ( m_stage == stageWrite && level == "information" ) - kDebug() << QString( "(K3b::VcdJob) VcdxBuild information, %1" ).arg( text ); - if ( ( text ).startsWith( "writing track" ) ) - emit newSubTask( i18n( "Creating Image for track %1" , ( text ).mid( 14 ) ) ); - else { - if ( level != "error" ) { - kDebug() << QString( "(K3b::VcdJob) vcdxbuild warning, %1" ).arg( text ); - parseInformation( text ); - } else { - kDebug() << QString( "(K3b::VcdJob) vcdxbuild error, %1" ).arg( text ); - emit infoMessage( text, K3b::Job::ERROR ); - } + m_stage = stageWrite; + } else { + return ; + } + } else if ( tagName == "log" ) { + QDomText tel = el.firstChild().toText(); + const QString level = el.attribute( "level" ).toLower(); + if ( tel.isText() ) { + const QString text = tel.data(); + if ( m_stage == stageWrite && level == "information" ) + kDebug() << QString( "(K3b::VcdJob) VcdxBuild information, %1" ).arg( text ); + if ( ( text ).startsWith( "writing track" ) ) + emit newSubTask( i18n( "Creating Image for track %1" , ( text ).mid( 14 ) ) ); + else { + if ( level != "error" ) { + kDebug() << QString( "(K3b::VcdJob) vcdxbuild warning, %1" ).arg( text ); + parseInformation( text ); + } else { + kDebug() << QString( "(K3b::VcdJob) vcdxbuild error, %1" ).arg( text ); + emit infoMessage( text, K3b::Job::ERROR ); } } } } } } void K3b::VcdJob::slotVcdxBuildFinished( int exitCode, QProcess::ExitStatus exitStatus ) { if ( exitStatus == QProcess::NormalExit ) { // TODO: check the process' exitStatus() switch ( exitCode ) { case 0: emit infoMessage( i18n( "Cue/Bin files successfully created." ), K3b::Job::SUCCESS ); m_imageFinished = true; break; default: emit infoMessage( i18n( "%1 returned an unknown error (code %2)." , QString("vcdxbuild") , exitCode ), K3b::Job::ERROR ); emit infoMessage( i18n( "Please send me an email with the last output." ), K3b::Job::ERROR ); cancelAll(); jobFinished( false ); return ; } } else { emit infoMessage( i18n( "%1 did not exit cleanly." , QString("Vcdxbuild") ), K3b::Job::ERROR ); cancelAll(); jobFinished( false ); return ; } //remove xml-file if ( QFile::exists( m_xmlFile ) ) QFile::remove ( m_xmlFile ); kDebug() << QString( "(K3b::VcdJob) create only image: %1" ).arg( vcdDoc() ->onlyCreateImages() ); if ( !vcdDoc() ->onlyCreateImages() ) startWriterjob(); else jobFinished( true ); } + void K3b::VcdJob::startWriterjob() { kDebug() << QString( "(K3b::VcdJob) writing copy %1 of %2" ).arg( m_currentcopy ).arg( m_doc->copies() ); if ( prepareWriterJob() ) { if ( waitForMedia( m_doc->burner() ) < 0 ) { cancel(); return ; } // just to be sure we did not get canceled during the async discWaiting if ( m_canceled ) return ; if ( m_doc->copies() > 1 ) emit newTask( i18n( "Writing Copy %1 of %2" , m_currentcopy , m_doc->copies() ) ); emit burning( true ); m_writerJob->start(); } } bool K3b::VcdJob::prepareWriterJob() { if ( m_writerJob ) delete m_writerJob; const K3b::ExternalBin* cdrecordBin = k3bcore->externalBinManager() ->binObject( "cdrecord" ); if ( writingApp() == K3b::WRITING_APP_DEFAULT && cdrecordBin->hasFeature( "cuefile" ) && m_doc->burner() ->dao() ) setWritingApp( K3b::WRITING_APP_CDRECORD ); if ( writingApp() == K3b::WRITING_APP_CDRDAO || writingApp() == K3b::WRITING_APP_DEFAULT ) { K3b::CdrdaoWriter * writer = new K3b::CdrdaoWriter( m_doc->burner(), this, this ); // create cdrdao job writer->setCommand( K3b::CdrdaoWriter::WRITE ); writer->setSimulate( m_doc->dummy() ); writer->setBurnSpeed( m_doc->speed() ); writer->setTocFile( m_cueFile ); m_writerJob = writer; } else if ( writingApp() == K3b::WRITING_APP_CDRECORD ) { K3b::CdrecordWriter * writer = new K3b::CdrecordWriter( m_doc->burner(), this, this ); // create cdrecord job writer->setSimulate( m_doc->dummy() ); writer->setBurnSpeed( m_doc->speed() ); writer->setDao( true ); writer->setCueFile( m_cueFile ); m_writerJob = writer; } connect( m_writerJob, SIGNAL( infoMessage( const QString&, int ) ), this, SIGNAL( infoMessage( const QString&, int ) ) ); connect( m_writerJob, SIGNAL( percent( int ) ), this, SLOT( slotWriterJobPercent( int ) ) ); connect( m_writerJob, SIGNAL( processedSize( int, int ) ), this, SLOT( slotProcessedSize( int, int ) ) ); connect( m_writerJob, SIGNAL( subPercent( int ) ), this, SIGNAL( subPercent( int ) ) ); connect( m_writerJob, SIGNAL( processedSubSize( int, int ) ), this, SIGNAL( processedSubSize( int, int ) ) ); connect( m_writerJob, SIGNAL( nextTrack( int, int ) ), this, SLOT( slotWriterNextTrack( int, int ) ) ); connect( m_writerJob, SIGNAL( buffer( int ) ), this, SIGNAL( bufferStatus( int ) ) ); connect( m_writerJob, SIGNAL( deviceBuffer( int ) ), this, SIGNAL( deviceBuffer( int ) ) ); connect( m_writerJob, SIGNAL( writeSpeed( int, K3b::Device::SpeedMultiplicator ) ), this, SIGNAL( writeSpeed( int, K3b::Device::SpeedMultiplicator ) ) ); connect( m_writerJob, SIGNAL( finished( bool ) ), this, SLOT( slotWriterJobFinished( bool ) ) ); connect( m_writerJob, SIGNAL( newTask( const QString& ) ), this, SIGNAL( newTask( const QString& ) ) ); connect( m_writerJob, SIGNAL( newSubTask( const QString& ) ), this, SIGNAL( newSubTask( const QString& ) ) ); connect( m_writerJob, SIGNAL( debuggingOutput( const QString&, const QString& ) ), this, SIGNAL( debuggingOutput( const QString&, const QString& ) ) ); return true; } void K3b::VcdJob::slotWriterJobPercent( int p ) { emit percent( ( int ) ( ( m_createimageonlypercent * ( m_currentcopy + 1 ) ) + p / ( m_doc->copies() + 2 ) ) ); } void K3b::VcdJob::slotProcessedSize( int cs, int ts ) { emit processedSize( cs + ( ts * ( m_currentcopy - 1 ) ) , ts * m_doc->copies() ); } void K3b::VcdJob::slotWriterNextTrack( int t, int tt ) { emit newSubTask( i18n( "Writing Track %1 of %2" , t , tt ) ); } void K3b::VcdJob::slotWriterJobFinished( bool success ) { if ( m_canceled ) return ; if ( m_currentcopy >= m_doc->copies() ) { // remove bin-file if it is unfinished or the user selected to remove image if ( QFile::exists( m_doc->vcdImage() ) ) { if ( (!m_doc->onTheFly() && m_doc->removeImages()) || !m_imageFinished ) { emit infoMessage( i18n( "Removing Binary file %1" , m_doc->vcdImage() ), K3b::Job::SUCCESS ); QFile::remove ( m_doc->vcdImage() ); m_doc->setVcdImage( "" ); } } // remove cue-file if it is unfinished or the user selected to remove image if ( QFile::exists( m_cueFile ) ) { if ( (!m_doc->onTheFly() && m_doc->removeImages()) || !m_imageFinished ) { emit infoMessage( i18n( "Removing Cue file %1" , m_cueFile ), K3b::Job::SUCCESS ); QFile::remove ( m_cueFile ); m_cueFile = ""; } } } if ( success ) { // allright // the writerJob should have emitted the "simulation/writing successful" signal if ( m_currentcopy >= m_doc->copies() ) { if ( k3bcore->globalSettings()->ejectMedia() ) { K3b::Device::eject( m_doc->burner() ); } jobFinished( true ); } else { if( !m_doc->burner()->eject() ) { blockingInformation( i18n("K3b was unable to eject the written disk. Please do so manually.") ); } m_currentcopy++; startWriterjob(); } } else { cancelAll(); jobFinished( false ); } } void K3b::VcdJob::parseInformation( const QString &text ) { // parse warning if ( text.contains( "mpeg user scan data: one or more BCD fields out of range for" ) ) { int index = text.indexOf( " for" ); emit infoMessage( i18n( "One or more BCD fields out of range for %1" , text.mid( index + 4 ).trimmed() ), K3b::Job::WARNING ); } else if ( text.contains( "mpeg user scan data: from now on, scan information data errors will not be reported anymore" ) ) { emit infoMessage( i18n( "From now on, scan information data errors will not be reported anymore" ), K3b::Job::INFO ); emit infoMessage( i18n( "Consider enabling the 'update scan offsets' option, if it is not enabled already." ), K3b::Job::INFO ); } else if ( text.contains( "APS' pts seems out of order (actual pts" ) ) { int index = text.indexOf( "(actual pts" ); int index2 = text.indexOf( ", last seen pts" ); int index3 = text.indexOf( ") -- ignoring this aps" ); emit infoMessage( i18n( "APS' pts seems out of order (actual pts %1, last seen pts %2)" , text.mid( index + 12, index2 - index - 12 ).trimmed() , text.mid( index2 + 14, index3 - index2 - 14 ).trimmed() ), K3b::Job::WARNING ); emit infoMessage( i18n( "Ignoring this aps" ), K3b::Job::INFO ); } else if ( text.contains( "bad packet at packet" ) ) { int index = text.indexOf( "at packet #" ); int index2 = text.indexOf( "(stream byte offset" ); int index3 = text.indexOf( ") -- remaining " ); int index4 = text.indexOf( "bytes of stream will be ignored" ); emit infoMessage( i18n( "Bad packet at packet #%1 (stream byte offset %2)" , text.mid( index + 11, index2 - index - 11 ).trimmed() , text.mid( index2 + 19, index3 - index2 - 19 ).trimmed() ), K3b::Job::WARNING ); emit infoMessage( i18n( "Remaining %1 bytes of stream will be ignored." , text.mid( index3 + 15, index4 - index3 - 15 ).trimmed() ), K3b::Job::WARNING ); } } QString K3b::VcdJob::jobDescription() const { switch ( m_doc->vcdType() ) { case K3b::VcdDoc::VCD11: return i18n( "Writing Video CD (Version 1.1)" ); case K3b::VcdDoc::VCD20: return i18n( "Writing Video CD (Version 2.0)" ); case K3b::VcdDoc::SVCD10: return i18n( "Writing Super Video CD" ); case K3b::VcdDoc::HQVCD: return i18n( "Writing High-Quality Video CD" ); default: return i18n( "Writing Video CD" ); } } QString K3b::VcdJob::jobDetails() const { return ( i18np( "1 MPEG (%2)", "%1 MPEGs (%2)", m_doc->tracks() ->count(), KIO::convertSize( m_doc->size() ) ) + ( m_doc->copies() > 1 ? i18np( " - %1 copy", " - %1 copies", m_doc->copies() ) : QString() ) ); } #include "k3bvcdjob.moc" diff --git a/libk3b/projects/videocd/k3bvcdjob.h b/libk3b/projects/videocd/k3bvcdjob.h index 7ae6f7fd5..fa05749f4 100644 --- a/libk3b/projects/videocd/k3bvcdjob.h +++ b/libk3b/projects/videocd/k3bvcdjob.h @@ -1,115 +1,116 @@ /* * * Copyright (C) 2003-2004 Christian Kvasny +* Copyright (C) 2009 Sebastian Trueg * * This file is part of the K3b project. -* Copyright (C) 1998-2007 Sebastian Trueg +* Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3BVCDJOB_H #define K3BVCDJOB_H #include #include "k3bjob.h" class K3Process; namespace K3b { class VcdDoc; class VcdTrack; class Process; class AbstractWriter; class Doc; class VcdJob : public BurnJob { Q_OBJECT public: VcdJob( VcdDoc*, JobHandler*, QObject* parent = 0 ); ~VcdJob(); Doc* doc() const; VcdDoc* vcdDoc() const { return m_doc; } Device::Device* writer() const; QString jobDescription() const; QString jobDetails() const; public Q_SLOTS: void start(); void cancel(); private Q_SLOTS: void cancelAll(); protected Q_SLOTS: void slotVcdxBuildFinished( int, QProcess::ExitStatus ); - void slotParseVcdxBuildOutput( K3Process*, char* output, int len ); + void slotParseVcdxBuildOutput( const QString& ); void slotWriterJobPercent( int p ); void slotProcessedSize( int cs, int ts ); void slotWriterNextTrack( int t, int tt ); void slotWriterJobFinished( bool success ); private: bool prepareWriterJob(); void xmlGen(); void vcdxBuild(); void parseInformation( const QString& ); void startWriterjob(); int m_copies; int m_finishedCopies; unsigned long m_blocksToCopy; unsigned long m_bytesFinishedTracks; unsigned long m_bytesFinished; enum { stageUnknown, stageScan, stageWrite, _stage_max }; VcdDoc* m_doc; Device::Device* m_writer; Device::Device* m_reader; VcdTrack* m_currentWrittenTrack; int m_speed; int m_stage; int m_currentcopy; int m_currentWrittenTrackNumber; double m_createimageonlypercent; bool firstTrack; bool m_burnProof; bool m_keepImage; bool m_onlyCreateImage; bool m_onTheFly; bool m_dummy; bool m_fastToc; bool m_readRaw; bool m_imageFinished; bool m_canceled; QString m_tempPath; QString m_cueFile; QString m_xmlFile; QString m_collectedOutput; AbstractWriter* m_writerJob; Process* m_process; }; } #endif diff --git a/libk3b/projects/videodvd/k3bvideodvddoc.cpp b/libk3b/projects/videodvd/k3bvideodvddoc.cpp index 6bb929ac2..f33ab7818 100644 --- a/libk3b/projects/videodvd/k3bvideodvddoc.cpp +++ b/libk3b/projects/videodvd/k3bvideodvddoc.cpp @@ -1,76 +1,76 @@ /* * * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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 "k3bvideodvddoc.h" #include "k3bvideodvdjob.h" #include #include #include K3b::VideoDvdDoc::VideoDvdDoc( QObject* parent ) : K3b::DataDoc( parent ) { } K3b::VideoDvdDoc::~VideoDvdDoc() { } bool K3b::VideoDvdDoc::newDocument() { if( K3b::DataDoc::newDocument() ) { // K3b::DataDoc::newDocument already deleted m_videoTsDir (again: bad design!) m_videoTsDir = new K3b::DirItem( "VIDEO_TS", this, root() ); m_videoTsDir->setRemoveable(false); m_videoTsDir->setRenameable(false); m_videoTsDir->setMoveable(false); m_videoTsDir->setHideable(false); K3b::DirItem* audioTsDir = new K3b::DirItem( "AUDIO_TS", this, root() ); audioTsDir->setRemoveable(false); audioTsDir->setRenameable(false); audioTsDir->setMoveable(false); audioTsDir->setHideable(false); setMultiSessionMode( NONE ); setModified( false ); return true; } else return false; } K3b::BurnJob* K3b::VideoDvdDoc::newBurnJob( K3b::JobHandler* hdl, QObject* parent ) { return new K3b::VideoDvdJob( this, hdl, parent ); } -int K3b::VideoDvdDoc::supportedMediaTypes() const +K3b::Device::MediaTypes K3b::VideoDvdDoc::supportedMediaTypes() const { return K3b::Device::MEDIA_WRITABLE_DVD; } //#include "k3bdvddoc.moc" diff --git a/libk3b/projects/videodvd/k3bvideodvddoc.h b/libk3b/projects/videodvd/k3bvideodvddoc.h index 6546b9a68..9d9a263d7 100644 --- a/libk3b/projects/videodvd/k3bvideodvddoc.h +++ b/libk3b/projects/videodvd/k3bvideodvddoc.h @@ -1,48 +1,48 @@ /* * * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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. */ #ifndef _K3B_VIDEODVD_DOC_H_ #define _K3B_VIDEODVD_DOC_H_ #include #include "k3b_export.h" namespace K3b { class LIBK3B_EXPORT VideoDvdDoc : public DataDoc { public: VideoDvdDoc( QObject* parent = 0 ); virtual ~VideoDvdDoc(); virtual int type() const { return VIDEODVD; } - int supportedMediaTypes() const; + Device::MediaTypes supportedMediaTypes() const; virtual BurnJob* newBurnJob( JobHandler* hdl, QObject* parent ); virtual bool newDocument(); DirItem* videoTsDir() const { return m_videoTsDir; } // TODO: implement load- and saveDocumentData since we do not need all those options protected: virtual QString typeString() const { return "video_dvd"; } private: DirItem* m_videoTsDir; }; } #endif diff --git a/libk3b/tools/CMakeLists.txt b/libk3b/tools/CMakeLists.txt index 26c83e395..362712e99 100644 --- a/libk3b/tools/CMakeLists.txt +++ b/libk3b/tools/CMakeLists.txt @@ -1,39 +1,38 @@ install( FILES k3bwavefilewriter.h k3bbusywidget.h k3bdeviceselectiondialog.h k3bmd5job.h k3bstringutils.h k3bdevicecombobox.h k3bstdguiitems.h k3bvalidators.h k3bthroughputestimator.h k3biso9660.h k3bmultichoicedialog.h k3bdevicehandler.h k3bcdparanoialib.h k3blistview.h k3bmsfedit.h k3bcdtextvalidator.h k3bintvalidator.h k3bexceptions.h k3bprogressdialog.h k3bpushbutton.h k3blistviewitemanimator.h k3bthreadwidget.h k3bsignalwaiter.h k3biso9660backend.h - k3bpipe.h k3bdirsizejob.h k3bchecksumpipe.h k3bintmapcombobox.h k3brichtextlabel.h k3bactivepipe.h k3bfilesplitter.h k3bfilesysteminfo.h k3bmedium.h k3bmediacache.h k3bcddb.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel) diff --git a/libk3b/tools/k3bactivepipe.cpp b/libk3b/tools/k3bactivepipe.cpp index 7f7c61679..eb177c632 100644 --- a/libk3b/tools/k3bactivepipe.cpp +++ b/libk3b/tools/k3bactivepipe.cpp @@ -1,213 +1,199 @@ /* * * Copyright (C) 2006-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3bactivepipe.h" -#include - #include #include #include -#include - class K3b::ActivePipe::Private : public QThread { public: Private( K3b::ActivePipe* pipe ) : m_pipe( pipe ), - fdToWriteTo(-1), sourceIODevice(0), sinkIODevice(0), - closeFdToWriteTo(false) { + closeSinkIODevice( false ), + closeSourceIODevice( false ) { } void run() { - kDebug() << "(K3b::ActivePipe) started thread."; + kDebug() << "(K3b::ActivePipe) writing from" << sourceIODevice << "to" << sinkIODevice; + bytesRead = bytesWritten = 0; buffer.resize( 10*2048 ); - ssize_t r = 0; - while( ( r = m_pipe->read( buffer.data(), buffer.size() ) ) > 0 ) { + bool fail = false; + qint64 r = 0; + while( !fail && ( r = m_pipe->readData( buffer.data(), buffer.size() ) ) > 0 ) { bytesRead += r; - // write it out ssize_t w = 0; ssize_t ww = 0; while( w < r ) { if( ( ww = m_pipe->write( buffer.data()+w, r-w ) ) > 0 ) { w += ww; bytesWritten += ww; } + else { + kDebug() << "write failed." << sinkIODevice->errorString(); + fail = true; + break; + } } } - // kDebug() << "(K3b::ActivePipe) thread done: " << r << " (total bytes read/written: " << bytesRead << "/" << bytesWritten << ")"; - close( closeWhenDone ); - } - int readFd() const { - return pipeIn.out(); - } + if ( r < 0 ) { + kDebug() << "Read failed:" << sourceIODevice->errorString(); + } - int writeFd() const { - if( fdToWriteTo == -1 ) - return pipeOut.in(); - else - return fdToWriteTo; + kDebug() << "thread done: " << fail << " (total bytes read/written: " << bytesRead << "/" << bytesWritten << ")"; } - void close( bool closeAll ) { - if( sourceIODevice ) - sourceIODevice->close(); - if( sinkIODevice ) - sinkIODevice->close(); - - if( closeAll ) { - pipeIn.close(); - pipeOut.close(); - if( fdToWriteTo != -1 && - closeFdToWriteTo ) - ::close( fdToWriteTo ); - } + void _k3b_close() { + kDebug(); + if ( closeWhenDone ) + m_pipe->close(); } private: K3b::ActivePipe* m_pipe; public: - int fdToWriteTo; - K3b::Pipe pipeIn; - K3b::Pipe pipeOut; - QIODevice* sourceIODevice; QIODevice* sinkIODevice; bool closeWhenDone; - bool closeFdToWriteTo; + bool closeSinkIODevice; + bool closeSourceIODevice; QByteArray buffer; quint64 bytesRead; quint64 bytesWritten; }; K3b::ActivePipe::ActivePipe() { d = new Private( this ); + connect( d, SIGNAL(finished()), this, SLOT(_k3b_close()) ); } K3b::ActivePipe::~ActivePipe() { delete d; } +bool K3b::ActivePipe::open( OpenMode mode ) +{ + return QIODevice::open( mode ); +} + + bool K3b::ActivePipe::open( bool closeWhenDone ) { if( d->isRunning() ) return false; + QIODevice::open( ReadWrite|Unbuffered ); + d->closeWhenDone = closeWhenDone; - if( d->sourceIODevice ) { + if( d->sourceIODevice && !d->sourceIODevice->isOpen() ) { + kDebug() << "Need to open source device:" << d->sourceIODevice; if( !d->sourceIODevice->open( QIODevice::ReadOnly ) ) return false; } - else if( !d->pipeIn.open() ) { - return false; - } - if( d->sinkIODevice ) { + if( d->sinkIODevice && !d->sinkIODevice->isOpen() ) { + kDebug() << "Need to open sink device:" << d->sinkIODevice; if( !d->sinkIODevice->open( QIODevice::WriteOnly ) ) return false; } - else if( d->fdToWriteTo == -1 && !d->pipeOut.open() ) { - close(); - return false; - } kDebug() << "(K3b::ActivePipe) successfully opened pipe."; - d->start(); + // we only do active piping if both devices are set. + // Otherwise we only work as a conduit + if ( d->sourceIODevice && d->sinkIODevice ) { + d->start(); + } + return true; } void K3b::ActivePipe::close() { - d->pipeIn.closeIn(); + kDebug(); + if( d->sourceIODevice && d->closeSourceIODevice ) + d->sourceIODevice->close(); + if( d->sinkIODevice && d->closeSinkIODevice ) + d->sinkIODevice->close(); d->wait(); - d->close( true ); } -void K3b::ActivePipe::writeToFd( int fd, bool close ) -{ - d->fdToWriteTo = fd; - d->sinkIODevice = 0; - d->closeFdToWriteTo = close; -} - - -void K3b::ActivePipe::readFromIODevice( QIODevice* dev ) +void K3b::ActivePipe::readFrom( QIODevice* dev, bool close ) { d->sourceIODevice = dev; + d->closeSourceIODevice = close; } -void K3b::ActivePipe::writeToIODevice( QIODevice* dev ) +void K3b::ActivePipe::writeTo( QIODevice* dev, bool close ) { - d->fdToWriteTo = -1; d->sinkIODevice = dev; + d->closeSinkIODevice = close; } -int K3b::ActivePipe::in() const -{ - return d->pipeIn.in(); -} - - -int K3b::ActivePipe::read( char* data, int max ) +qint64 K3b::ActivePipe::readData( char* data, qint64 max ) { - if( d->sourceIODevice ) + if( d->sourceIODevice ) { return d->sourceIODevice->read( data, max ); - else - return ::read( d->readFd(), data, max ); + } + + return -1; } -int K3b::ActivePipe::write( char* data, int max ) +qint64 K3b::ActivePipe::writeData( const char* data, qint64 max ) { - if( d->sinkIODevice ) + if( d->sinkIODevice ) { return d->sinkIODevice->write( data, max ); + } else - return ::write( d->writeFd(), data, max ); + return -1; } quint64 K3b::ActivePipe::bytesRead() const { return d->bytesRead; } quint64 K3b::ActivePipe::bytesWritten() const { return d->bytesWritten; } + +#include "k3bactivepipe.moc" diff --git a/libk3b/tools/k3bactivepipe.h b/libk3b/tools/k3bactivepipe.h index 29d31940e..c4223bbb6 100644 --- a/libk3b/tools/k3bactivepipe.h +++ b/libk3b/tools/k3bactivepipe.h @@ -1,111 +1,114 @@ /* * * Copyright (C) 2006-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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. */ #ifndef _K3B_ACTIVE_PIPE_H_ #define _K3B_ACTIVE_PIPE_H_ #include -class QIODevice; +#include namespace K3b { /** * The active pipe pumps data from a source to a sink using an * additional thread. + * + * The active pumping is only performed if both the source and sink + * QIODevices are set. Otherwise the pipe only serves as a conduit for + * data streams. The latter is mostly interesting when using the ChecksumPipe + * in combination with a Job that can only push data (like the DataTrackReader). */ - class LIBK3B_EXPORT ActivePipe + class LIBK3B_EXPORT ActivePipe : public QIODevice { + Q_OBJECT + public: ActivePipe(); virtual ~ActivePipe(); /** * Opens the pipe and thus starts the * pumping. * * \param closeWhenDone If true the pipes will be closed * once all data has been read. */ virtual bool open( bool closeWhenDone = false ); /** * Close the pipe */ virtual void close(); - /** - * Set the file descriptor to write to. If this is -1 (the default) then - * data has to read from the out() file descriptor. - * - * \param fd The file descriptor to write to. - * \param close If true the reading file descriptor will be closed on a call to close() - */ - void writeToFd( int fd, bool close = false ); - /** * Read from a QIODevice instead of a file descriptor. * The device will be opened QIODevice::ReadOnly and closed * afterwards. + * + * \param close If true the device will be closed once close() is called. */ - void readFromIODevice( QIODevice* dev ); + void readFrom( QIODevice* dev, bool close = false ); /** - * Write to a QIODevice instead of a file descriptor. + * Write to a QIODevice instead of using the readyRead signal. * The device will be opened QIODevice::WriteOnly and closed * afterwards. + * + * \param close If true the device will be closed once close() is called. */ - void writeToIODevice( QIODevice* dev ); - - /** - * The file descriptor to write into - * Only valid if no source has been set - */ - int in() const; + void writeTo( QIODevice* dev, bool close = false ); /** * The number of bytes that have been read. */ quint64 bytesRead() const; /** * The number of bytes that have been written. */ quint64 bytesWritten() const; protected: /** * Reads the data from the source. * The default implementation reads from the file desc * set via readFromFd or from in() */ - virtual int read( char* data, int max ); + virtual qint64 readData( char* data, qint64 max ); /** * Write the data to the sink. * The default implementation writes to the file desc * set via writeToFd or out() * * Can be reimplememented to further process the data. */ - virtual int write( char* data, int max ); + virtual qint64 writeData( const char* data, qint64 max ); + + /** + * Hidden open method. Use open(bool). + */ + bool open( OpenMode mode ); private: class Private; Private* d; + + Q_PRIVATE_SLOT( d, void _k3b_close() ) }; } #endif diff --git a/libk3b/tools/k3bchecksumpipe.cpp b/libk3b/tools/k3bchecksumpipe.cpp index d31a278f2..eb6f66656 100644 --- a/libk3b/tools/k3bchecksumpipe.cpp +++ b/libk3b/tools/k3bchecksumpipe.cpp @@ -1,98 +1,104 @@ /* * * Copyright (C) 2006-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bchecksumpipe.h" #include #include #include class K3b::ChecksumPipe::Private { public: - Private() - : checksumType(MD5) { - } + Private() + : checksumType(MD5) { + } - void update( const char* in, int len ) { - switch( checksumType ) { - case MD5: - md5.update( in, len ); - break; + void update( const char* in, qint64 len ) { + switch( checksumType ) { + case MD5: + md5.update( in, len ); + break; + } } - } - void reset() { - switch( checksumType ) { - case MD5: - md5.reset(); - break; + void reset() { + switch( checksumType ) { + case MD5: + md5.reset(); + break; + } } - } - int checksumType; + int checksumType; - KMD5 md5; + KMD5 md5; }; K3b::ChecksumPipe::ChecksumPipe() - : K3b::ActivePipe() + : K3b::ActivePipe() { - d = new Private(); + d = new Private(); } K3b::ChecksumPipe::~ChecksumPipe() { - delete d; + delete d; } bool K3b::ChecksumPipe::open( bool closeWhenDone ) { - return open( MD5, closeWhenDone ); + return open( MD5, closeWhenDone ); } bool K3b::ChecksumPipe::open( Type type, bool closeWhenDone ) { - if( K3b::ActivePipe::open( closeWhenDone ) ) { - d->reset(); - d->checksumType = type; - return true; - } - else - return false; + if( K3b::ActivePipe::open( closeWhenDone ) ) { + d->reset(); + d->checksumType = type; + return true; + } + else + return false; } QByteArray K3b::ChecksumPipe::checksum() const { - switch( d->checksumType ) { - case MD5: - return d->md5.hexDigest(); - } + switch( d->checksumType ) { + case MD5: + return d->md5.hexDigest(); + } + + return QByteArray(); +} - return QByteArray(); + +qint64 K3b::ChecksumPipe::writeData( const char* data, qint64 max ) +{ + d->update( data, max ); + return K3b::ActivePipe::writeData( data, max ); } -int K3b::ChecksumPipe::write( char* data, int max ) +bool K3b::ChecksumPipe::open( OpenMode mode ) { - d->update( data, max ); - return K3b::ActivePipe::write( data, max ); + return ActivePipe::open( mode ); } diff --git a/libk3b/tools/k3bchecksumpipe.h b/libk3b/tools/k3bchecksumpipe.h index 0e72ef537..30709cc15 100644 --- a/libk3b/tools/k3bchecksumpipe.h +++ b/libk3b/tools/k3bchecksumpipe.h @@ -1,67 +1,72 @@ /* * * Copyright (C) 2006-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_CHECKSUM_PIPE_H_ #define _K3B_CHECKSUM_PIPE_H_ #include #include namespace K3b { /** * The checksum pipe calculates the checksum of the data * passed through it. */ class LIBK3B_EXPORT ChecksumPipe : public ActivePipe { public: ChecksumPipe(); ~ChecksumPipe(); enum Type { MD5 }; /** * \reimplemented * Defaults to MD5 checksum */ bool open( bool closeWhenDone = false ); /** * Opens the pipe and thus starts the * checksum calculation * * \param closeWhenDone If true the pipes will be closed * once all data has been read. */ bool open( Type type, bool closeWhenDone = false ); /** * Get the calculated checksum */ QByteArray checksum() const; protected: - int write( char* data, int max ); + qint64 writeData( const char* data, qint64 max ); private: + /** + * Hidden open method. Use open(bool). + */ + bool open( OpenMode mode ); + class Private; Private* d; }; } #endif diff --git a/libk3b/tools/k3bdevicehandler.cpp b/libk3b/tools/k3bdevicehandler.cpp index 3323f3a49..e1904b248 100644 --- a/libk3b/tools/k3bdevicehandler.cpp +++ b/libk3b/tools/k3bdevicehandler.cpp @@ -1,312 +1,312 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3bdevicehandler.h" #include #include #include #include class K3b::Device::DeviceHandler::Private { public: Private( bool _selfDelete ) : selfDelete( _selfDelete ) { } bool selfDelete; bool success; int errorCode; - int command; + Commands command; DiskInfo ngInfo; Toc toc; CdText cdText; QByteArray cdTextRaw; long long bufferCapacity; long long availableBufferCapacity; Device* dev; K3b::Msf nextWritableAddress; }; K3b::Device::DeviceHandler::DeviceHandler( Device* dev, QObject* parent ) : K3b::ThreadJob( 0, parent ), d( new Private( false ) ) { d->dev = dev; } K3b::Device::DeviceHandler::DeviceHandler( QObject* parent ) : K3b::ThreadJob( 0, parent ), d( new Private( false ) ) { } -K3b::Device::DeviceHandler::DeviceHandler( int command, Device* dev ) +K3b::Device::DeviceHandler::DeviceHandler( Commands command, Device* dev ) : K3b::ThreadJob( 0, 0 ), d( new Private( false ) ) { d->dev = dev; sendCommand(command); } K3b::Device::DeviceHandler::~DeviceHandler() { delete d; } int K3b::Device::DeviceHandler::errorCode() const { return d->errorCode; } bool K3b::Device::DeviceHandler::success() const { return d->success; } const K3b::Device::DiskInfo& K3b::Device::DeviceHandler::diskInfo() const { return d->ngInfo; } const K3b::Device::Toc& K3b::Device::DeviceHandler::toc() const { return d->toc; } const K3b::Device::CdText& K3b::Device::DeviceHandler::cdText() const { return d->cdText; } const QByteArray& K3b::Device::DeviceHandler::cdTextRaw() const { return d->cdTextRaw; } K3b::Msf K3b::Device::DeviceHandler::diskSize() const { return d->ngInfo.capacity(); } K3b::Msf K3b::Device::DeviceHandler::remainingSize() const { return d->ngInfo.remainingSize(); } int K3b::Device::DeviceHandler::tocType() const { return d->toc.contentType(); } int K3b::Device::DeviceHandler::numSessions() const { return d->ngInfo.numSessions(); } long long K3b::Device::DeviceHandler::bufferCapacity() const { return d->bufferCapacity; } long long K3b::Device::DeviceHandler::availableBufferCapacity() const { return d->availableBufferCapacity; } K3b::Msf K3b::Device::DeviceHandler::nextWritableAddress() const { return d->nextWritableAddress; } void K3b::Device::DeviceHandler::setDevice( Device* dev ) { d->dev = dev; } -void K3b::Device::DeviceHandler::sendCommand( int command ) +void K3b::Device::DeviceHandler::sendCommand( DeviceHandler::Commands command ) { if( active() ) { kDebug() << "(K3b::Device::DeviceHandler) thread already running. canceling thread..."; cancel(); wait(); } d->command = command; start(); } void K3b::Device::DeviceHandler::getToc() { sendCommand(DeviceHandler::TOC); } void K3b::Device::DeviceHandler::getDiskInfo() { sendCommand(DeviceHandler::DISKINFO); } void K3b::Device::DeviceHandler::getDiskSize() { sendCommand(DeviceHandler::DISKSIZE); } void K3b::Device::DeviceHandler::getRemainingSize() { sendCommand(DeviceHandler::REMAININGSIZE); } void K3b::Device::DeviceHandler::getTocType() { sendCommand(DeviceHandler::TOCTYPE); } void K3b::Device::DeviceHandler::getNumSessions() { sendCommand(DeviceHandler::NUMSESSIONS); } void K3b::Device::DeviceHandler::block( bool b ) { sendCommand(b ? DeviceHandler::BLOCK : DeviceHandler::UNBLOCK); } void K3b::Device::DeviceHandler::eject() { sendCommand(DeviceHandler::EJECT); } -K3b::Device::DeviceHandler* K3b::Device::sendCommand( int command, Device* dev ) +K3b::Device::DeviceHandler* K3b::Device::sendCommand( DeviceHandler::Commands command, Device* dev ) { return new DeviceHandler( command, dev ); } void K3b::Device::DeviceHandler::jobFinished( bool success ) { K3b::ThreadJob::jobFinished( success ); emit finished( this ); if( d->selfDelete ) { deleteLater(); } } bool K3b::Device::DeviceHandler::run() { kDebug() << "(K3b::Device::DeviceHandler) starting command: " << d->command; d->success = false; // clear data d->toc.clear(); d->ngInfo = DiskInfo(); d->cdText.clear(); d->cdTextRaw.resize(0); if( d->dev ) { d->success = d->dev->open(); if( !canceled() && d->command & DISKINFO ) { d->ngInfo = d->dev->diskInfo(); if( !canceled() && !d->ngInfo.empty() ) { d->toc = d->dev->readToc(); if( d->toc.contentType() == AUDIO || d->toc.contentType() == MIXED ) d->cdText = d->dev->readCdText(); } } if( !canceled() && d->command & (NG_DISKINFO| DISKSIZE| REMAININGSIZE| NUMSESSIONS) ) { d->ngInfo = d->dev->diskInfo(); } if( !canceled() && d->command & (TOC|TOCTYPE) ) { d->toc = d->dev->readToc(); } if( !canceled() && d->command & CD_TEXT ) { d->cdText = d->dev->readCdText(); d->success = (d->success && !d->cdText.isEmpty()); } if( !canceled() && d->command & CD_TEXT_RAW ) { unsigned char* data = 0; unsigned int dataLen = 0; if( d->dev->readTocPmaAtip( &data, dataLen, 5, false, 0 ) ) { // we need more than the header and a multiple of 18 bytes to have valid CD-TEXT if( dataLen > 4 && dataLen%18 == 4 ) { d->cdTextRaw = QByteArray::fromRawData( reinterpret_cast(data), dataLen ); } else { kDebug() << "(K3b::Device::DeviceHandler) invalid CD-TEXT length: " << dataLen; delete [] data; d->success = false; } } else d->success = false; } if( !canceled() && d->command & BLOCK ) d->success = (d->success && d->dev->block( true )); if( !canceled() && d->command & UNBLOCK ) d->success = (d->success && d->dev->block( false )); // // It is important that eject is performed before load // since the RELOAD command is a combination of both // if( !canceled() && d->command & EJECT ) d->success = (d->success && d->dev->eject()); if( !canceled() && d->command & LOAD ) d->success = (d->success && d->dev->load()); if( !canceled() && d->command & BUFFER_CAPACITY ) d->success = d->dev->readBufferCapacity( d->bufferCapacity, d->availableBufferCapacity ); if ( !canceled() && d->command & NEXT_WRITABLE_ADDRESS ) { int nwa = d->dev->nextWritableAddress(); d->nextWritableAddress = nwa; d->success = ( d->success && ( nwa > 0 ) ); } d->dev->close(); } kDebug() << "(K3b::Device::DeviceHandler) finished command: " << d->command; return d->success; } #include "k3bdevicehandler.moc" diff --git a/libk3b/tools/k3bdevicehandler.h b/libk3b/tools/k3bdevicehandler.h index 05e06a2f6..52d70d33c 100644 --- a/libk3b/tools/k3bdevicehandler.h +++ b/libk3b/tools/k3bdevicehandler.h @@ -1,234 +1,238 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_DEVICE_HANDLER_H_ #define _K3B_DEVICE_HANDLER_H_ #include #include "k3bdevice.h" #include "k3bdiskinfo.h" #include "k3bmsf.h" #include "k3bcdtext.h" #include "k3b_export.h" namespace K3b { namespace Device { class Device; /** * The Device::Devicehandler is a threaded wrapper around Device::Device. * It allows async access to the time comsuming blocking Device::Device methods. * Since it's a Job it is very easy to handle. Just use one of the methods and * connect to the finished signal. * Be aware that all methods only return valid values if the corresponding info has * been successfuly requested. * * Be aware that multiple requests in a row (without waiting for the job to finish) will * only result in one finished() signal answering the last request. */ class LIBK3B_EXPORT DeviceHandler : public ThreadJob { Q_OBJECT public: - DeviceHandler( Device*, QObject* parent = 0 ); - DeviceHandler( QObject* parent = 0 ); - - /** - * This constructor is used by the global "quick" methods and should not be used - * otherwise except for the same usage. - */ - DeviceHandler( int command, Device* ); - - ~DeviceHandler(); - - const DiskInfo& diskInfo() const; - const Toc& toc() const; - const CdText& cdText() const; - const QByteArray& cdTextRaw() const; - Msf diskSize() const; - Msf remainingSize() const; - int tocType() const; - int numSessions() const; - long long bufferCapacity() const; - long long availableBufferCapacity() const; - - Msf nextWritableAddress() const; - - bool success() const; - - /** - * Use this when the command - * returnes some error code. - */ - int errorCode() const; - enum Command { + NO_COMMAND = 0x0, /** * Always successful, even with an empty or no media at all! */ NG_DISKINFO = 0x1, // TODO: rename this into DISKINFO /** * Always successful, even with an empty or no media at all! */ TOC = 0x2, /** * Successful if the media contains CD-Text. */ CD_TEXT = 0x4, /** * Successful if the media contains CD-Text. */ CD_TEXT_RAW = 0x8, /** * Always successful, even with an empty or no media at all! */ DISKSIZE = 0x10, /** * Always successful, even with an empty or no media at all! */ REMAININGSIZE = 0x20, /** * Always successful, even with an empty or no media at all! */ TOCTYPE = 0x40, /** * Always successful, even with an empty or no media at all! */ NUMSESSIONS = 0x80, /** * Successful if the drive could be blocked. */ BLOCK = 0x100, /** * Successful if the drive could be unblocked. */ UNBLOCK = 0x200, /** * Successful if the media was ejected. */ EJECT = 0x400, /** * Successful if the media was loaded */ LOAD = 0x800, RELOAD = EJECT|LOAD, /** * Retrieves NG_DISKINFO, TOC, and CD-Text in case of an audio or mixed * mode cd. * The only difference to NG_DISKINFO|TOC|CD_TEXT is that no CD-Text is not * considered an error. * * Always successful, even with an empty or no media at all! */ DISKINFO = 0x1000, // TODO: rename this in somthing like: DISKINFO_COMPLETE /** * Determine the device buffer state. */ BUFFER_CAPACITY = 0x2000, NEXT_WRITABLE_ADDRESS = 0x4000 }; + Q_DECLARE_FLAGS( Commands, Command ) + + DeviceHandler( Device*, QObject* parent = 0 ); + DeviceHandler( QObject* parent = 0 ); + + /** + * This constructor is used by the global "quick" methods and should not be used + * otherwise except for the same usage. + */ + DeviceHandler( Commands command, Device* ); + + ~DeviceHandler(); + + const DiskInfo& diskInfo() const; + const Toc& toc() const; + const CdText& cdText() const; + const QByteArray& cdTextRaw() const; + Msf diskSize() const; + Msf remainingSize() const; + int tocType() const; + int numSessions() const; + long long bufferCapacity() const; + long long availableBufferCapacity() const; + + Msf nextWritableAddress() const; + + bool success() const; + + /** + * Use this when the command + * returnes some error code. + */ + int errorCode() const; Q_SIGNALS: void finished( K3b::Device::DeviceHandler* ); public Q_SLOTS: void setDevice( K3b::Device::Device* ); - void sendCommand( int command ); + void sendCommand( Commands command ); void getToc(); void getDiskInfo(); void getDiskSize(); void getRemainingSize(); void getTocType(); void getNumSessions(); void block( bool ); void eject(); private: void jobFinished( bool success ); bool run(); class Private; Private* const d; }; /** * Usage: * \code * connect( Device::sendCommand( Device::DeviceHandler::MOUNT, dev ), * SIGNAL(finished(DeviceHandler*)), * this, SLOT(someSlot(DeviceHandler*)) ); * * void someSlot( DeviceHandler* dh ) { * if( dh->success() ) { * \endcode * * Be aware that the DeviceHandler will get destroyed once the signal has been * emitted. */ - LIBK3B_EXPORT DeviceHandler* sendCommand( int command, Device* ); + LIBK3B_EXPORT DeviceHandler* sendCommand( DeviceHandler::Commands command, Device* ); inline DeviceHandler* diskInfo(Device* dev) { return sendCommand(DeviceHandler::DISKINFO,dev); } inline DeviceHandler* toc(Device* dev) { return sendCommand(DeviceHandler::TOC,dev); } inline DeviceHandler* diskSize(Device* dev) { return sendCommand(DeviceHandler::DISKSIZE,dev); } inline DeviceHandler* remainingSize(Device* dev) { return sendCommand(DeviceHandler::REMAININGSIZE,dev); } inline DeviceHandler* tocType(Device* dev) { return sendCommand(DeviceHandler::TOCTYPE,dev); } inline DeviceHandler* numSessions(Device* dev) { return sendCommand(DeviceHandler::NUMSESSIONS,dev); } inline DeviceHandler* block(Device* dev) { return sendCommand(DeviceHandler::BLOCK,dev); } inline DeviceHandler* unblock(Device* dev) { return sendCommand(DeviceHandler::UNBLOCK,dev); } inline DeviceHandler* eject(Device* dev) { return sendCommand(DeviceHandler::EJECT,dev); } inline DeviceHandler* reload(Device* dev) { return sendCommand(DeviceHandler::RELOAD,dev); } inline DeviceHandler* load(Device* dev) { return sendCommand(DeviceHandler::LOAD,dev); } } } +Q_DECLARE_OPERATORS_FOR_FLAGS(K3b::Device::DeviceHandler::Commands) + #endif diff --git a/libk3b/tools/k3bfilesplitter.cpp b/libk3b/tools/k3bfilesplitter.cpp index e11b8b774..759fe5ce6 100644 --- a/libk3b/tools/k3bfilesplitter.cpp +++ b/libk3b/tools/k3bfilesplitter.cpp @@ -1,307 +1,350 @@ /* * - * Copyright (C) 2006-2008 Sebastian Trueg + * Copyright (C) 2006-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bfilesplitter.h" #include "k3bfilesysteminfo.h" #include #include +#include class K3b::FileSplitter::Private { public: Private( K3b::FileSplitter* splitter ) : m_splitter( splitter ) { } QString filename; QFile file; int counter; qint64 maxFileSize; + qint64 size; qint64 currentOverallPos; qint64 currentFilePos; void determineMaxFileSize() { if( maxFileSize == 0 ) { if( K3b::FileSystemInfo( filename ).type() == K3b::FileSystemInfo::FS_FAT ) maxFileSize = 1024ULL*1024ULL*1024ULL; // 1GB else maxFileSize = 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL; // incredibly big, 1024 TB } } QString buildFileName( int counter ) { if( counter > 0 ) return filename + '.' + QString::number(counter).rightJustified( 3, '0' ); else return filename; } + qint64 partFileSize( int counter ) { + QFileInfo fi( buildFileName( counter ) ); + if ( fi.exists() ) + return fi.size(); + else + return 0; + } + QString currentFileName() { return buildFileName( counter ); } bool openPrevFile() { return openFile( --counter ); } bool openNextFile() { return openFile( ++counter ); } bool openFile( int counter ) { file.close(); file.setFileName( buildFileName( counter ) ); currentFilePos = 0; if( file.open( m_splitter->openMode() ) ) { return true; } else { m_splitter->close(); return false; } } private: K3b::FileSplitter* m_splitter; }; K3b::FileSplitter::FileSplitter() { d = new Private( this ); } K3b::FileSplitter::FileSplitter( const QString& filename ) { d = new Private( this ); setName( filename ); } K3b::FileSplitter::~FileSplitter() { delete d; } -const QString& K3b::FileSplitter::name() const +QString K3b::FileSplitter::name() const { return d->filename; } void K3b::FileSplitter::setName( const QString& filename ) { close(); d->maxFileSize = 0; d->filename = filename; } bool K3b::FileSplitter::open( OpenMode mode ) { + kDebug() << mode; close(); d->determineMaxFileSize(); d->counter = 0; d->currentFilePos = 0; d->currentOverallPos = 0; + d->size = 0; if ( QIODevice::open( mode ) ) { return d->openFile( 0 ); } else { return false; } } void K3b::FileSplitter::close() { + QIODevice::close(); d->file.close(); d->counter = 0; d->currentFilePos = 0; d->currentOverallPos = 0; } -int K3b::FileSplitter::handle() const -{ - // FIXME: use a K3b::Pipe to simulate this - return -1; -} - - - void K3b::FileSplitter::flush() { d->file.flush(); } qint64 K3b::FileSplitter::size() const { - // not implemented due to Offset size limitations - return 0; + if ( d->size == 0 ) { + int i = 0; + forever { + qint64 s = d->partFileSize( i++ ); + d->size += s; + if ( s == 0 ) + break; + } + } + + return d->size; } qint64 K3b::FileSplitter::pos() const { return d->currentOverallPos; } bool K3b::FileSplitter::seek( qint64 pos ) { - Q_UNUSED( pos ); + kDebug() << pos; // FIXME: implement me (although not used yet) - return false; + return QIODevice::seek( pos ); } bool K3b::FileSplitter::atEnd() const { return d->file.atEnd() && !QFile::exists( d->buildFileName( d->counter+1 ) ); } qint64 K3b::FileSplitter::readData( char *data, qint64 maxlen ) { qint64 r = d->file.read( data, maxlen ); if( r == 0 ) { if( atEnd() ) { return r; } else if( d->openNextFile() ) { // recursively call us return readData( data, maxlen ); } } else if( r > 0 ) { d->currentOverallPos += r; d->currentFilePos += r; } - + else { + kDebug() << "Read failed from" << d->file.fileName(); + setErrorString( d->file.errorString() ); + } return r; } qint64 K3b::FileSplitter::writeData( const char *data, qint64 len ) { - // We cannot rely on QFile::at since it uses long on most copmpilations qint64 max = qMin( len, d->maxFileSize - d->currentFilePos ); qint64 r = d->file.write( data, max ); - if( r < 0 ) + if( r < 0 ) { + setErrorString( d->file.errorString() ); return r; + } d->currentOverallPos += r; d->currentFilePos += r; // recursively call us if( r < len ) { if( d->openNextFile() ) return r + writeData( data+r, len-r ); else return -1; } else return r; } // int K3b::FileSplitter::getch() // { // int r = d->file.getch(); // if( r == -1 ) { // if( !d->file.atEnd() ) { // return -1; // } // else if( !atEnd() ) { // if( !d->openNextFile() ) // return -1; // else // return getch(); // } // } // d->currentOverallPos++; // d->currentFilePos++; // return r; // } // int K3b::FileSplitter::putch( int c ) // { // if( d->currentFilePos < d->maxFileSize ) { // d->currentOverallPos++; // d->currentFilePos++; // return d->file.putch( c ); // } // else if( d->openNextFile() ) { // // recursively call us // return putch( c ); // } // else // return -1; // } // int K3b::FileSplitter::ungetch( int c ) // { // if( d->currentFilePos > 0 ) { // int r = d->file.ungetch( c ); // if( r != -1 ) { // d->currentOverallPos--; // d->currentFilePos--; // } // return r; // } // else if( d->counter > 0 ) { // // open prev file // if( d->openPrevFile() ) { // // seek to the end // d->file.at( d->file.size() ); // d->currentFilePos = d->file.at(); // return getch(); // } // else // return -1; // } // else // return -1; // } void K3b::FileSplitter::remove() { close(); - while( QFile::exists( d->buildFileName( d->counter ) ) ) - QFile::remove( d->buildFileName( d->counter++ ) ); + int i = 0; + while( QFile::exists( d->buildFileName( i ) ) ) + QFile::remove( d->buildFileName( i++ ) ); } void K3b::FileSplitter::setMaxFileSize( qint64 size ) { d->maxFileSize = size; } + + +bool K3b::FileSplitter::waitForBytesWritten( int ) +{ + if ( isOpen() && isWritable() ) { + return true; + } + else { + return false; + } +} + + +bool K3b::FileSplitter::waitForReadyRead( int ) +{ + if ( isOpen() && isReadable() ) { + return !atEnd(); + } + else { + return false; + } +} + +#include "k3bfilesplitter.moc" diff --git a/libk3b/tools/k3bfilesplitter.h b/libk3b/tools/k3bfilesplitter.h index e3d4d23c0..116978b3a 100644 --- a/libk3b/tools/k3bfilesplitter.h +++ b/libk3b/tools/k3bfilesplitter.h @@ -1,106 +1,106 @@ /* * - * Copyright (C) 2006-2008 Sebastian Trueg + * Copyright (C) 2006-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_FILE_SPLITTER_H_ #define _K3B_FILE_SPLITTER_H_ #include #include #include namespace K3b { /** * QFile replacement which splits * big files according to the underlying file system's * maximum file size. * * The filename will be changed to include a counter * if the file has to be splitted like so: * *

      * filename.iso
      * filename.iso.001
      * filename.iso.002
      * ...
      * 
*/ class LIBK3B_EXPORT FileSplitter : public QIODevice { + Q_OBJECT + public: FileSplitter(); FileSplitter( const QString& filename ); ~FileSplitter(); /** * Set the maximum file size. If this is set to 0 * (the default) the max filesize is determined based on * the filesystem type. * * Be aware that setName will reset the max file size. */ void setMaxFileSize( qint64 size ); - const QString& name() const; + QString name() const; void setName( const QString& filename ); virtual bool open( OpenMode mode ); virtual void close(); - /** - * File descriptor to read from and write to. - * Not implemented yet! - */ - int handle() const; - virtual void flush(); - /** - * Not implemented - */ virtual qint64 size() const; - /** - * Not implemented - */ virtual qint64 pos() const; /** * Not implemented */ virtual bool seek( qint64 ); virtual bool atEnd() const; /** * Deletes all the splitted files. * Caution: Does remove all files that fit the naming scheme without any * additional checks. */ void remove(); + /** + * \return \p true if the file is open in writable mode + */ + bool waitForBytesWritten( int msecs ); + + /** + * \return \p true if open and not at end + */ + bool waitForReadyRead( int msecs ); + protected: virtual qint64 readData( char *data, qint64 maxlen ); virtual qint64 writeData( const char *data, qint64 len ); private: class Private; Private* d; }; } #endif diff --git a/libk3b/tools/k3blistview.h b/libk3b/tools/k3blistview.h index 85df83668..97d662120 100644 --- a/libk3b/tools/k3blistview.h +++ b/libk3b/tools/k3blistview.h @@ -1,312 +1,304 @@ /* * * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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. */ #ifndef K3BLISTVIEW_H #define K3BLISTVIEW_H #include #include "k3b_export.h" #include #include #include //Added by qt3to4: #include #include #include #include +#include + class QPainter; class QPushButton; class QResizeEvent; class QComboBox; class QSpinBox; class QLineEdit; class QEvent; class QValidator; -namespace K3b { - class MsfEdit; -} namespace K3b { + class MsfEdit; class ListView; -} - -namespace K3b { /** * \deprecated */ class KDE_DEPRECATED LIBK3B_EXPORT ListViewItem : public K3ListViewItem { public: ListViewItem(Q3ListView *parent); ListViewItem(Q3ListViewItem *parent); ListViewItem(Q3ListView *parent, Q3ListViewItem *after); ListViewItem(Q3ListViewItem *parent, Q3ListViewItem *after); ListViewItem(Q3ListView *parent, const QString&, const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString()); ListViewItem(Q3ListViewItem *parent, const QString&, const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString()); ListViewItem(Q3ListView *parent, Q3ListViewItem *after, const QString&, const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString()); ListViewItem(Q3ListViewItem *parent, Q3ListViewItem *after, const QString&, const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString(), const QString& = QString()); virtual ~ListViewItem(); /** * reimplemented from K3ListViewItem */ void setup(); virtual int width( const QFontMetrics& fm, const Q3ListView* lv, int c ) const; void setEditor( int col, int type, const QStringList& = QStringList() ); void setButton( int col, bool ); void setValidator( int col, QValidator* v ); QValidator* validator( int col ) const; int editorType( int col ) const; bool needButton( int col ) const; const QStringList& comboStrings( int col ) const; enum EditorType { NONE, COMBO, LINE, SPIN, MSF }; void setFont( int col, const QFont& f ); void setBackgroundColor( int col, const QColor& ); void setForegroundColor( int col, const QColor& ); void setDisplayProgressBar( int col, bool ); void setProgress( int, int ); void setTotalSteps( int col, int steps ); /** * The margin left and right of the cell */ void setMarginHorizontal( int col, int margin ); /** * The top and button margin of the cell */ void setMarginVertical( int margin ); int marginHorizontal( int col ) const; int marginVertical() const; /** * Do not reimplement this but paintK3bCell to use the margin and background stuff. */ virtual void paintCell( QPainter* p, const QColorGroup& cg, int col, int width, int align ); virtual void paintK3bCell( QPainter* p, const QColorGroup& cg, int col, int width, int align ); private: void paintProgressBar( QPainter* p, const QColorGroup& cgh, int col, int width ); class ColumnInfo; mutable ColumnInfo* m_columns; ColumnInfo* getColumnInfo( int ) const; void init(); int m_vMargin; }; -} -namespace K3b { /** * \deprecated */ - KDE_DEPRECATED class LIBK3B_EXPORT CheckListViewItem : public ListViewItem + class KDE_DEPRECATED LIBK3B_EXPORT CheckListViewItem : public ListViewItem { public: CheckListViewItem(Q3ListView *parent); CheckListViewItem(Q3ListViewItem *parent); CheckListViewItem(Q3ListView *parent, Q3ListViewItem *after); CheckListViewItem(Q3ListViewItem *parent, Q3ListViewItem *after); virtual bool isChecked() const; virtual void setChecked( bool checked ); protected: virtual void paintK3bCell( QPainter* p, const QColorGroup& cg, int col, int width, int align ); private: bool m_checked; }; -} - -namespace K3b { /** * \deprecated */ - KDE_DEPRECATED class LIBK3B_EXPORT ListView : public K3ListView + class LIBK3B_EXPORT ListView : public K3ListView { - friend class ListViewItem; - Q_OBJECT public: ListView (QWidget *parent = 0 ); virtual ~ListView(); virtual void setCurrentItem( Q3ListViewItem* ); ListViewItem* currentlyEditedItem() const { return m_currentEditItem; } QWidget* editor( ListViewItem::EditorType ) const; enum BgPixPosition { TOP_LEFT, CENTER }; /** * This will set a background pixmap which is not tiled. * @param pos position on the viewport. */ void setK3bBackgroundPixmap( const QPixmap&, int pos = CENTER ); /** * Searches for the first item above @p i which is one level higher. * For 1st level items this will always be the listview's root item. */ static Q3ListViewItem* parentItem( Q3ListViewItem* i ); Q_SIGNALS: void editorButtonClicked( K3b::ListViewItem*, int ); public Q_SLOTS: void setNoItemText( const QString& ); // void setNoItemPixmap( const QPixmap& ); void setNoItemVerticalMargin( int i ) { m_noItemVMargin = i; } void setNoItemHorizontalMargin( int i ) { m_noItemHMargin = i; } void setDoubleClickForEdit( bool b ) { m_doubleClickForEdit = b; } void hideEditor(); void editItem( ListViewItem*, int ); virtual void clear(); private Q_SLOTS: void updateEditorSize(); virtual void slotEditorLineEditReturnPressed(); virtual void slotEditorComboBoxActivated( const QString& ); virtual void slotEditorSpinBoxValueChanged( int ); virtual void slotEditorMsfEditValueChanged( int ); virtual void slotEditorButtonClicked(); protected Q_SLOTS: void showEditor( K3b::ListViewItem*, int col ); void placeEditor( K3b::ListViewItem*, int col ); /** * This is called whenever one of the editor's contents changes * the default implementation just returnes true * * FIXME: should be called something like mayRename */ virtual bool renameItem( K3b::ListViewItem*, int, const QString& ); /** * default impl just emits signal * editorButtonClicked(...) */ virtual void slotEditorButtonClicked( K3b::ListViewItem*, int ); protected: /** * calls K3ListView::drawContentsOffset * and paints a the noItemText if no item is in the list */ virtual void drawContentsOffset ( QPainter * p, int ox, int oy, int cx, int cy, int cw, int ch ); virtual void resizeEvent( QResizeEvent* ); virtual void paintEmptyArea( QPainter*, const QRect& rect ); /** * Reimplemented for internal reasons. * * Further reimplementations should call this function or else some features may not work correctly. * * The API is unaffected. */ virtual void viewportResizeEvent( QResizeEvent* ); /** * Reimplemented for internal reasons. * Further reimplementations should call this function or else * some features may not work correctly. * * The API is unaffected. */ virtual void viewportPaintEvent(QPaintEvent*); virtual bool eventFilter( QObject*, QEvent* ); ListViewItem* currentEditItem() const { return m_currentEditItem; } int currentEditColumn() const { return m_currentEditColumn; } private: QWidget* prepareEditor( ListViewItem* item, int col ); void prepareButton( ListViewItem* item, int col ); bool doRename(); QString m_noItemText; // QPixmap m_noItemPixmap; int m_noItemVMargin; int m_noItemHMargin; ListViewItem* m_currentEditItem; int m_currentEditColumn; bool m_doubleClickForEdit; Q3ListViewItem* m_lastClickedItem; QPushButton* m_editorButton; QComboBox* m_editorComboBox; QSpinBox* m_editorSpinBox; QLineEdit* m_editorLineEdit; MsfEdit* m_editorMsfEdit; QPixmap m_backgroundPixmap; int m_backgroundPixmapPosition; class Private; Private* d; + + friend class ListViewItem; }; } #endif diff --git a/libk3b/tools/k3bmd5job.cpp b/libk3b/tools/k3bmd5job.cpp index 88e9864d0..01e5528e2 100644 --- a/libk3b/tools/k3bmd5job.cpp +++ b/libk3b/tools/k3bmd5job.cpp @@ -1,320 +1,306 @@ /* * - * Copyright (C) 2003-2007 Sebastian Trueg + * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2007 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bmd5job.h" #include #include #include #include #include #include #include #include #include -#include - -#include +#include class K3b::Md5Job::Private { public: Private() - : fileDes(-1), - fdNotifier(0), + : ioDevice(0), finished(true), data(0), isoFile(0), maxSize(0), lastProgress(0) { } KMD5 md5; K3b::FileSplitter file; QTimer timer; QString filename; - int fileDes; + QIODevice* ioDevice; K3b::Device::Device* device; - QSocketNotifier* fdNotifier; bool finished; char* data; const K3b::Iso9660File* isoFile; qint64 maxSize; qint64 readData; int lastProgress; KIO::filesize_t imageSize; static const int BUFFERSIZE = 2048*10; }; K3b::Md5Job::Md5Job( K3b::JobHandler* jh, QObject* parent ) : K3b::Job( jh, parent ), d( new Private() ) { d->data = new char[Private::BUFFERSIZE]; connect( &d->timer, SIGNAL(timeout()), this, SLOT(slotUpdate()) ); } K3b::Md5Job::~Md5Job() { delete [] d->data; delete d; } void K3b::Md5Job::start() { cancel(); jobStarted(); d->readData = 0; if( d->isoFile ) { d->imageSize = d->isoFile->size(); } else if( !d->filename.isEmpty() ) { if( !QFile::exists( d->filename ) ) { emit infoMessage( i18n("Could not find file %1",d->filename), ERROR ); jobFinished(false); return; } d->file.setName( d->filename ); if( !d->file.open( QIODevice::ReadOnly ) ) { emit infoMessage( i18n("Could not open file %1",d->filename), ERROR ); jobFinished(false); return; } d->imageSize = K3b::filesize( d->filename ); } else d->imageSize = 0; if( d->device ) { // // Let the drive determine the optimal reading speed // d->device->setSpeed( 0xffff, 0xffff ); } d->md5.reset(); d->finished = false; - if( d->fileDes != -1 ) - setupFdNotifier(); + if( d->ioDevice ) + connect( d->ioDevice, SIGNAL(readyRead()), this, SLOT(slotUpdate()) ); else d->timer.start(0); } -void K3b::Md5Job::setupFdNotifier() -{ - // the QSocketNotifier will fire once the fd is closed - delete d->fdNotifier; - d->fdNotifier = new QSocketNotifier( d->fileDes, QSocketNotifier::Read, this ); - connect( d->fdNotifier, SIGNAL(activated(int)), this, SLOT(slotUpdate()) ); - d->fdNotifier->setEnabled( true ); -} - - void K3b::Md5Job::cancel() { if( !d->finished ) { stopAll(); emit canceled(); jobFinished( false ); } } void K3b::Md5Job::setFile( const QString& filename ) { d->filename = filename; d->isoFile = 0; - d->fileDes = -1; + d->ioDevice = 0; d->device = 0; } void K3b::Md5Job::setFile( const K3b::Iso9660File* file ) { d->isoFile = file; - d->fileDes = -1; + d->ioDevice = 0; d->filename.truncate(0); d->device = 0; } -void K3b::Md5Job::setFd( int fd ) +void K3b::Md5Job::setIODevice( QIODevice* dev ) { - d->fileDes = fd; + d->ioDevice = dev; d->filename.truncate(0); d->isoFile = 0; d->device = 0; } void K3b::Md5Job::setDevice( K3b::Device::Device* dev ) { d->device = dev; - d->fileDes = -1; + d->ioDevice = 0; d->filename.truncate(0); d->isoFile = 0; } void K3b::Md5Job::setMaxReadSize( qint64 size ) { d->maxSize = size; } void K3b::Md5Job::slotUpdate() { if( !d->finished ) { // determine bytes to read qint64 readSize = Private::BUFFERSIZE; if( d->maxSize > 0 ) readSize = qMin( readSize, d->maxSize - d->readData ); if( readSize <= 0 ) { // kDebug() << "(K3b::Md5Job) reached max size of " << d->maxSize << ". Stopping."; emit debuggingOutput( "K3b::Md5Job", QString("Reached max read of %1. Stopping after %2 bytes.").arg(d->maxSize).arg(d->readData) ); stopAll(); emit percent( 100 ); jobFinished(true); } else { int read = 0; // // read from the iso9660 file // if( d->isoFile ) { read = d->isoFile->read( d->readData, d->data, readSize ); } // // read from the device // else if( d->device ) { // // when reading from a device we always read multiples of 2048 bytes. // Only the last sector may not be used completely. // qint64 sector = d->readData/2048; qint64 sectorCnt = qMax( readSize/2048, ( qint64 )1 ); read = -1; if( d->device->read10( reinterpret_cast(d->data), sectorCnt*2048, sector, sectorCnt ) ) read = qMin( readSize, sectorCnt*2048 ); } // // read from the file // - else if( d->fileDes < 0 ) { + else if( !d->ioDevice ) { read = d->file.read( d->data, readSize ); } // - // reading from the file descriptor + // reading from the io device // else { - read = ::read( d->fileDes, d->data, readSize ); + read = d->ioDevice->read( d->data, readSize ); } if( read < 0 ) { - emit infoMessage( i18n("Error while reading from file %1",d->filename), ERROR ); + emit infoMessage( i18n("Error while reading from file %1", d->filename), ERROR ); stopAll(); jobFinished(false); } else if( read == 0 ) { // kDebug() << "(K3b::Md5Job) read all data. Total size: " << d->readData << ". Stopping."; emit debuggingOutput( "K3b::Md5Job", QString("All data read. Stopping after %1 bytes.").arg(d->readData) ); stopAll(); emit percent( 100 ); jobFinished(true); } else { d->readData += read; d->md5.update( d->data, read ); int progress = 0; if( d->isoFile || !d->filename.isEmpty() ) progress = (int)((double)d->readData * 100.0 / (double)d->imageSize); else if( d->maxSize > 0 ) progress = (int)((double)d->readData * 100.0 / (double)d->maxSize); if( progress != d->lastProgress ) { d->lastProgress = progress; emit percent( progress ); } } } } } QByteArray K3b::Md5Job::hexDigest() { if( d->finished ) return d->md5.hexDigest(); else return ""; } QByteArray K3b::Md5Job::base64Digest() { if( d->finished ) return d->md5.base64Digest(); else return ""; } void K3b::Md5Job::stop() { emit debuggingOutput( "K3b::Md5Job", QString("Stopped manually after %1 bytes.").arg(d->readData) ); stopAll(); jobFinished( true ); } void K3b::Md5Job::stopAll() { - if( d->fdNotifier ) - d->fdNotifier->setEnabled( false ); + if( d->ioDevice ) + disconnect( d->ioDevice, SIGNAL(readyRead()), this, SLOT(slotUpdate()) ); if( d->file.isOpen() ) d->file.close(); d->timer.stop(); d->finished = true; } #include "k3bmd5job.moc" diff --git a/libk3b/tools/k3bmd5job.h b/libk3b/tools/k3bmd5job.h index fb5df9149..f58977f96 100644 --- a/libk3b/tools/k3bmd5job.h +++ b/libk3b/tools/k3bmd5job.h @@ -1,92 +1,94 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_MD5_JOB_H_ #define _K3B_MD5_JOB_H_ #include #include #include "k3b_export.h" +class QIODevice; + namespace K3b { namespace Device { class Device; } class Iso9660File; class LIBK3B_EXPORT Md5Job : public Job { Q_OBJECT public: Md5Job( JobHandler* jh , QObject* parent = 0 ); ~Md5Job(); QByteArray hexDigest(); QByteArray base64Digest(); public Q_SLOTS: void start(); void stop(); void cancel(); // FIXME: read from QIODevice and thus add FileSplitter support /** * read from a file. * * Be aware that the Md5Job uses FileSplitter to read splitted * images. In the future this will be changed with the introduction * of a setIODevice method. */ void setFile( const QString& filename ); /** * read from an iso9660 file */ void setFile( const Iso9660File* ); /** * read from a device * This should be used in combination with setMaxReadSize */ void setDevice( Device::Device* dev ); /** - * read from the opened file descriptor. + * read from the opened QIODevice. * One needs to set the max read length or call stop() * to finish calculation. */ - void setFd( int fd ); + void setIODevice( QIODevice* ioDev ); /** * Set the maximum bytes to read. */ void setMaxReadSize( qint64 ); private Q_SLOTS: void slotUpdate(); private: void setupFdNotifier(); void stopAll(); class Private; Private* const d; }; } #endif diff --git a/libk3b/tools/k3bmedium.cpp b/libk3b/tools/k3bmedium.cpp index fd6460351..c51bc455a 100644 --- a/libk3b/tools/k3bmedium.cpp +++ b/libk3b/tools/k3bmedium.cpp @@ -1,498 +1,507 @@ /* * * Copyright (C) 2005-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 #include "k3bmedium.h" #include "k3bmedium_p.h" #include "k3bcddb.h" #include "k3bdeviceglobals.h" #include "k3bglobals.h" #include "k3biso9660.h" #include "k3biso9660backend.h" #include #include #include #include #include K3b::MediumPrivate::MediumPrivate() : device( 0 ), content( K3b::Medium::CONTENT_NONE ) { } K3b::Medium::Medium() { d = new K3b::MediumPrivate; } K3b::Medium::Medium( const K3b::Medium& other ) { d = other.d; } K3b::Medium::Medium( K3b::Device::Device* dev ) { d = new K3b::MediumPrivate; d->device = dev; } K3b::Medium::~Medium() { } K3b::Medium& K3b::Medium::operator=( const K3b::Medium& other ) { d = other.d; return *this; } bool K3b::Medium::isValid() const { return d->device != 0; } void K3b::Medium::setDevice( K3b::Device::Device* dev ) { if( d->device != dev ) { reset(); d->device = dev; } } K3b::Device::Device* K3b::Medium::device() const { return d->device; } K3b::Device::DiskInfo K3b::Medium::diskInfo() const { return d->diskInfo; } K3b::Device::Toc K3b::Medium::toc() const { return d->toc; } K3b::Device::CdText K3b::Medium::cdText() const { return d->cdText; } KCDDB::CDInfo K3b::Medium::cddbInfo() const { return d->cddbInfo; } QList K3b::Medium::writingSpeeds() const { return d->writingSpeeds; } K3b::Medium::MediumContents K3b::Medium::content() const { return d->content; } const K3b::Iso9660SimplePrimaryDescriptor& K3b::Medium::iso9660Descriptor() const { return d->isoDesc; } void K3b::Medium::reset() { d->diskInfo = K3b::Device::DiskInfo(); d->toc.clear(); d->cdText.clear(); d->writingSpeeds.clear(); d->content = CONTENT_NONE; d->cddbInfo.clear(); // clear the desc d->isoDesc = K3b::Iso9660SimplePrimaryDescriptor(); } void K3b::Medium::update() { if( d->device ) { reset(); d->diskInfo = d->device->diskInfo(); if( d->diskInfo.diskState() != K3b::Device::STATE_NO_MEDIA ) { kDebug() << "(K3b::Medium) found medium: (" << d->device->blockDeviceName() << ')' << endl << "====================================================="; d->diskInfo.debug(); kDebug() << "====================================================="; } if( diskInfo().diskState() == K3b::Device::STATE_COMPLETE || diskInfo().diskState() == K3b::Device::STATE_INCOMPLETE ) { d->toc = d->device->readToc(); if( d->toc.contentType() == K3b::Device::AUDIO || d->toc.contentType() == K3b::Device::MIXED ) { // update CD-Text d->cdText = d->device->readCdText(); } } if( diskInfo().mediaType() & K3b::Device::MEDIA_WRITABLE ) { d->writingSpeeds = d->device->determineSupportedWriteSpeeds(); } analyseContent(); } } void K3b::Medium::analyseContent() { // set basic content types switch( d->toc.contentType() ) { case K3b::Device::AUDIO: d->content = CONTENT_AUDIO; break; case K3b::Device::DATA: case K3b::Device::DVD: d->content = CONTENT_DATA; break; case K3b::Device::MIXED: d->content = CONTENT_AUDIO|CONTENT_DATA; break; default: d->content = CONTENT_NONE; } // analyze filesystem if( d->content & CONTENT_DATA ) { //kDebug() << "(K3b::Medium) Checking file system."; unsigned long startSec = 0; if( diskInfo().numSessions() > 1 ) { // We use the last data track // this way we get the latest session on a ms cd K3b::Device::Toc::const_iterator it = d->toc.constEnd(); --it; // this is valid since there is at least one data track while( it != d->toc.constBegin() && (*it).type() != K3b::Device::Track::TYPE_DATA ) --it; startSec = (*it).firstSector().lba(); } else { // use first data track K3b::Device::Toc::const_iterator it = d->toc.constBegin(); while( it != d->toc.constEnd() && (*it).type() != K3b::Device::Track::TYPE_DATA ) ++it; startSec = (*it).firstSector().lba(); } //kDebug() << "(K3b::Medium) Checking file system at " << startSec; // force the backend since we don't need decryption // which just slows down the whole process K3b::Iso9660 iso( new K3b::Iso9660DeviceBackend( d->device ) ); iso.setStartSector( startSec ); iso.setPlainIso9660( true ); if( iso.open() ) { d->isoDesc = iso.primaryDescriptor(); kDebug() << "(K3b::Medium) found volume id from start sector " << startSec << ": '" << d->isoDesc.volumeId << "'" ; if( diskInfo().isDvdMedia() ) { // Every VideoDVD needs to have a VIDEO_TS.IFO file if( iso.firstIsoDirEntry()->entry( "VIDEO_TS/VIDEO_TS.IFO" ) != 0 ) d->content |= CONTENT_VIDEO_DVD; } else { kDebug() << "(K3b::Medium) checking for VCD."; // check for VCD const K3b::Iso9660Entry* vcdEntry = iso.firstIsoDirEntry()->entry( "VCD/INFO.VCD" ); const K3b::Iso9660Entry* svcdEntry = iso.firstIsoDirEntry()->entry( "SVCD/INFO.SVD" ); const K3b::Iso9660File* vcdInfoFile = 0; if( vcdEntry ) { kDebug() << "(K3b::Medium) found vcd entry."; if( vcdEntry->isFile() ) vcdInfoFile = static_cast(vcdEntry); } if( svcdEntry && !vcdInfoFile ) { kDebug() << "(K3b::Medium) found svcd entry."; if( svcdEntry->isFile() ) vcdInfoFile = static_cast(svcdEntry); } if( vcdInfoFile ) { char buffer[8]; if ( vcdInfoFile->read( 0, buffer, 8 ) == 8 && ( !qstrncmp( buffer, "VIDEO_CD", 8 ) || !qstrncmp( buffer, "SUPERVCD", 8 ) || !qstrncmp( buffer, "HQ-VCD ", 8 ) ) ) d->content |= CONTENT_VIDEO_CD; } } } // opened iso9660 } } QString K3b::Medium::shortString( bool useContent ) const { QString mediaTypeString = K3b::Device::mediaTypeString( diskInfo().mediaType(), true ); if( diskInfo().diskState() == K3b::Device::STATE_UNKNOWN ) { return i18n("No medium information"); } else if( diskInfo().diskState() == K3b::Device::STATE_NO_MEDIA ) { return i18n("No medium present"); } else if( diskInfo().diskState() == K3b::Device::STATE_EMPTY ) { return i18n("Empty %1 medium", mediaTypeString ); } else { if( useContent ) { // AUDIO + MIXED if( d->toc.contentType() == K3b::Device::AUDIO || d->toc.contentType() == K3b::Device::MIXED ) { QString title = cdText().title(); QString performer = cdText().performer(); if ( title.isEmpty() ) { title = cddbInfo().get( KCDDB::Title ).toString(); } if ( performer.isEmpty() ) { performer = cddbInfo().get( KCDDB::Artist ).toString(); } if( !performer.isEmpty() && !title.isEmpty() ) { return QString("%1 - %2 (%3)") .arg( performer ) .arg( title ) .arg( d->toc.contentType() == K3b::Device::AUDIO ? i18n("Audio CD") : i18n("Mixed CD") ); } else if( d->toc.contentType() == K3b::Device::AUDIO ) { return i18n("Audio CD"); } else { return i18n("%1 (Mixed CD)", beautifiedVolumeId() ); } } // DATA CD and DVD else if( !volumeId().isEmpty() ) { if( content() & CONTENT_VIDEO_DVD ) { return QString("%1 (%2)").arg(beautifiedVolumeId()).arg( i18n("Video DVD") ); } else if( content() & CONTENT_VIDEO_CD ) { return QString("%1 (%2)").arg(beautifiedVolumeId()).arg(i18n("Video CD") ); } else if( diskInfo().diskState() == K3b::Device::STATE_INCOMPLETE ) { return i18n("%1 (Appendable Data %2)", beautifiedVolumeId(), mediaTypeString ); } else { return i18n("%1 (Complete Data %2)", beautifiedVolumeId(), mediaTypeString ); } } else { if( diskInfo().diskState() == K3b::Device::STATE_INCOMPLETE ) { return i18n("Appendable Data %1", mediaTypeString ); } else { return i18n("Complete Data %1", mediaTypeString ); } } } // without content else { if( diskInfo().diskState() == K3b::Device::STATE_INCOMPLETE ) { return i18n("Appendable %1 medium", mediaTypeString ); } else { return i18n("Complete %1 medium", mediaTypeString ); } } } } QString K3b::Medium::longString() const { QString s = QString("

%1 %2 (%3)" "

") .arg( d->device->vendor() ) .arg( d->device->description() ) .arg( d->device->blockDeviceName() ) + shortString( true ) + " (" + K3b::Device::mediaTypeString( diskInfo().mediaType(), true ) + ')'; if( diskInfo().diskState() == K3b::Device::STATE_COMPLETE || diskInfo().diskState() == K3b::Device::STATE_INCOMPLETE ) { s += "
" + i18np("1 in %2 track", "%1 in %2 tracks", d->toc.count(), KIO::convertSize(diskInfo().size().mode1Bytes() ) ); if( diskInfo().numSessions() > 1 ) s += i18np(" and %1 session", " and %1 sessions", diskInfo().numSessions() ); } if( diskInfo().diskState() == K3b::Device::STATE_EMPTY || diskInfo().diskState() == K3b::Device::STATE_INCOMPLETE ) s += "
" + i18n("Free space: %1", KIO::convertSize( diskInfo().remainingSize().mode1Bytes() ) ); if( !diskInfo().empty() && diskInfo().rewritable() ) s += "
" + i18n("Capacity: %1", KIO::convertSize( diskInfo().capacity().mode1Bytes() ) ); return s; } QString K3b::Medium::volumeId() const { return iso9660Descriptor().volumeId; } QString K3b::Medium::beautifiedVolumeId() const { const QString& oldId = volumeId(); QString newId; bool newWord = true; for( int i = 0; i < oldId.length(); ++i ) { QChar c = oldId[i]; // // first let's handle the cases where we do not change // the id anyway // // In case the id already contains spaces or lower case chars // it is likely that it already looks good and does ignore // the restricted iso9660 charset (like almost every project // created with K3b) // if( c.isLetter() && c.toLower() == c ) return oldId; else if( c.isSpace() ) return oldId; // replace underscore with space else if( c.unicode() == 95 ) { newId.append( ' ' ); newWord = true; } // from here on only upper case chars and numbers and stuff else if( c.isLetter() ) { if( newWord ) { newId.append( c ); newWord = false; } else { newId.append( c.toLower() ); } } else { newId.append( c ); } } return newId; } KIcon K3b::Medium::icon() const { if ( diskInfo().empty() ) { return KIcon( "media-optical-recordable" ); } else if ( content() & CONTENT_AUDIO ) { return KIcon( "media-optical-audio" ); } else { return KIcon( "media-optical" ); } } bool K3b::Medium::operator==( const K3b::Medium& other ) const { if( this->d == other.d ) return true; return( this->device() == other.device() && this->diskInfo() == other.diskInfo() && this->toc() == other.toc() && this->cdText() == other.cdText() && d->cddbInfo == other.d->cddbInfo && this->content() == other.content() && this->iso9660Descriptor() == other.iso9660Descriptor() ); } bool K3b::Medium::operator!=( const K3b::Medium& other ) const { if( this->d == other.d ) return false; return( this->device() != other.device() || this->diskInfo() != other.diskInfo() || this->toc() != other.toc() || this->cdText() != other.cdText() || d->cddbInfo != other.d->cddbInfo || this->content() != other.content() || this->iso9660Descriptor() != other.iso9660Descriptor() ); } bool K3b::Medium::sameMedium( const K3b::Medium& other ) const { if( this->d == other.d ) return true; // here we do ignore cddb info return( this->device() == other.device() && this->diskInfo() == other.diskInfo() && this->toc() == other.toc() && this->cdText() == other.cdText() && this->content() == other.content() && this->iso9660Descriptor() == other.iso9660Descriptor() ); } + + +// static +QString K3b::Medium::mediaRequestString( Device::MediaTypes requestedMediaTypes, Device::MediaStates requestedMediaStates, const K3b::Msf& requestedSize ) +{ +#warning IMPLEMENTME: mediaRequestString and use me in EmptyDiskWaiter and MediaSelectionComboBox + // FIXME: do not construct strings as in K3b 1.0. It makes translation to many languages impossible! + return QString(); +} diff --git a/libk3b/tools/k3bmedium.h b/libk3b/tools/k3bmedium.h index 7fc6f8221..bf9d1a2a3 100644 --- a/libk3b/tools/k3bmedium.h +++ b/libk3b/tools/k3bmedium.h @@ -1,166 +1,171 @@ /* * * Copyright (C) 2005-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_MEDIUM_H_ #define _K3B_MEDIUM_H_ #include "k3b_export.h" #include "k3bdiskinfo.h" #include "k3btoc.h" #include "k3bcdtext.h" #include "k3bdevice.h" #include "k3biso9660.h" #include #include #include namespace KCDDB{ class CDInfo; } namespace K3b { class MediumPrivate; /** * Medium represents a medium in K3b. * * It is implicetely shared, thus copying is very fast. */ class LIBK3B_EXPORT Medium { public: Medium(); Medium( const Medium& ); explicit Medium( Device::Device* dev ); ~Medium(); /** * Copy operator */ Medium& operator=( const Medium& ); bool isValid() const; void setDevice( Device::Device* dev ); /** * Resets everything to default values except the device. * This means empty toc, cd text, no writing speeds, and a diskinfo * with state UNKNOWN. */ void reset(); /** * Updates the medium information if the device is not null. * Do not use this in the GUI thread since it uses blocking * K3bdevice methods. */ void update(); Device::Device* device() const; Device::DiskInfo diskInfo() const; Device::Toc toc() const; Device::CdText cdText() const; KCDDB::CDInfo cddbInfo() const; /** * The writing speeds the device supports with the inserted medium. * With older devices this list might even be empty for writable * media. In that case refer to Device::Device::maxWriteSpeed * combined with a manual speed selection. */ QList writingSpeeds() const; QString volumeId() const; /** * This method tries to make a volume identificator witch uses a reduced character set * look more beautiful by, for example, replacing '_' with a space or replacing all upper * case words. * * Volume ids already containing spaces or lower case characters are left unchanged. */ QString beautifiedVolumeId() const; /** * An icon representing the contents of the medium. */ KIcon icon() const; /** * Content type. May be combined by a binary OR. */ enum MediumContent { CONTENT_NONE = 0x1, CONTENT_AUDIO = 0x2, CONTENT_DATA = 0x4, CONTENT_VIDEO_CD = 0x8, CONTENT_VIDEO_DVD = 0x10, CONTENT_ALL = 0xFF }; Q_DECLARE_FLAGS( MediumContents, MediumContent ) /** * \return a bitwise combination of MediumContent. * A VideoCD for example may have the following content: * CONTENT_AUDIO|CONTENT_DATA|CONTENT_VIDEO_CD */ MediumContents content() const; /** * \return The volume descriptor from the ISO9660 filesystem. */ const Iso9660SimplePrimaryDescriptor& iso9660Descriptor() const; /** * \return A short one-liner string representing the medium. * This string may be used for labels or selection boxes. * \param useContent if true the content of the CD/DVD will be used, otherwise * the string will simply be something like "empty DVD-R medium". */ QString shortString( bool useContent = true ) const; /** * \return A HTML formatted string decribing this medium. This includes the device, the * medium type, the contents type, and some detail information like the number of * tracks. * This string may be used for tooltips or short descriptions. */ QString longString() const; /** * Compares the plain medium ignoring the cddb information which can differ * based on the cddb settings. */ bool sameMedium( const Medium& other ) const; bool operator==( const Medium& other ) const; bool operator!=( const Medium& other ) const; + /** + * Constructs a user readable string which can be used to request certain media. + */ + static QString mediaRequestString( Device::MediaTypes requestedMediaTypes, Device::MediaStates requestedMediaStates, const K3b::Msf& requestedSize ); + private: void analyseContent(); QSharedDataPointer d; friend class MediaCache; }; } Q_DECLARE_OPERATORS_FOR_FLAGS( K3b::Medium::MediumContents ) #endif diff --git a/libk3b/tools/k3bpipe.cpp b/libk3b/tools/k3bpipe.cpp deleted file mode 100644 index 94f3cad99..000000000 --- a/libk3b/tools/k3bpipe.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * - * Copyright (C) 2006-2008 Sebastian Trueg - * - * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg - * - * 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 "k3bpipe.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - - -K3b::Pipe::Pipe() -{ - m_fd[0] = m_fd[1] = -1; -} - - -K3b::Pipe::~Pipe() -{ - close(); -} - - -bool K3b::Pipe::open() -{ - close(); - - if( ::socketpair( AF_UNIX, SOCK_STREAM, 0, m_fd ) == 0 ) { - fcntl( m_fd[0], F_SETFD, FD_CLOEXEC ); - fcntl( m_fd[1], F_SETFD, FD_CLOEXEC ); - return true; - } - else { - kDebug() << "(K3b::Pipe) failed to setup socket pair."; - return false; - } -} - - -void K3b::Pipe::closeIn() -{ - if( m_fd[1] != -1 ) { - ::close( m_fd[1] ); - m_fd[1] = -1; - } -} - - -void K3b::Pipe::closeOut() -{ - if( m_fd[0] != -1 ) { - ::close( m_fd[0] ); - m_fd[0] = -1; - } -} - - -void K3b::Pipe::close() -{ - closeIn(); - closeOut(); -} diff --git a/libk3b/tools/k3bpipe.h b/libk3b/tools/k3bpipe.h deleted file mode 100644 index 19aa5d90f..000000000 --- a/libk3b/tools/k3bpipe.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * - * Copyright (C) 2006-2008 Sebastian Trueg - * - * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg - * - * 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. - */ - -#ifndef _K3B_PIPE_H_ -#define _K3B_PIPE_H_ - -#include "k3b_export.h" - -namespace K3b { - /** - * The Pipe class represents a file descriptor pair - * which can for example be used to connect two processes - */ - class LIBK3B_EXPORT Pipe - { - public: - /** - * Creates a closed pipe object - */ - Pipe(); - - /** - * The destructor takes care of closing the pipe - */ - ~Pipe(); - - /** - * Open the pipe. This creates a file descriptor pair - * which can be accessed using the in() and out() - * methods. - */ - bool open(); - - int in() const { return m_fd[1]; } - int out() const { return m_fd[0]; } - - /** - * Calls both closeIn() and closeOut() - */ - void close(); - - void closeIn(); - void closeOut(); - - private: - int m_fd[2]; - }; -} - -#endif diff --git a/libk3b/tools/qprocess/README b/libk3b/tools/qprocess/README new file mode 100644 index 000000000..1ba0fa8eb --- /dev/null +++ b/libk3b/tools/qprocess/README @@ -0,0 +1,9 @@ +This is a fork of QProcess (KProcess needs to tag along without any changes since K3b::Process is derived from that) +from Qt 4.5 which adds the following changes: + +- New method QProcess::setFlags with flags RawStdin and RawStdout +- If RawStdin/RawStdout is set the ringbuffer is not used for stdin/stdout. Calls to QProcess::readData/writeData are directly + done on the process pipe (only unix has been patched so far) + Also the pipe is blocking, i.e. fcntl( .. O_NONBLOCK ) is not called + The latter is very important since K3b does its piping in a separate thread and non-blocking pipes make that + near to impossible. diff --git a/libk3b/tools/qprocess/k3bkprocess.cpp b/libk3b/tools/qprocess/k3bkprocess.cpp new file mode 100644 index 000000000..ba3f3b0dd --- /dev/null +++ b/libk3b/tools/qprocess/k3bkprocess.cpp @@ -0,0 +1,382 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "k3bkprocess_p.h" + +#include +#include +#ifdef Q_OS_WIN +# include +#endif + +#include + +#ifdef Q_OS_WIN +# include +#else +# include +# include +#endif + +#ifndef Q_OS_WIN +# define STD_OUTPUT_HANDLE 1 +# define STD_ERROR_HANDLE 2 +#endif + +void K3bKProcessPrivate::writeAll(const QByteArray &buf, int fd) +{ +#ifdef Q_OS_WIN + HANDLE h = GetStdHandle(fd); + if (h) { + DWORD wr; + WriteFile(h, buf.data(), buf.size(), &wr, 0); + } +#else + int off = 0; + do { + int ret = ::write(fd, buf.data() + off, buf.size() - off); + if (ret < 0) { + if (errno != EINTR) + return; + } else { + off += ret; + } + } while (off < buf.size()); +#endif +} + +void K3bKProcessPrivate::forwardStd(::QProcess::ProcessChannel good, int fd) +{ + Q_Q(K3bKProcess); + + QProcess::ProcessChannel oc = q->readChannel(); + q->setReadChannel(good); + writeAll(q->readAll(), fd); + q->setReadChannel(oc); +} + +void K3bKProcessPrivate::_k_forwardStdout() +{ + forwardStd(QProcess::StandardOutput, STD_OUTPUT_HANDLE); +} + +void K3bKProcessPrivate::_k_forwardStderr() +{ + forwardStd(QProcess::StandardError, STD_ERROR_HANDLE); +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +K3bKProcess::K3bKProcess(QObject *parent) : + K3bQProcess(parent), + d_ptr(new K3bKProcessPrivate) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(KProcess::ForwardedChannels); +} + +K3bKProcess::K3bKProcess(K3bKProcessPrivate *d, QObject *parent) : + K3bQProcess(parent), + d_ptr(d) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(KProcess::ForwardedChannels); +} + +K3bKProcess::~K3bKProcess() +{ + delete d_ptr; +} + +void K3bKProcess::setOutputChannelMode(KProcess::OutputChannelMode mode) +{ + Q_D(K3bKProcess); + + d->outputChannelMode = mode; + disconnect(this, SIGNAL(readyReadStandardOutput())); + disconnect(this, SIGNAL(readyReadStandardError())); + switch (mode) { + case KProcess::OnlyStdoutChannel: + connect(this, SIGNAL(readyReadStandardError()), SLOT(_k_forwardStderr())); + break; + case KProcess::OnlyStderrChannel: + connect(this, SIGNAL(readyReadStandardOutput()), SLOT(_k_forwardStdout())); + break; + default: + K3bQProcess::setProcessChannelMode((QProcess::ProcessChannelMode)mode); + return; + } + K3bQProcess::setProcessChannelMode(QProcess::SeparateChannels); +} + +KProcess::OutputChannelMode K3bKProcess::outputChannelMode() const +{ + Q_D(const K3bKProcess); + + return d->outputChannelMode; +} + +void K3bKProcess::setNextOpenMode(QIODevice::OpenMode mode) +{ + Q_D(K3bKProcess); + + d->openMode = mode; +} + +#define DUMMYENV "_KPROCESS_DUMMY_=" + +void K3bKProcess::clearEnvironment() +{ + setEnvironment(QStringList() << QString::fromLatin1(DUMMYENV)); +} + +void K3bKProcess::setEnv(const QString &name, const QString &value, bool overwrite) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append('='); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + if (overwrite) { + *it = fname.append(value); + setEnvironment(env); + } + return; + } + env.append(fname.append(value)); + setEnvironment(env); +} + +void K3bKProcess::unsetEnv(const QString &name) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append('='); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + env.erase(it); + if (env.isEmpty()) + env.append(DUMMYENV); + setEnvironment(env); + return; + } +} + +void K3bKProcess::setProgram(const QString &exe, const QStringList &args) +{ + Q_D(K3bKProcess); + + d->prog = exe; + d->args = args; +} + +void K3bKProcess::setProgram(const QStringList &argv) +{ + Q_D(K3bKProcess); + + Q_ASSERT( !argv.isEmpty() ); + d->args = argv; + d->prog = d->args.takeFirst(); +} + +K3bKProcess &K3bKProcess::operator<<(const QString &arg) +{ + Q_D(K3bKProcess); + + if (d->prog.isEmpty()) + d->prog = arg; + else + d->args << arg; + return *this; +} + +K3bKProcess &K3bKProcess::operator<<(const QStringList &args) +{ + Q_D(K3bKProcess); + + if (d->prog.isEmpty()) + setProgram(args); + else + d->args << args; + return *this; +} + +void K3bKProcess::clearProgram() +{ + Q_D(K3bKProcess); + + d->prog.clear(); + d->args.clear(); +} + +void K3bKProcess::setShellCommand(const QString &cmd) +{ + Q_D(K3bKProcess); + + KShell::Errors err; + d->args = KShell::splitArgs( + cmd, KShell::AbortOnMeta | KShell::TildeExpand, &err); + if (err == KShell::NoError && !d->args.isEmpty()) { + d->prog = KStandardDirs::findExe(d->args[0]); + if (!d->prog.isEmpty()) { + d->args.removeFirst(); + return; + } + } + + d->args.clear(); + +#ifdef Q_OS_UNIX +// #ifdef NON_FREE // ... as they ship non-POSIX /bin/sh +# if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__) + // If /bin/sh is a symlink, we can be pretty sure that it points to a + // POSIX shell - the original bourne shell is about the only non-POSIX + // shell still in use and it is always installed natively as /bin/sh. + d->prog = QFile::symLinkTarget(QString::fromLatin1("/bin/sh")); + if (d->prog.isEmpty()) { + // Try some known POSIX shells. + d->prog = KStandardDirs::findExe("ksh"); + if (d->prog.isEmpty()) { + d->prog = KStandardDirs::findExe("ash"); + if (d->prog.isEmpty()) { + d->prog = KStandardDirs::findExe("bash"); + if (d->prog.isEmpty()) { + d->prog = KStandardDirs::findExe("zsh"); + if (d->prog.isEmpty()) + // We're pretty much screwed, to be honest ... + d->prog = QString::fromLatin1("/bin/sh"); + } + } + } + } +# else + d->prog = QString::fromLatin1("/bin/sh"); +# endif + + d->args << "-c" << cmd; +#else // Q_OS_UNIX + // KMacroExpander::expandMacrosShellQuote(), KShell::quoteArg() and + // KShell::joinArgs() may generate these for security reasons. + setEnv(PERCENT_VARIABLE, "%"); + + //see also TrollTechTaskTracker entry 88373. + d->prog = KStandardDirs::findExe("kcmdwrapper"); + + UINT size; + WCHAR sysdir[MAX_PATH + 1]; + size = GetSystemDirectoryW(sysdir, MAX_PATH + 1); + QString cmdexe = QString::fromUtf16((const ushort *) sysdir, size); + cmdexe.append("\\cmd.exe"); + + d->args << cmdexe << cmd; +#endif +} + +QStringList K3bKProcess::program() const +{ + Q_D(const K3bKProcess); + + QStringList argv = d->args; + argv.prepend(d->prog); + return argv; +} + +void K3bKProcess::start() +{ + Q_D(K3bKProcess); + + K3bQProcess::start(d->prog, d->args, d->openMode); +} + +int K3bKProcess::execute(int msecs) +{ + start(); + if (!waitForFinished(msecs)) { + kill(); + waitForFinished(-1); + return -2; + } + return (exitStatus() == QProcess::NormalExit) ? exitCode() : -1; +} + +// static +int K3bKProcess::execute(const QString &exe, const QStringList &args, int msecs) +{ + K3bKProcess p; + p.setProgram(exe, args); + return p.execute(msecs); +} + +// static +int K3bKProcess::execute(const QStringList &argv, int msecs) +{ + K3bKProcess p; + p.setProgram(argv); + return p.execute(msecs); +} + +int K3bKProcess::startDetached() +{ + Q_D(K3bKProcess); + + qint64 pid; + if (!K3bQProcess::startDetached(d->prog, d->args, workingDirectory(), &pid)) + return 0; + return (int) pid; +} + +// static +int K3bKProcess::startDetached(const QString &exe, const QStringList &args) +{ + qint64 pid; + if (!K3bQProcess::startDetached(exe, args, QString(), &pid)) + return 0; + return (int) pid; +} + +// static +int K3bKProcess::startDetached(const QStringList &argv) +{ + QStringList args = argv; + QString prog = args.takeFirst(); + return startDetached(prog, args); +} + +int K3bKProcess::pid() const +{ +#ifdef Q_OS_UNIX + return (int) K3bQProcess::pid(); +#else + return K3bQProcess::pid() ? K3bQProcess::pid()->dwProcessId : 0; +#endif +} + +#include "k3bkprocess.moc" diff --git a/libk3b/tools/qprocess/k3bkprocess.h b/libk3b/tools/qprocess/k3bkprocess.h new file mode 100644 index 000000000..306bdff20 --- /dev/null +++ b/libk3b/tools/qprocess/k3bkprocess.h @@ -0,0 +1,345 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef K3B_KPROCESS_H +#define K3B_KPROCESS_H + +#include + +#include "k3bqprocess.h" +#include "k3b_export.h" +#include + +class K3bKProcessPrivate; + +/** + * \class K3bKProcess kprocess.h + * + * Child process invocation, monitoring and control. + * + * This class extends QProcess by some useful functionality, overrides + * some defaults with saner values and wraps parts of the API into a more + * accessible one. + * This is the preferred way of spawning child processes in KDE; don't + * use QProcess directly. + * + * @author Oswald Buddenhagen + **/ +class LIBK3B_EXPORT K3bKProcess : public K3bQProcess +{ + Q_OBJECT + Q_DECLARE_PRIVATE(K3bKProcess) + +public: + // we reuse the KProcess enum to make future transition easier +#if 0 + /** + * Modes in which the output channels can be opened. + */ + enum OutputChannelMode { + SeparateChannels = ::QProcess::SeparateChannels, + /**< Standard output and standard error are handled by K3bKProcess + as separate channels */ + MergedChannels = ::QProcess::MergedChannels, + /**< Standard output and standard error are handled by K3bKProcess + as one channel */ + ForwardedChannels = ::QProcess::ForwardedChannels, + /**< Both standard output and standard error are forwarded + to the parent process' respective channel */ + OnlyStdoutChannel, + /**< Only standard output is handled; standard error is forwarded */ + OnlyStderrChannel /**< Only standard error is handled; standard output is forwarded */ + }; +#endif + + /** + * Constructor + */ + explicit K3bKProcess(QObject *parent = 0); + + /** + * Destructor + */ + virtual ~K3bKProcess(); + + /** + * Set how to handle the output channels of the child process. + * + * The default is ForwardedChannels, which is unlike in QProcess. + * Do not request more than you actually handle, as this output is + * simply lost otherwise. + * + * This function must be called before starting the process. + * + * @param mode the output channel handling mode + */ + void setOutputChannelMode(KProcess::OutputChannelMode mode); + + /** + * Query how the output channels of the child process are handled. + * + * @return the output channel handling mode + */ + KProcess::OutputChannelMode outputChannelMode() const; + + /** + * Set the QIODevice open mode the process will be opened in. + * + * This function must be called before starting the process, obviously. + * + * @param mode the open mode. Note that this mode is automatically + * "reduced" according to the channel modes and redirections. + * The default is QIODevice::ReadWrite. + */ + void setNextOpenMode(QIODevice::OpenMode mode); + + /** + * Adds the variable @p name to the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + * @param value the new value for the environment variable + * @param overwrite if @c false and the environment variable is already + * set, the old value will be preserved + */ + void setEnv(const QString &name, const QString &value, bool overwrite = true); + + /** + * Removes the variable @p name from the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + */ + void unsetEnv(const QString &name); + + /** + * Empties the process' environment. + * + * Note that LD_LIBRARY_PATH/DYLD_LIBRARY_PATH is automatically added + * on *NIX. + * + * This function must be called before starting the process. + */ + void clearEnvironment(); + + /** + * Set the program and the command line arguments. + * + * This function must be called before starting the process, obviously. + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + */ + void setProgram(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + */ + void setProgram(const QStringList &argv); + + /** + * Append an element to the command line argument list for this process. + * + * If no executable is set yet, it will be set instead. + * + * For example, doing an "ls -l /usr/local/bin" can be achieved by: + * \code + * K3bKProcess p; + * p << "ls" << "-l" << "/usr/local/bin"; + * ... + * \endcode + * + * This function must be called before starting the process, obviously. + * + * @param arg the argument to add + * @return a reference to this K3bKProcess + */ + K3bKProcess &operator<<(const QString& arg); + + /** + * @overload + * + * @param args the arguments to add + * @return a reference to this K3bKProcess + */ + K3bKProcess &operator<<(const QStringList& args); + + /** + * Clear the program and command line argument list. + */ + void clearProgram(); + + /** + * Set a command to execute through a shell (a POSIX sh on *NIX + * and cmd.exe on Windows). + * + * Using this for anything but user-supplied commands is usually a bad + * idea, as the command's syntax depends on the platform. + * Redirections including pipes, etc. are better handled by the + * respective functions provided by QProcess. + * + * If K3bKProcess determines that the command does not really need a + * shell, it will trasparently execute it without one for performance + * reasons. + * + * This function must be called before starting the process, obviously. + * + * @param cmd the command to execute through a shell. + * The caller must make sure that all filenames etc. are properly + * quoted when passed as argument. Failure to do so often results in + * serious security holes. See KShell::quoteArg(). + */ + void setShellCommand(const QString &cmd); + + /** + * Obtain the currently set program and arguments. + * + * @return a list, the first element being the program, the remaining ones + * being command line arguments to the program. + */ + QStringList program() const; + + /** + * Start the process. + * + * @see QProcess::start(const QString &, const QStringList &, OpenMode) + */ + void start(); + + /** + * Start the process, wait for it to finish, and return the exit code. + * + * This method is roughly equivalent to the sequence: + * + * start(); + * waitForFinished(msecs); + * return exitCode(); + * + * + * Unlike the other execute() variants this method is not static, + * so the process can be parametrized properly and talked to. + * + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + int execute(int msecs = -1); + + /** + * @overload + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QString &exe, const QStringList &args = QStringList(), int msecs = -1); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QStringList &argv, int msecs = -1); + + /** + * Start the process and detach from it. See QProcess::startDetached() + * for details. + * + * Unlike the other startDetached() variants this method is not static, + * so the process can be parametrized properly. + * @note Currently, only the setProgram()/setShellCommand() and + * setWorkingDirectory() parametrizations are supported. + * + * The K3bKProcess object may be re-used immediately after calling this + * function. + * + * @return the PID of the started process or 0 on error + */ + int startDetached(); + + /** + * @overload + * + * @param exe the program to start + * @param args the command line arguments for the program, + * one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to start and the command line arguments + * for the program, one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QStringList &argv); + + /** + * Obtain the process' ID as known to the system. + * + * Unlike with QProcess::pid(), this is a real PID also on Windows. + * + * This function can be called only while the process is running. + * It cannot be applied to detached processes. + * + * @return the process ID + */ + int pid() const; + +protected: + /** + * @internal + */ + K3bKProcess(K3bKProcessPrivate *d, QObject *parent); + + /** + * @internal + */ + K3bKProcessPrivate * const d_ptr; + +private: + // hide those + using K3bQProcess::setReadChannelMode; + using K3bQProcess::readChannelMode; + using K3bQProcess::setProcessChannelMode; + using K3bQProcess::processChannelMode; + + Q_PRIVATE_SLOT(d_func(), void _k_forwardStdout()) + Q_PRIVATE_SLOT(d_func(), void _k_forwardStderr()) +}; + +#endif + diff --git a/libk3b/tools/qprocess/k3bkprocess_p.h b/libk3b/tools/qprocess/k3bkprocess_p.h new file mode 100644 index 000000000..45c023923 --- /dev/null +++ b/libk3b/tools/qprocess/k3bkprocess_p.h @@ -0,0 +1,47 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPROCESS_P_H +#define KPROCESS_P_H + +#include "k3bkprocess.h" + +class K3bKProcessPrivate { + Q_DECLARE_PUBLIC(K3bKProcess) +protected: + K3bKProcessPrivate() : + openMode(QIODevice::ReadWrite) + { + } + void writeAll(const QByteArray &buf, int fd); + void forwardStd(::QProcess::ProcessChannel good, int fd); + void _k_forwardStdout(); + void _k_forwardStderr(); + + QString prog; + QStringList args; + KProcess::OutputChannelMode outputChannelMode; + QIODevice::OpenMode openMode; + + K3bKProcess *q_ptr; +}; + +#endif diff --git a/libk3b/tools/qprocess/k3bqprocess.cpp b/libk3b/tools/qprocess/k3bqprocess.cpp new file mode 100644 index 000000000..c28b50d21 --- /dev/null +++ b/libk3b/tools/qprocess/k3bqprocess.cpp @@ -0,0 +1,1885 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QPROCESS_DEBUG + +#if defined QPROCESS_DEBUG +#include +#include +#include +#if !defined(Q_OS_WINCE) +#include +#endif + +//QT_BEGIN_NAMESPACE +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len && i < maxSize; ++i) { + char c = data[i]; + if (isprint(c)) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + char buf[5]; + qsnprintf(buf, sizeof(buf), "\\%3o", c); + buf[4] = '\0'; + out += QByteArray(buf); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} + +//QT_END_NAMESPACE + +#endif + +#include "k3bqprocess.h" +#include "k3bqprocess_p.h" + +#include +#include +#include +#include +#include + +#ifdef Q_WS_WIN +#include +#endif + +#ifndef QT_NO_PROCESS + +//QT_BEGIN_NAMESPACE + +void K3bQProcessPrivate::Channel::clear() +{ + switch (type) { + case PipeSource: + Q_ASSERT(process); + process->stdinChannel.type = Normal; + process->stdinChannel.process = 0; + break; + case PipeSink: + Q_ASSERT(process); + process->stdoutChannel.type = Normal; + process->stdoutChannel.process = 0; + break; + } + + type = Normal; + file.clear(); + process = 0; +} + +/*! + \class QProcess + + \brief The QProcess class is used to start external programs and + to communicate with them. + + \ingroup io + \ingroup misc + \mainclass + \reentrant + + To start a process, pass the name and command line arguments of + the program you want to run as arguments to start(). For example: + + \snippet doc/src/snippets/qprocess/qprocess-simpleexecution.cpp 0 + \dots + \snippet doc/src/snippets/qprocess/qprocess-simpleexecution.cpp 1 + \snippet doc/src/snippets/qprocess/qprocess-simpleexecution.cpp 2 + + QProcess then enters the \l Starting state, and when the program + has started, QProcess enters the \l Running state and emits + started(). + + QProcess allows you to treat a process as a sequential I/O + device. You can write to and read from the process just as you + would access a network connection using QTcpSocket. You can then + write to the process's standard input by calling write(), and + read the standard output by calling read(), readLine(), and + getChar(). Because it inherits QIODevice, QProcess can also be + used as an input source for QXmlReader, or for generating data to + be uploaded using QFtp. + + \note On Windows CE, reading and writing to a process is not supported. + + When the process exits, QProcess reenters the \l NotRunning state + (the initial state), and emits finished(). + + The finished() signal provides the exit code and exit status of + the process as arguments, and you can also call exitCode() to + obtain the exit code of the last process that finished, and + exitStatus() to obtain its exit status. If an error occurs at + any point in time, QProcess will emit the error() signal. You + can also call error() to find the type of error that occurred + last, and state() to find the current process state. + + \section1 Communicating via Channels + + Processes have two predefined output channels: The standard + output channel (\c stdout) supplies regular console output, and + the standard error channel (\c stderr) usually supplies the + errors that are printed by the process. These channels represent + two separate streams of data. You can toggle between them by + calling setReadChannel(). QProcess emits readyRead() when data is + available on the current read channel. It also emits + readyReadStandardOutput() when new standard output data is + available, and when new standard error data is available, + readyReadStandardError() is emitted. Instead of calling read(), + readLine(), or getChar(), you can explicitly read all data from + either of the two channels by calling readAllStandardOutput() or + readAllStandardError(). + + The terminology for the channels can be misleading. Be aware that + the process's output channels correspond to QProcess's + \e read channels, whereas the process's input channels correspond + to QProcess's \e write channels. This is because what we read + using QProcess is the process's output, and what we write becomes + the process's input. + + QProcess can merge the two output channels, so that standard + output and standard error data from the running process both use + the standard output channel. Call setProcessChannelMode() with + MergedChannels before starting the process to activative + this feature. You also have the option of forwarding the output of + the running process to the calling, main process, by passing + ForwardedChannels as the argument. + + Certain processes need special environment settings in order to + operate. You can set environment variables for your process by + calling setEnvironment(). To set a working directory, call + setWorkingDirectory(). By default, processes are run in the + current working directory of the calling process. + + \section1 Synchronous Process API + + QProcess provides a set of functions which allow it to be used + without an event loop, by suspending the calling thread until + certain signals are emitted: + + \list + \o waitForStarted() blocks until the process has started. + + \o waitForReadyRead() blocks until new data is + available for reading on the current read channel. + + \o waitForBytesWritten() blocks until one payload of + data has been written to the process. + + \o waitForFinished() blocks until the process has finished. + \endlist + + Calling these functions from the main thread (the thread that + calls QApplication::exec()) may cause your user interface to + freeze. + + The following example runs \c gzip to compress the string "Qt + rocks!", without an event loop: + + \snippet doc/src/snippets/process/process.cpp 0 + + \section1 Notes for Windows Users + + Some Windows commands (for example, \c dir) are not provided by + separate applications, but by the command interpreter itself. + If you attempt to use QProcess to execute these commands directly, + it won't work. One possible solution is to execute the command + interpreter itself (\c{cmd.exe} on some Windows systems), and ask + the interpreter to execute the desired command. + + \sa QBuffer, QFile, QTcpSocket +*/ + +/*! + \enum QProcess::ProcessChannel + + This enum describes the process channels used by the running process. + Pass one of these values to setReadChannel() to set the + current read channel of QProcess. + + \value StandardOutput The standard output (stdout) of the running + process. + + \value StandardError The standard error (stderr) of the running + process. + + \sa setReadChannel() +*/ + +/*! + \enum QProcess::ProcessChannelMode + + This enum describes the process channel modes of QProcess. Pass + one of these values to setProcessChannelMode() to set the + current read channel mode. + + \value SeparateChannels QProcess manages the output of the + running process, keeping standard output and standard error data + in separate internal buffers. You can select the QProcess's + current read channel by calling setReadChannel(). This is the + default channel mode of QProcess. + + \value MergedChannels QProcess merges the output of the running + process into the standard output channel (\c stdout). The + standard error channel (\c stderr) will not receive any data. The + standard output and standard error data of the running process + are interleaved. + + \value ForwardedChannels QProcess forwards the output of the + running process onto the main process. Anything the child process + writes to its standard output and standard error will be written + to the standard output and standard error of the main process. + + \sa setReadChannelMode() +*/ + +/*! + \enum QProcess::ProcessError + + This enum describes the different types of errors that are + reported by QProcess. + + \value FailedToStart The process failed to start. Either the + invoked program is missing, or you may have insufficient + permissions to invoke the program. + + \value Crashed The process crashed some time after starting + successfully. + + \value Timedout The last waitFor...() function timed out. The + state of QProcess is unchanged, and you can try calling + waitFor...() again. + + \value WriteError An error occurred when attempting to write to the + process. For example, the process may not be running, or it may + have closed its input channel. + + \value ReadError An error occurred when attempting to read from + the process. For example, the process may not be running. + + \value UnknownError An unknown error occurred. This is the default + return value of error(). + + \sa error() +*/ + +/*! + \enum QProcess::ProcessState + + This enum describes the different states of QProcess. + + \value NotRunning The process is not running. + + \value Starting The process is starting, but the program has not + yet been invoked. + + \value Running The process is running and is ready for reading and + writing. + + \sa state() +*/ + +/*! + \enum QProcess::ExitStatus + + This enum describes the different exit statuses of QProcess. + + \value NormalExit The process exited normally. + + \value CrashExit The process crashed. + + \sa exitStatus() +*/ + +/*! + \fn void QProcess::error(QProcess::ProcessError error) + + This signal is emitted when an error occurs with the process. The + specified \a error describes the type of error that occurred. +*/ + +/*! + \fn void QProcess::started() + + This signal is emitted by QProcess when the process has started, + and state() returns \l Running. +*/ + +/*! + \fn void QProcess::stateChanged(QProcess::ProcessState newState) + + This signal is emitted whenever the state of QProcess changes. The + \a newState argument is the state QProcess changed to. +*/ + +/*! + \fn void QProcess::finished(int exitCode) + \obsolete + \overload + + Use finished(int exitCode, QProcess::ExitStatus status) instead. +*/ + +/*! + \fn void QProcess::finished(int exitCode, QProcess::ExitStatus exitStatus) + + This signal is emitted when the process finishes. \a exitCode is the exit + code of the process, and \a exitStatus is the exit status. After the + process has finished, the buffers in QProcess are still intact. You can + still read any data that the process may have written before it finished. + + \sa exitStatus() +*/ + +/*! + \fn void QProcess::readyReadStandardOutput() + + This signal is emitted when the process has made new data + available through its standard output channel (\c stdout). It is + emitted regardless of the current \l{readChannel()}{read channel}. + + \sa readAllStandardOutput(), readChannel() +*/ + +/*! + \fn void QProcess::readyReadStandardError() + + This signal is emitted when the process has made new data + available through its standard error channel (\c stderr). It is + emitted regardless of the current \l{readChannel()}{read + channel}. + + \sa readAllStandardError(), readChannel() +*/ + +/*! \internal +*/ +K3bQProcessPrivate::K3bQProcessPrivate() +{ + processChannel = ::QProcess::StandardOutput; + processChannelMode = ::QProcess::SeparateChannels; + processError = ::QProcess::UnknownError; + processState = ::QProcess::NotRunning; + pid = 0; + sequenceNumber = 0; + exitCode = 0; + exitStatus = ::QProcess::NormalExit; + startupSocketNotifier = 0; + deathNotifier = 0; + notifier = 0; + pipeWriter = 0; + childStartedPipe[0] = INVALID_Q_PIPE; + childStartedPipe[1] = INVALID_Q_PIPE; + deathPipe[0] = INVALID_Q_PIPE; + deathPipe[1] = INVALID_Q_PIPE; + exitCode = 0; + crashed = false; + dying = false; + emittedReadyRead = false; + emittedBytesWritten = false; +#ifdef Q_WS_WIN + pipeWriter = 0; + processFinishedNotifier = 0; +#endif // Q_WS_WIN +#ifdef Q_OS_UNIX + serial = 0; +#endif +} + +/*! \internal +*/ +K3bQProcessPrivate::~K3bQProcessPrivate() +{ + if (stdinChannel.process) + stdinChannel.process->stdoutChannel.clear(); + if (stdoutChannel.process) + stdoutChannel.process->stdinChannel.clear(); +} + +/*! \internal +*/ +void K3bQProcessPrivate::cleanup() +{ + q_func()->setProcessState(::QProcess::NotRunning); +#ifdef Q_OS_WIN + if (pid) { + CloseHandle(pid->hThread); + CloseHandle(pid->hProcess); + delete pid; + pid = 0; + } + if (processFinishedNotifier) { + processFinishedNotifier->setEnabled(false); + qDeleteInEventHandler(processFinishedNotifier); + processFinishedNotifier = 0; + } + +#endif + pid = 0; + sequenceNumber = 0; + dying = false; + + if (stdoutChannel.notifier) { + stdoutChannel.notifier->setEnabled(false); + delete stdoutChannel.notifier; + stdoutChannel.notifier = 0; + } + if (stderrChannel.notifier) { + stderrChannel.notifier->setEnabled(false); + delete stderrChannel.notifier; + stderrChannel.notifier = 0; + } + if (stdinChannel.notifier) { + stdinChannel.notifier->setEnabled(false); + delete stdinChannel.notifier; + stdinChannel.notifier = 0; + } + if (startupSocketNotifier) { + startupSocketNotifier->setEnabled(false); + delete startupSocketNotifier; + startupSocketNotifier = 0; + } + if (deathNotifier) { + deathNotifier->setEnabled(false); + delete deathNotifier; + deathNotifier = 0; + } + if (notifier) { + delete notifier; + notifier = 0; + } + destroyPipe(stdoutChannel.pipe); + destroyPipe(stderrChannel.pipe); + destroyPipe(stdinChannel.pipe); + destroyPipe(childStartedPipe); + destroyPipe(deathPipe); +#ifdef Q_OS_UNIX + serial = 0; +#endif +} + +/*! \internal +*/ +bool K3bQProcessPrivate::_q_canReadStandardOutput() +{ + Q_Q(K3bQProcess); + qint64 available = bytesAvailableFromStdout(); + if (available == 0) { + if (stdoutChannel.notifier) + stdoutChannel.notifier->setEnabled(false); + destroyPipe(stdoutChannel.pipe); +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::canReadStandardOutput(), 0 bytes available"); +#endif + return false; + } + + if (!(processFlags & K3bQProcess::RawStdout)) { + char *ptr = outputReadBuffer.reserve(available); + qint64 readBytes = readFromStdout(ptr, available); + if (readBytes == -1) { + processError = ::QProcess::ReadError; + q->setErrorString(K3bQProcess::tr("Error reading from process")); + emit q->error(processError); +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::canReadStandardOutput(), failed to read from the process"); +#endif + return false; + } +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::canReadStandardOutput(), read %d bytes from the process' output", + int(readBytes)); +#endif + + if (stdoutChannel.closed) { + outputReadBuffer.chop(readBytes); + return false; + } + + outputReadBuffer.chop(available - readBytes); + + bool didRead = false; + if (readBytes == 0) { + if (stdoutChannel.notifier) + stdoutChannel.notifier->setEnabled(false); + } else if (processChannel == ::QProcess::StandardOutput) { + didRead = true; + if (!emittedReadyRead) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + } + emit q->readyReadStandardOutput(); + return didRead; + } + else { + if (!emittedReadyRead) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + emit q->readyReadStandardOutput(); + return true; + } +} + +/*! \internal +*/ +bool K3bQProcessPrivate::_q_canReadStandardError() +{ + Q_Q(K3bQProcess); + qint64 available = bytesAvailableFromStderr(); + if (available == 0) { + if (stderrChannel.notifier) + stderrChannel.notifier->setEnabled(false); + destroyPipe(stderrChannel.pipe); + return false; + } + + char *ptr = errorReadBuffer.reserve(available); + qint64 readBytes = readFromStderr(ptr, available); + if (readBytes == -1) { + processError = ::QProcess::ReadError; + q->setErrorString(K3bQProcess::tr("Error reading from process")); + emit q->error(processError); + return false; + } + if (stderrChannel.closed) { + errorReadBuffer.chop(readBytes); + return false; + } + + errorReadBuffer.chop(available - readBytes); + + bool didRead = false; + if (readBytes == 0) { + if (stderrChannel.notifier) + stderrChannel.notifier->setEnabled(false); + } else if (processChannel == ::QProcess::StandardError) { + didRead = true; + if (!emittedReadyRead) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + } + emit q->readyReadStandardError(); + return didRead; +} + +/*! \internal +*/ +bool K3bQProcessPrivate::_q_canWrite() +{ + Q_Q(K3bQProcess); + if (processFlags & K3bQProcess::RawStdin) { + if (stdinChannel.notifier) + stdinChannel.notifier->setEnabled(false); + isReadyWrite = true; + emit q->readyWrite(); + } + else { + if (stdinChannel.notifier) + stdinChannel.notifier->setEnabled(false); + + if (writeBuffer.isEmpty()) { +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::canWrite(), not writing anything (empty write buffer)."); +#endif + return false; + } + + qint64 written = writeToStdin(writeBuffer.readPointer(), + writeBuffer.nextDataBlockSize()); + if (written < 0) { + destroyPipe(stdinChannel.pipe); + processError = ::QProcess::WriteError; + q->setErrorString(K3bQProcess::tr("Error writing to process")); +#if defined(QPROCESS_DEBUG) && !defined(Q_OS_WINCE) + qDebug("K3bQProcessPrivate::canWrite(), failed to write (%s)", strerror(errno)); +#endif + emit q->error(processError); + return false; + } + +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::canWrite(), wrote %d bytes to the process input", int(written)); +#endif + + writeBuffer.free(written); + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(written); + emittedBytesWritten = false; + } + if (stdinChannel.notifier && !writeBuffer.isEmpty()) + stdinChannel.notifier->setEnabled(true); + if (writeBuffer.isEmpty() && stdinChannel.closed) + closeWriteChannel(); + } + return true; +} + +/*! \internal +*/ +bool K3bQProcessPrivate::_q_processDied() +{ + Q_Q(K3bQProcess); +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::_q_processDied()"); +#endif +#ifdef Q_OS_UNIX + if (!waitForDeadChild()) + return false; +#endif +#ifdef Q_OS_WIN + if (processFinishedNotifier) + processFinishedNotifier->setEnabled(false); +#endif + + // the process may have died before it got a chance to report that it was + // either running or stopped, so we will call _q_startupNotification() and + // give it a chance to emit started() or error(FailedToStart). + if (processState == ::QProcess::Starting) { + if (!_q_startupNotification()) + return true; + } + + if (dying) { + // at this point we know the process is dead. prevent + // reentering this slot recursively by calling waitForFinished() + // or opening a dialog inside slots connected to the readyRead + // signals emitted below. + return true; + } + dying = true; + + // in case there is data in the pipe line and this slot by chance + // got called before the read notifications, call these two slots + // so the data is made available before the process dies. + _q_canReadStandardOutput(); + _q_canReadStandardError(); + + findExitCode(); + + if (crashed) { + exitStatus = ::QProcess::CrashExit; + processError = ::QProcess::Crashed; + q->setErrorString(K3bQProcess::tr("Process crashed")); + emit q->error(processError); + } + + bool wasRunning = (processState == ::QProcess::Running); + + cleanup(); + + if (wasRunning) { + // we received EOF now: + emit q->readChannelFinished(); + // in the future: + //emit q->standardOutputClosed(); + //emit q->standardErrorClosed(); + + emit q->finished(exitCode); + emit q->finished(exitCode, exitStatus); + } +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::_q_processDied() process is dead"); +#endif + return true; +} + +/*! \internal +*/ +bool K3bQProcessPrivate::_q_startupNotification() +{ + Q_Q(K3bQProcess); +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::startupNotification()"); +#endif + + if (startupSocketNotifier) + startupSocketNotifier->setEnabled(false); + if (processStarted()) { + q->setProcessState(::QProcess::Running); + emit q->started(); + return true; + } + + q->setProcessState(::QProcess::NotRunning); + processError = ::QProcess::FailedToStart; + emit q->error(processError); +#ifdef Q_OS_UNIX + // make sure the process manager removes this entry + waitForDeadChild(); + findExitCode(); +#endif + cleanup(); + return false; +} + +/*! \internal +*/ +void K3bQProcessPrivate::closeWriteChannel() +{ +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::closeWriteChannel()"); +#endif + if (stdinChannel.notifier) { + stdinChannel.notifier->setEnabled(false); + if (stdinChannel.notifier) { + delete stdinChannel.notifier; + stdinChannel.notifier = 0; + } + } +#ifdef Q_OS_WIN + // ### Find a better fix, feeding the process little by little + // instead. + flushPipeWriter(); +#endif + destroyPipe(stdinChannel.pipe); +} + +/*! + Constructs a QProcess object with the given \a parent. +*/ +K3bQProcess::K3bQProcess(QObject *parent) + : QIODevice(parent), + d_ptr( new K3bQProcessPrivate ) +{ + d_ptr->q_ptr = this; +//#if defined QPROCESS_DEBUG + qDebug("K3bQProcess::QProcess(%p)", parent); +//#endif +} + +/*! + Destructs the QProcess object, i.e., killing the process. + + Note that this function will not return until the process is + terminated. +*/ +K3bQProcess::~K3bQProcess() +{ + Q_D(K3bQProcess); + if (d->processState != ::QProcess::NotRunning) { + qWarning("QProcess: Destroyed while process is still running."); + kill(); + waitForFinished(); + } +#ifdef Q_OS_UNIX + // make sure the process manager removes this entry + d->findExitCode(); +#endif + d->cleanup(); + delete d; +} + +K3bQProcess::ProcessFlags K3bQProcess::flags() const +{ + Q_D(const K3bQProcess); + return d->processFlags; +} + +void K3bQProcess::setFlags( K3bQProcess::ProcessFlags flags ) +{ + Q_D(K3bQProcess); + d->processFlags = flags; +} + +/*! + \obsolete + Returns the read channel mode of the QProcess. This function is + equivalent to processChannelMode() + + \sa processChannelMode() +*/ +::QProcess::ProcessChannelMode K3bQProcess::readChannelMode() const +{ + return processChannelMode(); +} + +/*! + \obsolete + + Use setProcessChannelMode(\a mode) instead. + + \sa setProcessChannelMode() +*/ +void K3bQProcess::setReadChannelMode(::QProcess::ProcessChannelMode mode) +{ + setProcessChannelMode(mode); +} + +/*! + \since 4.2 + + Returns the channel mode of the QProcess standard output and + standard error channels. + + \sa setReadChannelMode(), ProcessChannelMode, setReadChannel() +*/ +::QProcess::ProcessChannelMode K3bQProcess::processChannelMode() const +{ + Q_D(const K3bQProcess); + return d->processChannelMode; +} + +/*! + \since 4.2 + + Sets the channel mode of the QProcess standard output and standard + error channels to the \a mode specified. + This mode will be used the next time start() is called. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 0 + + \sa readChannelMode(), ProcessChannelMode, setReadChannel() +*/ +void K3bQProcess::setProcessChannelMode(::QProcess::ProcessChannelMode mode) +{ + Q_D(K3bQProcess); + d->processChannelMode = mode; +} + +/*! + Returns the current read channel of the QProcess. + + \sa setReadChannel() +*/ +QProcess::ProcessChannel K3bQProcess::readChannel() const +{ + Q_D(const K3bQProcess); + return d->processChannel; +} + +/*! + Sets the current read channel of the QProcess to the given \a + channel. The current input channel is used by the functions + read(), readAll(), readLine(), and getChar(). It also determines + which channel triggers QProcess to emit readyRead(). + + \sa readChannel() +*/ +void K3bQProcess::setReadChannel(::QProcess::ProcessChannel channel) +{ + Q_D(K3bQProcess); +// if (d->processChannel != channel) { +// QByteArray buf = d->buffer.readAll(); +// if (d->processChannel == QProcess::StandardOutput) { +// for (int i = buf.size() - 1; i >= 0; --i) +// d->outputReadBuffer.ungetChar(buf.at(i)); +// } else { +// for (int i = buf.size() - 1; i >= 0; --i) +// d->errorReadBuffer.ungetChar(buf.at(i)); +// } +// } + d->processChannel = channel; +} + +/*! + Closes the read channel \a channel. After calling this function, + QProcess will no longer receive data on the channel. Any data that + has already been received is still available for reading. + + Call this function to save memory, if you are not interested in + the output of the process. + + \sa closeWriteChannel(), setReadChannel() +*/ +void K3bQProcess::closeReadChannel(::QProcess::ProcessChannel channel) +{ + Q_D(K3bQProcess); + + if (channel == ::QProcess::StandardOutput) + d->stdoutChannel.closed = true; + else + d->stderrChannel.closed = true; +} + +/*! + Schedules the write channel of QProcess to be closed. The channel + will close once all data has been written to the process. After + calling this function, any attempts to write to the process will + fail. + + Closing the write channel is necessary for programs that read + input data until the channel has been closed. For example, the + program "more" is used to display text data in a console on both + Unix and Windows. But it will not display the text data until + QProcess's write channel has been closed. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 1 + + The write channel is implicitly opened when start() is called. + + \sa closeReadChannel() +*/ +void K3bQProcess::closeWriteChannel() +{ + Q_D(K3bQProcess); + d->stdinChannel.closed = true; // closing + if (d->writeBuffer.isEmpty()) + d->closeWriteChannel(); +} + +/*! + \since 4.2 + + Redirects the process' standard input to the file indicated by \a + fileName. When an input redirection is in place, the QProcess + object will be in read-only mode (calling write() will result in + error). + + If the file \a fileName does not exist at the moment start() is + called or is not readable, starting the process will fail. + + Calling setStandardInputFile() after the process has started has no + effect. + + \sa setStandardOutputFile(), setStandardErrorFile(), + setStandardOutputProcess() +*/ +void K3bQProcess::setStandardInputFile(const QString &fileName) +{ + Q_D(K3bQProcess); + d->stdinChannel = fileName; +} + +/*! + \since 4.2 + + Redirects the process' standard output to the file \a + fileName. When the redirection is in place, the standard output + read channel is closed: reading from it using read() will always + fail, as will readAllStandardOutput(). + + If the file \a fileName doesn't exist at the moment start() is + called, it will be created. If it cannot be created, the starting + will fail. + + If the file exists and \a mode is QIODevice::Truncate, the file + will be truncated. Otherwise (if \a mode is QIODevice::Append), + the file will be appended to. + + Calling setStandardOutputFile() after the process has started has + no effect. + + \sa setStandardInputFile(), setStandardErrorFile(), + setStandardOutputProcess() +*/ +void K3bQProcess::setStandardOutputFile(const QString &fileName, OpenMode mode) +{ + Q_ASSERT(mode == Append || mode == Truncate); + Q_D(K3bQProcess); + + d->stdoutChannel = fileName; + d->stdoutChannel.append = mode == Append; +} + +/*! + \since 4.2 + + Redirects the process' standard error to the file \a + fileName. When the redirection is in place, the standard error + read channel is closed: reading from it using read() will always + fail, as will readAllStandardError(). The file will be appended to + if \a mode is Append, otherwise, it will be truncated. + + See setStandardOutputFile() for more information on how the file + is opened. + + Note: if setProcessChannelMode() was called with an argument of + QProcess::MergedChannels, this function has no effect. + + \sa setStandardInputFile(), setStandardOutputFile(), + setStandardOutputProcess() +*/ +void K3bQProcess::setStandardErrorFile(const QString &fileName, OpenMode mode) +{ + Q_ASSERT(mode == Append || mode == Truncate); + Q_D(K3bQProcess); + + d->stderrChannel = fileName; + d->stderrChannel.append = mode == Append; +} + +/*! + \since 4.2 + + Pipes the standard output stream of this process to the \a + destination process' standard input. + + The following shell command: + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 2 + + Can be accomplished with QProcesses with the following code: + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 3 +*/ +void K3bQProcess::setStandardOutputProcess(K3bQProcess *destination) +{ + K3bQProcessPrivate *dfrom = d_func(); + K3bQProcessPrivate *dto = destination->d_func(); + dfrom->stdoutChannel.pipeTo(dto); + dto->stdinChannel.pipeFrom(dfrom); +} + +/*! + If QProcess has been assigned a working directory, this function returns + the working directory that the QProcess will enter before the program has + started. Otherwise, (i.e., no directory has been assigned,) an empty + string is returned, and QProcess will use the application's current + working directory instead. + + \sa setWorkingDirectory() +*/ +QString K3bQProcess::workingDirectory() const +{ + Q_D(const K3bQProcess); + return d->workingDirectory; +} + +/*! + Sets the working directory to \a dir. QProcess will start the + process in this directory. The default behavior is to start the + process in the working directory of the calling process. + + \sa workingDirectory(), start() +*/ +void K3bQProcess::setWorkingDirectory(const QString &dir) +{ + Q_D(K3bQProcess); + d->workingDirectory = dir; +} + +/*! + Returns the native process identifier for the running process, if + available. If no process is currently running, 0 is returned. +*/ +Q_PID K3bQProcess::pid() const +{ + Q_D(const K3bQProcess); + return d->pid; +} + +/*! \reimp + + This function operates on the current read channel. + + \sa readChannel(), setReadChannel() +*/ +bool K3bQProcess::canReadLine() const +{ + Q_D(const K3bQProcess); + const QRingBuffer *readBuffer = (d->processChannel == ::QProcess::StandardError) + ? &d->errorReadBuffer + : &d->outputReadBuffer; + return readBuffer->canReadLine() || QIODevice::canReadLine(); +} + +/*! + Closes all communication with the process and kills it. After calling this + function, QProcess will no longer emit readyRead(), and data can no + longer be read or written. +*/ +void K3bQProcess::close() +{ + emit aboutToClose(); + while (waitForBytesWritten(-1)) + ; + kill(); + waitForFinished(-1); + QIODevice::close(); +} + +/*! \reimp + + Returns true if the process is not running, and no more data is available + for reading; otherwise returns false. +*/ +bool K3bQProcess::atEnd() const +{ + Q_D(const K3bQProcess); + const QRingBuffer *readBuffer = (d->processChannel == ::QProcess::StandardError) + ? &d->errorReadBuffer + : &d->outputReadBuffer; + return QIODevice::atEnd() && (!isOpen() || readBuffer->isEmpty()); +} + +/*! \reimp +*/ +bool K3bQProcess::isSequential() const +{ + return true; +} + +/*! \reimp +*/ +qint64 K3bQProcess::bytesAvailable() const +{ + Q_D(const K3bQProcess); + const QRingBuffer *readBuffer = (d->processChannel == ::QProcess::StandardError) + ? &d->errorReadBuffer + : &d->outputReadBuffer; +#if defined QPROCESS_DEBUG + qDebug("QProcess::bytesAvailable() == %i (%s)", readBuffer->size(), + (d->processChannel == ::QProcess::StandardError) ? "stderr" : "stdout"); +#endif + return readBuffer->size() + QIODevice::bytesAvailable(); +} + +/*! \reimp +*/ +qint64 K3bQProcess::bytesToWrite() const +{ + Q_D(const K3bQProcess); + qint64 size = d->writeBuffer.size(); +#ifdef Q_OS_WIN + size += d->pipeWriterBytesToWrite(); +#endif + return size; +} + +/*! + Returns the type of error that occurred last. + + \sa state() +*/ +::QProcess::ProcessError K3bQProcess::error() const +{ + Q_D(const K3bQProcess); + return d->processError; +} + +/*! + Returns the current state of the process. + + \sa stateChanged(), error() +*/ +::QProcess::ProcessState K3bQProcess::state() const +{ + Q_D(const K3bQProcess); + return d->processState; +} + +/*! + Sets the environment that QProcess will use when starting a process to the + \a environment specified which consists of a list of key=value pairs. + + For example, the following code adds the \c{C:\\BIN} directory to the list of + executable paths (\c{PATHS}) on Windows: + + \snippet doc/src/snippets/qprocess-environment/main.cpp 0 + + \sa environment(), systemEnvironment() +*/ +void K3bQProcess::setEnvironment(const QStringList &environment) +{ + Q_D(K3bQProcess); + d->environment = environment; +} + +/*! + Returns the environment that QProcess will use when starting a + process, or an empty QStringList if no environment has been set + using setEnvironment(). If no environment has been set, the + environment of the calling process will be used. + + \note The environment settings are ignored on Windows CE, + as there is no concept of an environment. + + \sa setEnvironment(), systemEnvironment() +*/ +QStringList K3bQProcess::environment() const +{ + Q_D(const K3bQProcess); + return d->environment; +} + +/*! + Blocks until the process has started and the started() signal has + been emitted, or until \a msecs milliseconds have passed. + + Returns true if the process was started successfully; otherwise + returns false (if the operation timed out or if an error + occurred). + + This function can operate without an event loop. It is + useful when writing non-GUI applications and when performing + I/O operations in a non-GUI thread. + + \warning Calling this function from the main (GUI) thread + might cause your user interface to freeze. + + If msecs is -1, this function will not time out. + + \sa started(), waitForReadyRead(), waitForBytesWritten(), waitForFinished() +*/ +bool K3bQProcess::waitForStarted(int msecs) +{ + Q_D(K3bQProcess); + if (d->processState == ::QProcess::Starting) { + if (!d->waitForStarted(msecs)) + return false; + setProcessState(::QProcess::Running); + emit started(); + } + return d->processState == ::QProcess::Running; +} + +/*! \reimp +*/ +bool K3bQProcess::waitForReadyRead(int msecs) +{ + Q_D(K3bQProcess); + + if (d->processState == ::QProcess::NotRunning) + return false; + if (d->processChannel == ::QProcess::StandardOutput && d->stdoutChannel.closed) + return false; + if (d->processChannel == ::QProcess::StandardError && d->stderrChannel.closed) + return false; + return d->waitForReadyRead(msecs); +} + +/*! \reimp +*/ +bool K3bQProcess::waitForBytesWritten(int msecs) +{ + Q_D(K3bQProcess); + if (d->processState == ::QProcess::NotRunning) + return false; + if (d->processState == ::QProcess::Starting) { + QTime stopWatch; + stopWatch.start(); + bool started = waitForStarted(msecs); + if (!started) + return false; + if (msecs != -1) + msecs -= stopWatch.elapsed(); + } + + return d->waitForBytesWritten(msecs); +} + +/*! + Blocks until the process has finished and the finished() signal + has been emitted, or until \a msecs milliseconds have passed. + + Returns true if the process finished; otherwise returns false (if + the operation timed out or if an error occurred). + + This function can operate without an event loop. It is + useful when writing non-GUI applications and when performing + I/O operations in a non-GUI thread. + + \warning Calling this function from the main (GUI) thread + might cause your user interface to freeze. + + If msecs is -1, this function will not time out. + + \sa finished(), waitForStarted(), waitForReadyRead(), waitForBytesWritten() +*/ +bool K3bQProcess::waitForFinished(int msecs) +{ + Q_D(K3bQProcess); + if (d->processState == ::QProcess::NotRunning) + return false; + if (d->processState == ::QProcess::Starting) { + QTime stopWatch; + stopWatch.start(); + bool started = waitForStarted(msecs); + if (!started) + return false; + if (msecs != -1) + msecs -= stopWatch.elapsed(); + } + + return d->waitForFinished(msecs); +} + +/*! + Sets the current state of the QProcess to the \a state specified. + + \sa state() +*/ +void K3bQProcess::setProcessState(::QProcess::ProcessState state) +{ + Q_D(K3bQProcess); + if (d->processState == state) + return; + d->processState = state; + emit stateChanged(state); +} + +/*! + This function is called in the child process context just before the + program is executed on Unix or Mac OS X (i.e., after \e fork(), but before + \e execve()). Reimplement this function to do last minute initialization + of the child process. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 4 + + You cannot exit the process (by calling exit(), for instance) from + this function. If you need to stop the program before it starts + execution, your workaround is to emit finished() and then call + exit(). + + \warning This function is called by QProcess on Unix and Mac OS X + only. On Windows, it is not called. +*/ +void K3bQProcess::setupChildProcess() +{ +} + +/*! \reimp +*/ +qint64 K3bQProcess::readData(char *data, qint64 maxlen) +{ + Q_D(K3bQProcess); + if (d->processFlags&K3bQProcess::RawStdout && + d->processChannel == ::QProcess::StandardOutput) { + return d->readFromStdout(data, maxlen); + } + else { + QRingBuffer *readBuffer = (d->processChannel == ::QProcess::StandardError) + ? &d->errorReadBuffer + : &d->outputReadBuffer; + + if (maxlen == 1 && !readBuffer->isEmpty()) { + int c = readBuffer->getChar(); + if (c == -1) { +#if defined QPROCESS_DEBUG + qDebug("QProcess::readData(%p \"%s\", %d) == -1", + data, qt_prettyDebug(data, 1, maxlen).constData(), 1); +#endif + return -1; + } + *data = (char) c; +#if defined QPROCESS_DEBUG + qDebug("QProcess::readData(%p \"%s\", %d) == 1", + data, qt_prettyDebug(data, 1, maxlen).constData(), 1); +#endif + return 1; + } + + qint64 bytesToRead = qint64(qMin(readBuffer->size(), (int)maxlen)); + qint64 readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readBuffer->readPointer(); + int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, + readBuffer->nextDataBlockSize()); + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + readBuffer->free(bytesToReadFromThisBlock); + } + +#if defined QPROCESS_DEBUG + qDebug("QProcess::readData(%p \"%s\", %lld) == %lld", + data, qt_prettyDebug(data, readSoFar, 16).constData(), maxlen, readSoFar); +#endif + if (!readSoFar && d->processState == ::QProcess::NotRunning) + return -1; // EOF + return readSoFar; + } +} + +/*! \reimp +*/ +qint64 K3bQProcess::writeData(const char *data, qint64 len) +{ + Q_D(K3bQProcess); + +#if defined(Q_OS_WINCE) + Q_UNUSED(data); + Q_UNUSED(len); + d->processError = ::QProcess::WriteError; + setErrorString(tr("Error writing to process")); + emit error(d->processError); + return -1; +#endif + + if (d->stdinChannel.closed) { +#if defined QPROCESS_DEBUG + qDebug("QProcess::writeData(%p \"%s\", %lld) == 0 (write channel closing)", + data, qt_prettyDebug(data, len, 16).constData(), len); +#endif + return 0; + } + + if (d->processFlags & K3bQProcess::RawStdin) { + d->waitForBytesWritten(); + qint64 r = d->writeToStdin(data, len); + if ( r > 0 ) + emit bytesWritten(r); + return r; + } + else { + if (len == 1) { + d->writeBuffer.putChar(*data); + if (d->stdinChannel.notifier) + d->stdinChannel.notifier->setEnabled(true); +#if defined QPROCESS_DEBUG + qDebug("QProcess::writeData(%p \"%s\", %lld) == 1 (written to buffer)", + data, qt_prettyDebug(data, len, 16).constData(), len); +#endif + return 1; + } + + char *dest = d->writeBuffer.reserve(len); + memcpy(dest, data, len); + if (d->stdinChannel.notifier) + d->stdinChannel.notifier->setEnabled(true); +#if defined QPROCESS_DEBUG + qDebug("QProcess::writeData(%p \"%s\", %lld) == %lld (written to buffer)", + data, qt_prettyDebug(data, len, 16).constData(), len, len); +#endif + return len; + } +} + +/*! + Regardless of the current read channel, this function returns all + data available from the standard output of the process as a + QByteArray. + + \sa readyReadStandardOutput(), readAllStandardError(), readChannel(), setReadChannel() +*/ +QByteArray K3bQProcess::readAllStandardOutput() +{ + ::QProcess::ProcessChannel tmp = readChannel(); + setReadChannel(::QProcess::StandardOutput); + QByteArray data = readAll(); + setReadChannel(tmp); + return data; +} + +/*! + Regardless of the current read channel, this function returns all + data available from the standard error of the process as a + QByteArray. + + \sa readyReadStandardError(), readAllStandardOutput(), readChannel(), setReadChannel() +*/ +QByteArray K3bQProcess::readAllStandardError() +{ + ::QProcess::ProcessChannel tmp = readChannel(); + setReadChannel(::QProcess::StandardError); + QByteArray data = readAll(); + setReadChannel(tmp); + return data; +} + +/*! + Starts the program \a program in a new process, passing the + command line arguments in \a arguments. The OpenMode is set to \a + mode. QProcess will immediately enter the Starting state. If the + process starts successfully, QProcess will emit started(); + otherwise, error() will be emitted. + + Note that arguments that contain spaces are not passed to the + process as separate arguments. + + \bold{Windows:} Arguments that contain spaces are wrapped in quotes. + + \note Processes are started asynchronously, which means the started() + and error() signals may be delayed. Call waitForStarted() to make + sure the process has started (or has failed to start) and those signals + have been emitted. + + \sa pid(), started(), waitForStarted() +*/ +void K3bQProcess::start(const QString &program, const QStringList &arguments, OpenMode mode) +{ + Q_D(K3bQProcess); + if (d->processState != ::QProcess::NotRunning) { + qWarning("QProcess::start: Process is already running"); + return; + } + +#if defined QPROCESS_DEBUG + qDebug() << "QProcess::start(" << program << "," << arguments << "," << mode << ")"; +#endif + + d->outputReadBuffer.clear(); + d->errorReadBuffer.clear(); + + d->isReadyWrite = false; + + if (d->stdinChannel.type != K3bQProcessPrivate::Channel::Normal) + mode &= ~WriteOnly; // not open for writing + if (d->stdoutChannel.type != K3bQProcessPrivate::Channel::Normal && + (d->stderrChannel.type != K3bQProcessPrivate::Channel::Normal || + d->processChannelMode == ::QProcess::MergedChannels)) + mode &= ~ReadOnly; // not open for reading + if (mode == 0) + mode = Unbuffered; + QIODevice::open(mode); + + d->stdinChannel.closed = false; + d->stdoutChannel.closed = false; + d->stderrChannel.closed = false; + + d->program = program; + d->arguments = arguments; + + d->exitCode = 0; + d->exitStatus = ::QProcess::NormalExit; + d->processError = ::QProcess::UnknownError; + setErrorString( QString() ); + d->startProcess(); +} + + +static QStringList parseCombinedArgString(const QString &program) +{ + QStringList args; + QString tmp; + int quoteCount = 0; + bool inQuote = false; + + // handle quoting. tokens can be surrounded by double quotes + // "hello world". three consecutive double quotes represent + // the quote character itself. + for (int i = 0; i < program.size(); ++i) { + if (program.at(i) == QLatin1Char('"')) { + ++quoteCount; + if (quoteCount == 3) { + // third consecutive quote + quoteCount = 0; + tmp += program.at(i); + } + continue; + } + if (quoteCount) { + if (quoteCount == 1) + inQuote = !inQuote; + quoteCount = 0; + } + if (!inQuote && program.at(i).isSpace()) { + if (!tmp.isEmpty()) { + args += tmp; + tmp.clear(); + } + } else { + tmp += program.at(i); + } + } + if (!tmp.isEmpty()) + args += tmp; + + return args; +} + +/*! + \overload + + Starts the program \a program in a new process. \a program is a + single string of text containing both the program name and its + arguments. The arguments are separated by one or more + spaces. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 5 + + The \a program string can also contain quotes, to ensure that arguments + containing spaces are correctly supplied to the new process. For example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 6 + + Note that, on Windows, quotes need to be both escaped and quoted. + For example, the above code would be specified in the following + way to ensure that \c{"My Documents"} is used as the argument to + the \c dir executable: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 7 + + The OpenMode is set to \a mode. +*/ +void K3bQProcess::start(const QString &program, OpenMode mode) +{ + QStringList args = parseCombinedArgString(program); + + QString prog = args.first(); + args.removeFirst(); + + start(prog, args, mode); +} + +/*! + Attempts to terminate the process. + + The process may not exit as a result of calling this function (it is given + the chance to prompt the user for any unsaved files, etc). + + On Windows, terminate() posts a WM_CLOSE message to all toplevel windows + of the process and then to the main thread of the process itself. On Unix + and Mac OS X the SIGTERM signal is sent. + + Console applications on Windows that do not run an event loop, or whose + event loop does not handle the WM_CLOSE message, can only be terminated by + calling kill(). + + \sa kill() +*/ +void K3bQProcess::terminate() +{ + Q_D(K3bQProcess); + d->terminateProcess(); +} + +/*! + Kills the current process, causing it to exit immediately. + + On Windows, kill() uses TerminateProcess, and on Unix and Mac OS X, the + SIGKILL signal is sent to the process. + + \sa terminate() +*/ +void K3bQProcess::kill() +{ + Q_D(K3bQProcess); + d->killProcess(); +} + +/*! + Returns the exit code of the last process that finished. +*/ +int K3bQProcess::exitCode() const +{ + Q_D(const K3bQProcess); + return d->exitCode; +} + +/*! + \since 4.1 + + Returns the exit status of the last process that finished. + + On Windows, if the process was terminated with TerminateProcess() + from another application this function will still return NormalExit + unless the exit code is less than 0. +*/ +::QProcess::ExitStatus K3bQProcess::exitStatus() const +{ + Q_D(const K3bQProcess); + return d->exitStatus; +} + +/*! + Starts the program \a program with the arguments \a arguments in a + new process, waits for it to finish, and then returns the exit + code of the process. Any data the new process writes to the + console is forwarded to the calling process. + + The environment and working directory are inherited by the calling + process. + + On Windows, arguments that contain spaces are wrapped in quotes. +*/ +int K3bQProcess::execute(const QString &program, const QStringList &arguments) +{ + QProcess process; + process.setReadChannelMode(::QProcess::ForwardedChannels); + process.start(program, arguments); + process.waitForFinished(-1); + return process.exitCode(); +} + +/*! + \overload + + Starts the program \a program in a new process. \a program is a + single string of text containing both the program name and its + arguments. The arguments are separated by one or more spaces. +*/ +int K3bQProcess::execute(const QString &program) +{ + QProcess process; + process.setReadChannelMode(::QProcess::ForwardedChannels); + process.start(program); + process.waitForFinished(-1); + return process.exitCode(); +} + +/*! + Starts the program \a program with the arguments \a arguments in a + new process, and detaches from it. Returns true on success; + otherwise returns false. If the calling process exits, the + detached process will continue to live. + + Note that arguments that contain spaces are not passed to the + process as separate arguments. + + \bold{Unix:} The started process will run in its own session and act + like a daemon. + + \bold{Windows:} Arguments that contain spaces are wrapped in quotes. + The started process will run as a regular standalone process. + + The process will be started in the directory \a workingDirectory. + + If the function is successful then *\a pid is set to the process + identifier of the started process. +*/ +bool K3bQProcess::startDetached(const QString &program, + const QStringList &arguments, + const QString &workingDirectory, + qint64 *pid) +{ + return K3bQProcessPrivate::startDetached(program, + arguments, + workingDirectory, + pid); +} + +/*! + Starts the program \a program with the given \a arguments in a + new process, and detaches from it. Returns true on success; + otherwise returns false. If the calling process exits, the + detached process will continue to live. + + Note that arguments that contain spaces are not passed to the + process as separate arguments. + + \bold{Unix:} The started process will run in its own session and act + like a daemon. + + \bold{Windows:} Arguments that contain spaces are wrapped in quotes. + The started process will run as a regular standalone process. +*/ +bool K3bQProcess::startDetached(const QString &program, + const QStringList &arguments) +{ + return K3bQProcessPrivate::startDetached(program, arguments); +} + +/*! + \overload + + Starts the program \a program in a new process. \a program is a + single string of text containing both the program name and its + arguments. The arguments are separated by one or more spaces. + + The \a program string can also contain quotes, to ensure that arguments + containing spaces are correctly supplied to the new process. +*/ +bool K3bQProcess::startDetached(const QString &program) +{ + QStringList args = parseCombinedArgString(program); + + QString prog = args.first(); + args.removeFirst(); + + return K3bQProcessPrivate::startDetached(prog, args); +} + +QT_BEGIN_INCLUDE_NAMESPACE +#ifdef Q_OS_MAC +# include +# define environ (*_NSGetEnviron()) +#elif defined(Q_OS_WINCE) + static char *qt_wince_environ[] = { 0 }; +#define environ qt_wince_environ +#elif !defined(Q_OS_WIN) + extern char **environ; +#endif +QT_END_INCLUDE_NAMESPACE + +/*! + \since 4.1 + + Returns the environment of the calling process as a list of + key=value pairs. Example: + + \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 8 + + \sa environment(), setEnvironment() +*/ +QStringList K3bQProcess::systemEnvironment() +{ + QStringList tmp; +// char *entry = 0; +// int count = 0; +// while ((entry = environ[count++])) +// tmp << QString::fromLocal8Bit(entry); + return tmp; +} + +bool K3bQProcess::isReadyWrite() const +{ + Q_D(const K3bQProcess); + return d->isReadyWrite; +} + + +/*! + \typedef Q_PID + \relates QProcess + + Typedef for the identifiers used to represent processes on the underlying + platform. On Unix, this corresponds to \l qint64; on Windows, it + corresponds to \c{_PROCESS_INFORMATION*}. + + \sa QProcess::pid() +*/ + + +//QT_END_NAMESPACE + +#include "moc_k3bqprocess.cpp" + +#endif // QT_NO_PROCESS + diff --git a/libk3b/tools/qprocess/k3bqprocess.h b/libk3b/tools/qprocess/k3bqprocess.h new file mode 100644 index 000000000..37c7fb91c --- /dev/null +++ b/libk3b/tools/qprocess/k3bqprocess.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef K3B_QPROCESS_H +#define K3B_QPROCESS_H + +#include +#include +#include + +#include "k3b_export.h" + +// QT_BEGIN_HEADER + +// QT_BEGIN_NAMESPACE + +// QT_MODULE(Core) + +#ifndef QT_NO_PROCESS + +#if (!defined(Q_OS_WIN32) && !defined(Q_OS_WINCE)) || defined(qdoc) +typedef qint64 Q_PID; +#else +QT_END_NAMESPACE +typedef struct _PROCESS_INFORMATION *Q_PID; +QT_BEGIN_NAMESPACE +#endif + +class K3bQProcessPrivate; + +class LIBK3B_EXPORT K3bQProcess : public QIODevice +{ + Q_OBJECT +public: + // BE AWARE: we use the original enums from QProcess to make the future transition of slots easier + /* + enum ProcessError { + FailedToStart, //### file not found, resource error + Crashed, + Timedout, + ReadError, + WriteError, + UnknownError + }; + enum ProcessState { + NotRunning, + Starting, + Running + }; + enum ProcessChannel { + StandardOutput, + StandardError + }; + enum ProcessChannelMode { + SeparateChannels, + MergedChannels, + ForwardedChannels + }; + enum ExitStatus { + NormalExit, + CrashExit + }; + */ + enum ProcessFlag { + NoFlags = 0x0, + RawStdin = 0x1, + RawStdout = 0x2 + }; + Q_DECLARE_FLAGS( ProcessFlags, ProcessFlag ) + + explicit K3bQProcess(QObject *parent = 0); + virtual ~K3bQProcess(); + + void start(const QString &program, const QStringList &arguments, OpenMode mode = ReadWrite); + void start(const QString &program, OpenMode mode = ReadWrite); + + ::QProcess::ProcessChannelMode readChannelMode() const; + void setReadChannelMode(::QProcess::ProcessChannelMode mode); + ::QProcess::ProcessChannelMode processChannelMode() const; + void setProcessChannelMode(::QProcess::ProcessChannelMode mode); + + ProcessFlags flags() const; + void setFlags( ProcessFlags flags ); + + ::QProcess::ProcessChannel readChannel() const; + void setReadChannel(::QProcess::ProcessChannel channel); + + void closeReadChannel(::QProcess::ProcessChannel channel); + void closeWriteChannel(); + + void setStandardInputFile(const QString &fileName); + void setStandardOutputFile(const QString &fileName, OpenMode mode = Truncate); + void setStandardErrorFile(const QString &fileName, OpenMode mode = Truncate); + void setStandardOutputProcess(K3bQProcess *destination); + + QString workingDirectory() const; + void setWorkingDirectory(const QString &dir); + + void setEnvironment(const QStringList &environment); + QStringList environment() const; + + ::QProcess::ProcessError error() const; + ::QProcess::ProcessState state() const; + + // #### Qt 5: Q_PID is a pointer on Windows and a value on Unix + Q_PID pid() const; + + bool waitForStarted(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + bool waitForBytesWritten(int msecs = 30000); + bool waitForFinished(int msecs = 30000); + + QByteArray readAllStandardOutput(); + QByteArray readAllStandardError(); + + int exitCode() const; + ::QProcess::ExitStatus exitStatus() const; + + // QIODevice + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + bool isSequential() const; + bool canReadLine() const; + void close(); + bool atEnd() const; + + bool isReadyWrite() const; + + static int execute(const QString &program, const QStringList &arguments); + static int execute(const QString &program); + + static bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, + qint64 *pid = 0); + static bool startDetached(const QString &program, const QStringList &arguments); + static bool startDetached(const QString &program); + + static QStringList systemEnvironment(); + +public Q_SLOTS: + void terminate(); + void kill(); + +Q_SIGNALS: + void started(); + void finished(int exitCode); + void finished(int exitCode, QProcess::ExitStatus exitStatus); + void error(QProcess::ProcessError error); + void stateChanged(QProcess::ProcessState state); + + void readyReadStandardOutput(); + void readyReadStandardError(); + void readyWrite(); + +protected: + void setProcessState(QProcess::ProcessState state); + + virtual void setupChildProcess(); + + // QIODevice + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + +private: + Q_DECLARE_PRIVATE(K3bQProcess) + Q_DISABLE_COPY(K3bQProcess) + + K3bQProcessPrivate* d_ptr; + + Q_PRIVATE_SLOT(d_func(), bool _q_canReadStandardOutput()) + Q_PRIVATE_SLOT(d_func(), bool _q_canReadStandardError()) + Q_PRIVATE_SLOT(d_func(), bool _q_canWrite()) + Q_PRIVATE_SLOT(d_func(), bool _q_startupNotification()) + Q_PRIVATE_SLOT(d_func(), bool _q_processDied()) + Q_PRIVATE_SLOT(d_func(), void _q_notified()) + friend class K3bQProcessManager; +}; + +#endif // QT_NO_PROCESS + +// QT_END_NAMESPACE + +// QT_END_HEADER + +Q_DECLARE_OPERATORS_FOR_FLAGS( K3bQProcess::ProcessFlags ) + +#endif // QPROCESS_H diff --git a/libk3b/tools/qprocess/k3bqprocess_p.h b/libk3b/tools/qprocess/k3bqprocess_p.h new file mode 100644 index 000000000..ab8285fa1 --- /dev/null +++ b/libk3b/tools/qprocess/k3bqprocess_p.h @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef K3B_QPROCESS_P_H +#define K3B_QPROCESS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "k3bqprocess.h" +#include "QtCore/qstringlist.h" +//#include "private/qringbuffer_p.h" +#include "qringbuffer_p.h" +//#include "private/qiodevice_p.h" + +#ifdef Q_OS_WIN +#include "QtCore/qt_windows.h" +typedef HANDLE Q_PIPE; +#define INVALID_Q_PIPE INVALID_HANDLE_VALUE +#else +typedef int Q_PIPE; +#define INVALID_Q_PIPE -1 +#endif + +#ifndef QT_NO_PROCESS + +//QT_BEGIN_NAMESPACE +class QSocketNotifier; +class QWindowsPipeWriter; +class QWinEventNotifier; +class QTimer; + + +class K3bQProcessPrivate +{ +public: + Q_DECLARE_PUBLIC(K3bQProcess) + K3bQProcess* q_ptr; + + struct Channel { + enum ProcessChannelType { + Normal = 0, + PipeSource = 1, + PipeSink = 2, + Redirect = 3 + // if you add "= 4" here, increase the number of bits below + }; + + Channel() : process(0), notifier(0), type(Normal), closed(false), append(false) + { + pipe[0] = INVALID_Q_PIPE; + pipe[1] = INVALID_Q_PIPE; + } + + void clear(); + + Channel &operator=(const QString &fileName) + { + clear(); + file = fileName; + type = fileName.isEmpty() ? Normal : Redirect; + return *this; + } + + void pipeTo(K3bQProcessPrivate *other) + { + clear(); + process = other; + type = PipeSource; + } + + void pipeFrom(K3bQProcessPrivate *other) + { + clear(); + process = other; + type = PipeSink; + } + + QString file; + K3bQProcessPrivate *process; + QSocketNotifier *notifier; + Q_PIPE pipe[2]; + + unsigned type : 2; + bool closed : 1; + bool append : 1; + }; + + K3bQProcessPrivate(); + virtual ~K3bQProcessPrivate(); + + // private slots + bool _q_canReadStandardOutput(); + bool _q_canReadStandardError(); + bool _q_canWrite(); + bool _q_startupNotification(); + bool _q_processDied(); + void _q_notified(); + + ::QProcess::ProcessChannel processChannel; + ::QProcess::ProcessChannelMode processChannelMode; + K3bQProcess::ProcessFlags processFlags; + ::QProcess::ProcessError processError; + ::QProcess::ProcessState processState; + QString workingDirectory; + Q_PID pid; + int sequenceNumber; + + bool dying; + bool emittedReadyRead; + bool emittedBytesWritten; + + Channel stdinChannel; + Channel stdoutChannel; + Channel stderrChannel; + bool createChannel(Channel &channel); + void closeWriteChannel(); + + QString program; + QStringList arguments; + QStringList environment; + + QRingBuffer outputReadBuffer; + QRingBuffer errorReadBuffer; + QRingBuffer writeBuffer; + + Q_PIPE childStartedPipe[2]; + Q_PIPE deathPipe[2]; + void destroyPipe(Q_PIPE pipe[2]); + + QSocketNotifier *startupSocketNotifier; + QSocketNotifier *deathNotifier; + + // the wonderful windows notifier + QTimer *notifier; + QWindowsPipeWriter *pipeWriter; + QWinEventNotifier *processFinishedNotifier; + + void startProcess(); +#ifdef Q_OS_UNIX + void execChild(const char *workingDirectory, char **path, char **argv, char **envp); +#endif + bool processStarted(); + void terminateProcess(); + void killProcess(); + void findExitCode(); +#ifdef Q_OS_UNIX + bool waitForDeadChild(); +#endif +#ifdef Q_OS_WIN + void flushPipeWriter(); + qint64 pipeWriterBytesToWrite() const; +#endif + + static bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory = QString(), + qint64 *pid = 0); + + int exitCode; + ::QProcess::ExitStatus exitStatus; + bool crashed; +#ifdef Q_OS_UNIX + int serial; +#endif + + bool isReadyWrite; + + bool waitForStarted(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + bool waitForBytesWritten(int msecs = 30000); + bool waitForFinished(int msecs = 30000); + bool waitForWrite(int msecs = 30000); + + qint64 bytesAvailableFromStdout() const; + qint64 bytesAvailableFromStderr() const; + qint64 readFromStdout(char *data, qint64 maxlen); + qint64 readFromStderr(char *data, qint64 maxlen); + qint64 writeToStdin(const char *data, qint64 maxlen); + + void cleanup(); +#ifdef Q_OS_UNIX + static void initializeProcessManager(); +#endif +}; + +//QT_END_NAMESPACE + +#endif // QT_NO_PROCESS + +#endif // QPROCESS_P_H diff --git a/libk3b/tools/qprocess/k3bqprocess_unix.cpp b/libk3b/tools/qprocess/k3bqprocess_unix.cpp new file mode 100644 index 000000000..1b5bdf6fb --- /dev/null +++ b/libk3b/tools/qprocess/k3bqprocess_unix.cpp @@ -0,0 +1,1388 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QPROCESS_DEBUG +#include "qdebug.h" + +#ifndef QT_NO_PROCESS + +#if defined QPROCESS_DEBUG +#include "qstring.h" +#include + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +//QT_BEGIN_NAMESPACE +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(c)) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} +//QT_END_NAMESPACE +#endif + +#include "qplatformdefs.h" + +#include "k3bqprocess.h" +#include "k3bqprocess_p.h" + +#ifdef Q_OS_MAC +#include +#endif + +//#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +//QT_BEGIN_NAMESPACE +#ifdef Q_OS_INTEGRITY +static inline char *strdup(const char *data) +{ + return qstrdup(data); +} +#endif + +static qint64 qt_native_read(int fd, char *data, qint64 maxlen) +{ + qint64 ret = 0; + do { + ret = ::read(fd, data, maxlen); + } while (ret == -1 && ( errno == EINTR || errno == EAGAIN )); + return ret; +} + +static qint64 qt_native_write(int fd, const char *data, qint64 len) +{ + qint64 ret = 0; + do { + ret = ::write(fd, data, len); + } while (ret == -1 && ( errno == EINTR || errno == EAGAIN )); + if ( ret == -1 ) + qDebug() << "write failed:" << ( int )errno << strerror( errno ); + return ret; +} + +static void qt_native_close(int fd) +{ + int ret; + do { + ret = ::close(fd); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_sigaction(int signum, const struct sigaction *act, + struct sigaction *oldact) +{ + int ret; + do { + ret = ::sigaction(signum, act, oldact); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_dup2(int oldfd, int newfd) +{ + int ret; + do { + ret = ::dup2(oldfd, newfd); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_chdir(const char *path) +{ + int ret; + do { + ret = ::chdir(path); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_execve(const char *filename, char *const argv[], + char *const envp[]) +{ + int ret; + do { + ret = ::execve(filename, argv, envp); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_execv(const char *path, char *const argv[]) +{ + int ret; + do { + ret = ::execv(path, argv); + } while (ret == -1 && errno == EINTR); +} + +static void qt_native_execvp(const char *file, char *const argv[]) +{ + int ret; + do { + ret = ::execvp(file, argv); + } while (ret == -1 && errno == EINTR); +} + +static int qt_qprocess_deadChild_pipe[2]; +static void (*qt_sa_old_sigchld_handler)(int) = 0; +static void qt_sa_sigchld_handler(int signum) +{ + qt_native_write(qt_qprocess_deadChild_pipe[1], "", 1); +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "*** SIGCHLD\n"); +#endif + + if (qt_sa_old_sigchld_handler && qt_sa_old_sigchld_handler != SIG_IGN) + qt_sa_old_sigchld_handler(signum); +} + + +struct K3bQProcessInfo { + K3bQProcess *process; + int deathPipe; + int exitResult; + pid_t pid; + int serialNumber; +}; + +class K3bQProcessManager : public QThread +{ + Q_OBJECT +public: + K3bQProcessManager(); + ~K3bQProcessManager(); + + void run(); + void catchDeadChildren(); + void add(pid_t pid, K3bQProcess *process); + void remove(K3bQProcess *process); + void lock(); + void unlock(); + +private: + QMutex mutex; + QMap children; +}; + +Q_GLOBAL_STATIC(K3bQProcessManager, processManager) + +K3bQProcessManager::K3bQProcessManager() +{ +#if defined (QPROCESS_DEBUG) + qDebug() << "K3bQProcessManager::K3bQProcessManager()"; +#endif + // initialize the dead child pipe and make it non-blocking. in the + // extremely unlikely event that the pipe fills up, we do not under any + // circumstances want to block. + ::pipe(qt_qprocess_deadChild_pipe); + ::fcntl(qt_qprocess_deadChild_pipe[0], F_SETFD, FD_CLOEXEC); + ::fcntl(qt_qprocess_deadChild_pipe[1], F_SETFD, FD_CLOEXEC); + ::fcntl(qt_qprocess_deadChild_pipe[0], F_SETFL, + ::fcntl(qt_qprocess_deadChild_pipe[0], F_GETFL) | O_NONBLOCK); + ::fcntl(qt_qprocess_deadChild_pipe[1], F_SETFL, + ::fcntl(qt_qprocess_deadChild_pipe[1], F_GETFL) | O_NONBLOCK); + + // set up the SIGCHLD handler, which writes a single byte to the dead + // child pipe every time a child dies. + struct sigaction oldAction; + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = qt_sa_sigchld_handler; + action.sa_flags = SA_NOCLDSTOP; + qt_native_sigaction(SIGCHLD, &action, &oldAction); + if (oldAction.sa_handler != qt_sa_sigchld_handler) + qt_sa_old_sigchld_handler = oldAction.sa_handler; +} + +K3bQProcessManager::~K3bQProcessManager() +{ + // notify the thread that we're shutting down. + qt_native_write(qt_qprocess_deadChild_pipe[1], "@", 1); + qt_native_close(qt_qprocess_deadChild_pipe[1]); + wait(); + + // on certain unixes, closing the reading end of the pipe will cause + // select in run() to block forever, rather than return with EBADF. + qt_native_close(qt_qprocess_deadChild_pipe[0]); + + qt_qprocess_deadChild_pipe[0] = -1; + qt_qprocess_deadChild_pipe[1] = -1; + + qDeleteAll(children.values()); + children.clear(); + + struct sigaction oldAction; + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = qt_sa_old_sigchld_handler; + action.sa_flags = SA_NOCLDSTOP; + qt_native_sigaction(SIGCHLD, &action, &oldAction); + if (oldAction.sa_handler != qt_sa_sigchld_handler) { + qt_native_sigaction(SIGCHLD, &oldAction, 0); + } +} + +void K3bQProcessManager::run() +{ + forever { + fd_set readset; + FD_ZERO(&readset); + FD_SET(qt_qprocess_deadChild_pipe[0], &readset); + +#if defined (QPROCESS_DEBUG) + qDebug() << "K3bQProcessManager::run() waiting for children to die"; +#endif + + // block forever, or until activity is detected on the dead child + // pipe. the only other peers are the SIGCHLD signal handler, and the + // K3bQProcessManager destructor. + int nselect = select(qt_qprocess_deadChild_pipe[0] + 1, &readset, 0, 0, 0); + if (nselect < 0) { + if (errno == EINTR) + continue; + break; + } + + // empty only one byte from the pipe, even though several SIGCHLD + // signals may have been delivered in the meantime, to avoid race + // conditions. + char c; + if (qt_native_read(qt_qprocess_deadChild_pipe[0], &c, 1) < 0 || c == '@') + break; + + // catch any and all children that we can. + catchDeadChildren(); + } +} + +void K3bQProcessManager::catchDeadChildren() +{ + QMutexLocker locker(&mutex); + + // try to catch all children whose pid we have registered, and whose + // deathPipe is still valid (i.e, we have not already notified it). + QMap::Iterator it = children.begin(); + while (it != children.end()) { + // notify all children that they may have died. they need to run + // waitpid() in their own thread. + K3bQProcessInfo *info = it.value(); + qt_native_write(info->deathPipe, "", 1); + +#if defined (QPROCESS_DEBUG) + qDebug() << "K3bQProcessManager::run() sending death notice to" << info->process; +#endif + ++it; + } +} + +static QBasicAtomicInt idCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +void K3bQProcessManager::add(pid_t pid, K3bQProcess *process) +{ +#if defined (QPROCESS_DEBUG) + qDebug() << "K3bQProcessManager::add() adding pid" << pid << "process" << process; +#endif + + // insert a new info structure for this process + K3bQProcessInfo *info = new K3bQProcessInfo; + info->process = process; + info->deathPipe = process->d_func()->deathPipe[1]; + info->exitResult = 0; + info->pid = pid; + + int serial = idCounter.fetchAndAddRelaxed(1); + process->d_func()->serial = serial; + children.insert(serial, info); +} + +void K3bQProcessManager::remove(K3bQProcess *process) +{ + QMutexLocker locker(&mutex); + + int serial = process->d_func()->serial; + K3bQProcessInfo *info = children.value(serial); + if (!info) + return; + +#if defined (QPROCESS_DEBUG) + qDebug() << "K3bQProcessManager::remove() removing pid" << info->pid << "process" << info->process; +#endif + + children.remove(serial); + delete info; +} + +void K3bQProcessManager::lock() +{ + mutex.lock(); +} + +void K3bQProcessManager::unlock() +{ + mutex.unlock(); +} + +static void qt_create_pipe(int *pipe) +{ + if (pipe[0] != -1) + qt_native_close(pipe[0]); + if (pipe[1] != -1) + qt_native_close(pipe[1]); +#ifdef Q_OS_IRIX + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, pipe) == -1) { + qWarning("QProcessPrivate::createPipe: Cannot create pipe %p: %s", + pipe, qPrintable(qt_error_string(errno))); + } +#else + if (::pipe(pipe) != 0) { + qWarning("QProcessPrivate::createPipe: Cannot create pipe %p: %s", + pipe, qPrintable(qt_error_string(errno))); + } +#endif + ::fcntl(pipe[0], F_SETFD, FD_CLOEXEC); + ::fcntl(pipe[1], F_SETFD, FD_CLOEXEC); +} + +void K3bQProcessPrivate::destroyPipe(int *pipe) +{ + if (pipe[1] != -1) { + qt_native_close(pipe[1]); + pipe[1] = -1; + } + if (pipe[0] != -1) { + qt_native_close(pipe[0]); + pipe[0] = -1; + } +} + +/* + Create the pipes to a K3bQProcessPrivate::Channel. + + This function must be called in order: stdin, stdout, stderr +*/ +bool K3bQProcessPrivate::createChannel(Channel &channel) +{ + Q_Q(K3bQProcess); + + if (&channel == &stderrChannel && processChannelMode == ::QProcess::MergedChannels) { + channel.pipe[0] = -1; + channel.pipe[1] = -1; + return true; + } + + if (channel.type == Channel::Normal) { + // we're piping this channel to our own process + qt_create_pipe(channel.pipe); + + // create the socket notifiers +// if (threadData->eventDispatcher) { + if (&channel == &stdinChannel) { + channel.notifier = new QSocketNotifier(channel.pipe[1], + QSocketNotifier::Write, q); + QObject::connect(channel.notifier, SIGNAL(activated(int)), + q, SLOT(_q_canWrite())); + if (!(processFlags & K3bQProcess::RawStdin)) { + // in raw mode we want to know when the process is ready to read from stdin + channel.notifier->setEnabled(false); + } + } else if ( &channel == &stderrChannel || !(processFlags&K3bQProcess::RawStdout) ){ + channel.notifier = new QSocketNotifier(channel.pipe[0], + QSocketNotifier::Read, q); + const char *receiver; + if (&channel == &stdoutChannel) + receiver = SLOT(_q_canReadStandardOutput()); + else + receiver = SLOT(_q_canReadStandardError()); + QObject::connect(channel.notifier, SIGNAL(activated(int)), + q, receiver); + } +// } + + return true; + } else if (channel.type == Channel::Redirect) { + // we're redirecting the channel to/from a file + QByteArray fname = QFile::encodeName(channel.file); + + if (&channel == &stdinChannel) { + // try to open in read-only mode + channel.pipe[1] = -1; + if ( (channel.pipe[0] = QT_OPEN(fname, O_RDONLY)) != -1) + return true; // success + + q->setErrorString(K3bQProcess::tr("Could not open input redirection for reading")); + } else { + int mode = O_WRONLY | O_CREAT; + if (channel.append) + mode |= O_APPEND; + else + mode |= O_TRUNC; + + channel.pipe[0] = -1; + if ( (channel.pipe[1] = QT_OPEN(fname, mode, 0666)) != -1) + return true; // success + + q->setErrorString(K3bQProcess::tr("Could not open output redirection for writing")); + } + + // could not open file + processError = ::QProcess::FailedToStart; + emit q->error(processError); + cleanup(); + return false; + } else { + Q_ASSERT_X(channel.process, "K3bQProcess::start", "Internal error"); + + Channel *source; + Channel *sink; + + if (channel.type == Channel::PipeSource) { + // we are the source + source = &channel; + sink = &channel.process->stdinChannel; + + Q_ASSERT(source == &stdoutChannel); + Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink); + } else { + // we are the sink; + source = &channel.process->stdoutChannel; + sink = &channel; + + Q_ASSERT(sink == &stdinChannel); + Q_ASSERT(source->process == this && source->type == Channel::PipeSource); + } + + if (source->pipe[1] != INVALID_Q_PIPE || sink->pipe[0] != INVALID_Q_PIPE) { + // already created, do nothing + return true; + } else { + Q_ASSERT(source->pipe[0] == INVALID_Q_PIPE && source->pipe[1] == INVALID_Q_PIPE); + Q_ASSERT(sink->pipe[0] == INVALID_Q_PIPE && sink->pipe[1] == INVALID_Q_PIPE); + + Q_PIPE pipe[2] = { -1, -1 }; + qt_create_pipe(pipe); + sink->pipe[0] = pipe[0]; + source->pipe[1] = pipe[1]; + + return true; + } + } +} + +static char **_q_dupEnvironment(const QStringList &environment, int *envc) +{ + // if LD_LIBRARY_PATH exists in the current environment, but + // not in the environment list passed by the programmer, then + // copy it over. +#if defined(Q_OS_MAC) + static const char libraryPath[] = "DYLD_LIBRARY_PATH"; +#else + static const char libraryPath[] = "LD_LIBRARY_PATH"; +#endif + const QString libraryPathString = QLatin1String(libraryPath); + QStringList env = environment; + QStringList matches = env.filter( + QRegExp(QLatin1Char('^') + libraryPathString + QLatin1Char('='))); + const QString envLibraryPath = QString::fromLocal8Bit(::getenv(libraryPath)); + if (matches.isEmpty() && !envLibraryPath.isEmpty()) { + QString entry = libraryPathString; + entry += QLatin1Char('='); + entry += envLibraryPath; + env << libraryPathString + QLatin1Char('=') + envLibraryPath; + } + + char **envp = new char *[env.count() + 1]; + envp[env.count()] = 0; + + for (int j = 0; j < env.count(); ++j) { + QString item = env.at(j); + envp[j] = ::strdup(item.toLocal8Bit().constData()); + } + + *envc = env.count(); + return envp; +} + +// under QNX RTOS we have to use vfork() when multithreading +inline pid_t qt_fork() +{ +#if defined(Q_OS_QNX) + return vfork(); +#else + return fork(); +#endif +} + +#ifdef Q_OS_MAC +Q_GLOBAL_STATIC(QMutex, cfbundleMutex); +#endif + +void K3bQProcessPrivate::startProcess() +{ + Q_Q(K3bQProcess); + +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::startProcess()"); +#endif + + processManager()->start(); + + // Initialize pipes + qt_create_pipe(childStartedPipe); +// if (threadData->eventDispatcher) { + startupSocketNotifier = new QSocketNotifier(childStartedPipe[0], + QSocketNotifier::Read, q); + QObject::connect(startupSocketNotifier, SIGNAL(activated(int)), + q, SLOT(_q_startupNotification())); +// } + + qt_create_pipe(deathPipe); + ::fcntl(deathPipe[0], F_SETFD, FD_CLOEXEC); + ::fcntl(deathPipe[1], F_SETFD, FD_CLOEXEC); +// if (threadData->eventDispatcher) { + deathNotifier = new QSocketNotifier(deathPipe[0], + QSocketNotifier::Read, q); + QObject::connect(deathNotifier, SIGNAL(activated(int)), + q, SLOT(_q_processDied())); +// } + + if (!createChannel(stdinChannel) || + !createChannel(stdoutChannel) || + !createChannel(stderrChannel)) + return; + + // Start the process (platform dependent) + q->setProcessState(::QProcess::Starting); + + // Create argument list with right number of elements, and set the final + // one to 0. + char **argv = new char *[arguments.count() + 2]; + argv[arguments.count() + 1] = 0; + + // Encode the program name. + QByteArray encodedProgramName = QFile::encodeName(program); +#ifdef Q_OS_MAC + // allow invoking of .app bundles on the Mac. + QFileInfo fileInfo(QString::fromUtf8(encodedProgramName.constData())); + if (encodedProgramName.endsWith(".app") && fileInfo.isDir()) { + QCFType url = CFURLCreateWithFileSystemPath(0, + QCFString(fileInfo.absoluteFilePath()), + kCFURLPOSIXPathStyle, true); + { + // CFBundle is not reentrant, since CFBundleCreate might return a reference + // to a cached bundle object. Protect the bundle calls with a mutex lock. + QMutexLocker lock(cfbundleMutex()); + QCFType bundle = CFBundleCreate(0, url); + url = CFBundleCopyExecutableURL(bundle); + } + if (url) { + QCFString str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); + encodedProgramName += "/Contents/MacOS/" + static_cast(str).toUtf8(); + } + } +#endif + + // Add the program name to the argument list. + char *dupProgramName = ::strdup(encodedProgramName.constData()); + argv[0] = dupProgramName; + + // Add every argument to the list + for (int i = 0; i < arguments.count(); ++i) { + QString arg = arguments.at(i); +#ifdef Q_OS_MAC + // Mac OS X uses UTF8 for exec, regardless of the system locale. + argv[i + 1] = ::strdup(arg.toUtf8().constData()); +#else + argv[i + 1] = ::strdup(arg.toLocal8Bit().constData()); +#endif + } + + // Duplicate the environment. + int envc = 0; + char **envp = _q_dupEnvironment(environment, &envc); + + // Encode the working directory if it's non-empty, otherwise just pass 0. + const char *workingDirPtr = 0; + QByteArray encodedWorkingDirectory; + if (!workingDirectory.isEmpty()) { + encodedWorkingDirectory = QFile::encodeName(workingDirectory); + workingDirPtr = encodedWorkingDirectory.constData(); + } + + // If the program does not specify a path, generate a list of possible + // locations for the binary using the PATH environment variable. + char **path = 0; + int pathc = 0; + if (!program.contains(QLatin1Char('/'))) { + const QString pathEnv = QString::fromLocal8Bit(::getenv("PATH")); + if (!pathEnv.isEmpty()) { + QStringList pathEntries = pathEnv.split(QLatin1Char(':'), QString::SkipEmptyParts); + if (!pathEntries.isEmpty()) { + pathc = pathEntries.size(); + path = new char *[pathc + 1]; + path[pathc] = 0; + + for (int k = 0; k < pathEntries.size(); ++k) { + QByteArray tmp = QFile::encodeName(pathEntries.at(k)); + if (!tmp.endsWith('/')) tmp += '/'; + tmp += encodedProgramName; + path[k] = ::strdup(tmp.constData()); + } + } + } + } + + // Start the process manager, and fork off the child process. + processManager()->lock(); + pid_t childPid = qt_fork(); + int lastForkErrno = errno; + if (childPid != 0) { + // Clean up duplicated memory. + free(dupProgramName); + for (int i = 1; i <= arguments.count(); ++i) + free(argv[i]); + for (int i = 0; i < envc; ++i) + free(envp[i]); + for (int i = 0; i < pathc; ++i) + free(path[i]); + delete [] argv; + delete [] envp; + delete [] path; + } + if (childPid < 0) { + // Cleanup, report error and return +#if defined (QPROCESS_DEBUG) + qDebug("qt_fork failed: %s", qt_error_string(lastForkErrno)); +#endif + processManager()->unlock(); + q->setProcessState(::QProcess::NotRunning); + processError = ::QProcess::FailedToStart; + q->setErrorString(K3bQProcess::tr("Resource error (fork failure): %1").arg(qt_error_string(lastForkErrno))); + emit q->error(processError); + cleanup(); + return; + } + + // Start the child. + if (childPid == 0) { + execChild(workingDirPtr, path, argv, envp); + ::_exit(-1); + } + + // Register the child. In the mean time, we can get a SIGCHLD, so we need + // to keep the lock held to avoid a race to catch the child. + processManager()->add(childPid, q); + pid = Q_PID(childPid); + processManager()->unlock(); + + // parent + // close the ends we don't use and make all pipes non-blocking + ::fcntl(deathPipe[0], F_SETFL, ::fcntl(deathPipe[0], F_GETFL) | O_NONBLOCK); + qt_native_close(childStartedPipe[1]); + childStartedPipe[1] = -1; + + if (stdinChannel.pipe[0] != -1) { + qt_native_close(stdinChannel.pipe[0]); + stdinChannel.pipe[0] = -1; + } + + if (stdinChannel.pipe[1] != -1 && !(processFlags&K3bQProcess::RawStdin)) + ::fcntl(stdinChannel.pipe[1], F_SETFL, ::fcntl(stdinChannel.pipe[1], F_GETFL) | O_NONBLOCK); + + if (stdoutChannel.pipe[1] != -1) { + qt_native_close(stdoutChannel.pipe[1]); + stdoutChannel.pipe[1] = -1; + } + + if (stdoutChannel.pipe[0] != -1 && !(processFlags&K3bQProcess::RawStdout)) + ::fcntl(stdoutChannel.pipe[0], F_SETFL, ::fcntl(stdoutChannel.pipe[0], F_GETFL) | O_NONBLOCK); + + if (stderrChannel.pipe[1] != -1) { + qt_native_close(stderrChannel.pipe[1]); + stderrChannel.pipe[1] = -1; + } + if (stderrChannel.pipe[0] != -1) + ::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK); +} + +void K3bQProcessPrivate::execChild(const char *workingDir, char **path, char **argv, char **envp) +{ + ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored + + Q_Q(K3bQProcess); + + // copy the stdin socket + qt_native_dup2(stdinChannel.pipe[0], fileno(stdin)); + + // copy the stdout and stderr if asked to + if (processChannelMode != ::QProcess::ForwardedChannels) { + qt_native_dup2(stdoutChannel.pipe[1], fileno(stdout)); + + // merge stdout and stderr if asked to + if (processChannelMode == ::QProcess::MergedChannels) { + qt_native_dup2(fileno(stdout), fileno(stderr)); + } else { + qt_native_dup2(stderrChannel.pipe[1], fileno(stderr)); + } + } + + // make sure this fd is closed if execvp() succeeds + qt_native_close(childStartedPipe[0]); + ::fcntl(childStartedPipe[1], F_SETFD, FD_CLOEXEC); + + // enter the working directory + if (workingDir) + qt_native_chdir(workingDir); + + // this is a virtual call, and it base behavior is to do nothing. + q->setupChildProcess(); + + // execute the process + if (environment.isEmpty()) { + qt_native_execvp(argv[0], argv); + } else { + if (path) { + char **arg = path; + while (*arg) { + argv[0] = *arg; +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "K3bQProcessPrivate::execChild() searching / starting %s\n", argv[0]); +#endif + qt_native_execve(argv[0], argv, envp); + ++arg; + } + } else { +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "K3bQProcessPrivate::execChild() starting %s\n", argv[0]); +#endif + qt_native_execve(argv[0], argv, envp); + } + } + + // notify failure +#if defined (QPROCESS_DEBUG) + fprintf(stderr, "K3bQProcessPrivate::execChild() failed, notifying parent process\n"); +#endif + qt_native_write(childStartedPipe[1], "", 1); + qt_native_close(childStartedPipe[1]); + childStartedPipe[1] = -1; +} + +bool K3bQProcessPrivate::processStarted() +{ + char c; + int i = qt_native_read(childStartedPipe[0], &c, 1); + if (startupSocketNotifier) { + startupSocketNotifier->setEnabled(false); + startupSocketNotifier->deleteLater(); + startupSocketNotifier = 0; + } + qt_native_close(childStartedPipe[0]); + childStartedPipe[0] = -1; + +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::processStarted() == %s", i <= 0 ? "true" : "false"); +#endif + return i <= 0; +} + +qint64 K3bQProcessPrivate::bytesAvailableFromStdout() const +{ + size_t nbytes = 0; + qint64 available = 0; + if (::ioctl(stdoutChannel.pipe[0], FIONREAD, (char *) &nbytes) >= 0) + available = (qint64) *((int *) &nbytes); +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::bytesAvailableFromStdout() == %lld", available); +#endif + return available; +} + +qint64 K3bQProcessPrivate::bytesAvailableFromStderr() const +{ + size_t nbytes = 0; + qint64 available = 0; + if (::ioctl(stderrChannel.pipe[0], FIONREAD, (char *) &nbytes) >= 0) + available = (qint64) *((int *) &nbytes); +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::bytesAvailableFromStderr() == %lld", available); +#endif + return available; +} + +qint64 K3bQProcessPrivate::readFromStdout(char *data, qint64 maxlen) +{ + qint64 bytesRead = qt_native_read(stdoutChannel.pipe[0], data, maxlen); +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::readFromStdout(%p \"%s\", %lld) == %lld", + data, qt_prettyDebug(data, bytesRead, 16).constData(), maxlen, bytesRead); +#endif + return bytesRead; +} + +qint64 K3bQProcessPrivate::readFromStderr(char *data, qint64 maxlen) +{ + qint64 bytesRead = qt_native_read(stderrChannel.pipe[0], data, maxlen); +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::readFromStderr(%p \"%s\", %lld) == %lld", + data, qt_prettyDebug(data, bytesRead, 16).constData(), maxlen, bytesRead); +#endif + return bytesRead; +} + +static void qt_ignore_sigpipe() +{ + // Set to ignore SIGPIPE once only. + static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0); + if (atom.testAndSetRelaxed(0, 1)) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + qt_native_sigaction(SIGPIPE, &noaction, 0); + } +} + +qint64 K3bQProcessPrivate::writeToStdin(const char *data, qint64 maxlen) +{ + qt_ignore_sigpipe(); + + qint64 written = qt_native_write(stdinChannel.pipe[1], data, maxlen); +#if defined QPROCESS_DEBUG + qDebug("K3bQProcessPrivate::writeToStdin(%p \"%s\", %lld) == %lld", + data, qt_prettyDebug(data, maxlen, 16).constData(), maxlen, written); +#endif + return written; +} + +void K3bQProcessPrivate::terminateProcess() +{ +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::killProcess()"); +#endif + if (pid) + ::kill(pid_t(pid), SIGTERM); +} + +void K3bQProcessPrivate::killProcess() +{ +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::killProcess()"); +#endif + if (pid) + ::kill(pid_t(pid), SIGKILL); +} + +static int qt_native_select(fd_set *fdread, fd_set *fdwrite, int timeout) +{ + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int ret; + do { + ret = select(FD_SETSIZE, fdread, fdwrite, 0, timeout < 0 ? 0 : &tv); + } while (ret < 0 && (errno == EINTR)); + return ret; +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +bool K3bQProcessPrivate::waitForStarted(int msecs) +{ + Q_Q(K3bQProcess); + +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::waitForStarted(%d) waiting for child to start (fd = %d)", msecs, + childStartedPipe[0]); +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(childStartedPipe[0], &fds); + int ret; + do { + ret = qt_native_select(&fds, 0, msecs); + } while (ret < 0 && errno == EINTR); + if (ret == 0) { + processError = ::QProcess::Timedout; + q->setErrorString(K3bQProcess::tr("Process operation timed out")); +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::waitForStarted(%d) == false (timed out)", msecs); +#endif + return false; + } + + bool startedEmitted = _q_startupNotification(); +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::waitForStarted() == %s", startedEmitted ? "true" : "false"); +#endif + return startedEmitted; +} + +bool K3bQProcessPrivate::waitForReadyRead(int msecs) +{ + Q_Q(K3bQProcess); +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::waitForReadyRead(%d)", msecs); +#endif + + QTime stopWatch; + stopWatch.start(); + + forever { + fd_set fdread; + fd_set fdwrite; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + + if (processState == ::QProcess::Starting) + FD_SET(childStartedPipe[0], &fdread); + + if (stdoutChannel.pipe[0] != -1) + FD_SET(stdoutChannel.pipe[0], &fdread); + if (stderrChannel.pipe[0] != -1) + FD_SET(stderrChannel.pipe[0], &fdread); + + FD_SET(deathPipe[0], &fdread); + + if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1) + FD_SET(stdinChannel.pipe[1], &fdwrite); + + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + int ret = qt_native_select(&fdread, &fdwrite, timeout); + if (ret < 0) { + if (errno == EINTR) + continue; + break; + } + if (ret == 0) { + processError = ::QProcess::Timedout; + q->setErrorString(K3bQProcess::tr("Process operation timed out")); + return false; + } + + if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) { + if (!_q_startupNotification()) + return false; + } + + bool readyReadEmitted = false; + if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread)) { + bool canRead = _q_canReadStandardOutput(); + if (processChannel == ::QProcess::StandardOutput && canRead) + readyReadEmitted = true; + } + if (stderrChannel.pipe[0] != -1 && FD_ISSET(stderrChannel.pipe[0], &fdread)) { + bool canRead = _q_canReadStandardError(); + if (processChannel == ::QProcess::StandardError && canRead) + readyReadEmitted = true; + } + if (readyReadEmitted) + return true; + + if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite)) + _q_canWrite(); + + if (deathPipe[0] == -1 || FD_ISSET(deathPipe[0], &fdread)) { + if (_q_processDied()) + return false; + } + } + return false; +} + +bool K3bQProcessPrivate::waitForBytesWritten(int msecs) +{ + Q_Q(K3bQProcess); +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::waitForBytesWritten(%d)", msecs); +#endif + + if (processFlags&K3bQProcess::RawStdin) + return true; + + QTime stopWatch; + stopWatch.start(); + + while (!writeBuffer.isEmpty()) { + fd_set fdread; + fd_set fdwrite; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + + if (processState == ::QProcess::Starting) + FD_SET(childStartedPipe[0], &fdread); + + if (stdoutChannel.pipe[0] != -1) + FD_SET(stdoutChannel.pipe[0], &fdread); + if (stderrChannel.pipe[0] != -1) + FD_SET(stderrChannel.pipe[0], &fdread); + + FD_SET(deathPipe[0], &fdread); + + if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1) + FD_SET(stdinChannel.pipe[1], &fdwrite); + + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + int ret = qt_native_select(&fdread, &fdwrite, timeout); + if (ret < 0) { + if (errno == EINTR) + continue; + break; + } + + if (ret == 0) { + processError = ::QProcess::Timedout; + q->setErrorString(K3bQProcess::tr("Process operation timed out")); + return false; + } + + if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) { + if (!_q_startupNotification()) + return false; + } + + if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite)) + return _q_canWrite(); + + if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread)) + _q_canReadStandardOutput(); + + if (stderrChannel.pipe[0] != -1 && FD_ISSET(stderrChannel.pipe[0], &fdread)) + _q_canReadStandardError(); + + if (deathPipe[0] == -1 || FD_ISSET(deathPipe[0], &fdread)) { + if (_q_processDied()) + return false; + } + } + + return false; +} + +bool K3bQProcessPrivate::waitForFinished(int msecs) +{ + Q_Q(K3bQProcess); +#if defined (QPROCESS_DEBUG) + qDebug("K3bQProcessPrivate::waitForFinished(%d)", msecs); +#endif + + QTime stopWatch; + stopWatch.start(); + + forever { + fd_set fdread; + fd_set fdwrite; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + + if (processState == ::QProcess::Starting) + FD_SET(childStartedPipe[0], &fdread); + + if (stdoutChannel.pipe[0] != -1) + FD_SET(stdoutChannel.pipe[0], &fdread); + if (stderrChannel.pipe[0] != -1) + FD_SET(stderrChannel.pipe[0], &fdread); + + if (processState == ::QProcess::Running) + FD_SET(deathPipe[0], &fdread); + + if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1) + FD_SET(stdinChannel.pipe[1], &fdwrite); + + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + int ret = qt_native_select(&fdread, &fdwrite, timeout); + if (ret < 0) { + if (errno == EINTR) + continue; + break; + } + if (ret == 0) { + processError = ::QProcess::Timedout; + q->setErrorString(K3bQProcess::tr("Process operation timed out")); + return false; + } + + if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) { + if (!_q_startupNotification()) + return false; + } + if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite)) + _q_canWrite(); + + if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread)) + _q_canReadStandardOutput(); + + if (stderrChannel.pipe[0] != -1 && FD_ISSET(stderrChannel.pipe[0], &fdread)) + _q_canReadStandardError(); + + if (deathPipe[0] == -1 || FD_ISSET(deathPipe[0], &fdread)) { + if (_q_processDied()) + return true; + } + } + return false; +} + +bool K3bQProcessPrivate::waitForWrite(int msecs) +{ + fd_set fdwrite; + FD_ZERO(&fdwrite); + FD_SET(stdinChannel.pipe[1], &fdwrite); + + int ret; + do { + ret = qt_native_select(0, &fdwrite, msecs < 0 ? 0 : msecs) == 1; + } while (ret < 0 && errno == EINTR); + return ret == 1; +} + +void K3bQProcessPrivate::findExitCode() +{ + Q_Q(K3bQProcess); + processManager()->remove(q); +} + +bool K3bQProcessPrivate::waitForDeadChild() +{ + Q_Q(K3bQProcess); + + // read a byte from the death pipe + char c; + qt_native_read(deathPipe[0], &c, 1); + + // check if our process is dead + int exitStatus; + pid_t waitResult = 0; + do { + waitResult = waitpid(pid_t(pid), &exitStatus, WNOHANG); + } while ((waitResult == -1 && errno == EINTR)); + if (waitResult > 0) { + processManager()->remove(q); + crashed = !WIFEXITED(exitStatus); + exitCode = WEXITSTATUS(exitStatus); +#if defined QPROCESS_DEBUG + qDebug() << "K3bQProcessPrivate::waitForDeadChild() dead with exitCode" + << exitCode << ", crashed?" << crashed; +#endif + return true; + } +#if defined QPROCESS_DEBUG + qDebug() << "K3bQProcessPrivate::waitForDeadChild() not dead!"; +#endif + return false; +} + +void K3bQProcessPrivate::_q_notified() +{ +} + +/*! \internal + */ +bool K3bQProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid) +{ + processManager()->start(); + + QByteArray encodedWorkingDirectory = QFile::encodeName(workingDirectory); + + // To catch the startup of the child + int startedPipe[2]; + ::pipe(startedPipe); + // To communicate the pid of the child + int pidPipe[2]; + ::pipe(pidPipe); + + pid_t childPid = qt_fork(); + if (childPid == 0) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + qt_native_sigaction(SIGPIPE, &noaction, 0); + + ::setsid(); + + qt_native_close(startedPipe[0]); + qt_native_close(pidPipe[0]); + + pid_t doubleForkPid = qt_fork(); + if (doubleForkPid == 0) { + ::fcntl(startedPipe[1], F_SETFD, FD_CLOEXEC); + qt_native_close(pidPipe[1]); + + if (!encodedWorkingDirectory.isEmpty()) + qt_native_chdir(encodedWorkingDirectory.constData()); + + char **argv = new char *[arguments.size() + 2]; + for (int i = 0; i < arguments.size(); ++i) { +#ifdef Q_OS_MAC + argv[i + 1] = ::strdup(arguments.at(i).toUtf8().constData()); +#else + argv[i + 1] = ::strdup(arguments.at(i).toLocal8Bit().constData()); +#endif + } + argv[arguments.size() + 1] = 0; + + if (!program.contains(QLatin1Char('/'))) { + const QString path = QString::fromLocal8Bit(::getenv("PATH")); + if (!path.isEmpty()) { + QStringList pathEntries = path.split(QLatin1Char(':')); + for (int k = 0; k < pathEntries.size(); ++k) { + QByteArray tmp = QFile::encodeName(pathEntries.at(k)); + if (!tmp.endsWith('/')) tmp += '/'; + tmp += QFile::encodeName(program); + argv[0] = tmp.data(); + qt_native_execv(argv[0], argv); + } + } + } else { + QByteArray tmp = QFile::encodeName(program); + argv[0] = tmp.data(); + qt_native_execv(argv[0], argv); + } + + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + qt_native_sigaction(SIGPIPE, &noaction, 0); + + // '\1' means execv failed + char c = '\1'; + qt_native_write(startedPipe[1], &c, 1); + qt_native_close(startedPipe[1]); + ::_exit(1); + } else if (doubleForkPid == -1) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + qt_native_sigaction(SIGPIPE, &noaction, 0); + + // '\2' means internal error + char c = '\2'; + qt_native_write(startedPipe[1], &c, 1); + } + + qt_native_close(startedPipe[1]); + qt_native_write(pidPipe[1], (const char *)&doubleForkPid, sizeof(pid_t)); + qt_native_chdir("/"); + ::_exit(1); + } + + qt_native_close(startedPipe[1]); + qt_native_close(pidPipe[1]); + + if (childPid == -1) { + qt_native_close(startedPipe[0]); + qt_native_close(pidPipe[0]); + return false; + } + + char reply = '\0'; + int startResult = qt_native_read(startedPipe[0], &reply, 1); + int result; + qt_native_close(startedPipe[0]); + while (::waitpid(childPid, &result, 0) == -1 && errno == EINTR) + { } + bool success = (startResult != -1 && reply == '\0'); + if (success && pid) { + pid_t actualPid = 0; + if (qt_native_read(pidPipe[0], (char *)&actualPid, sizeof(pid_t)) == sizeof(pid_t)) { + *pid = actualPid; + } else { + *pid = 0; + } + } + qt_native_close(pidPipe[0]); + return success; +} + +void K3bQProcessPrivate::initializeProcessManager() +{ + (void) processManager(); +} + +//QT_END_NAMESPACE + +#include "k3bqprocess_unix.moc" + +#endif // QT_NO_PROCESS diff --git a/libk3b/tools/qprocess/k3bqprocess_win.cpp b/libk3b/tools/qprocess/k3bqprocess_win.cpp new file mode 100644 index 000000000..ea868875f --- /dev/null +++ b/libk3b/tools/qprocess/k3bqprocess_win.cpp @@ -0,0 +1,909 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprocess.h" +#include "qprocess_p.h" +#include "qwindowspipewriter_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "private/qfsfileengine_p.h" // for longFileName and win95FileName + + +#ifndef QT_NO_PROCESS + +QT_BEGIN_NAMESPACE + +//#define QPROCESS_DEBUG + +#define NOTIFYTIMEOUT 100 + +static void qt_create_pipe(Q_PIPE *pipe, bool in) +{ + // Open the pipes. Make non-inheritable copies of input write and output + // read handles to avoid non-closable handles (this is done by the + // DuplicateHandle() call). + +#if !defined(Q_OS_WINCE) + SECURITY_ATTRIBUTES secAtt = { sizeof( SECURITY_ATTRIBUTES ), NULL, TRUE }; + + HANDLE tmpHandle; + if (in) { // stdin + if (!CreatePipe(&pipe[0], &tmpHandle, &secAtt, 1024 * 1024)) + return; + if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), + &pipe[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) + return; + } else { // stdout or stderr + if (!CreatePipe(&tmpHandle, &pipe[1], &secAtt, 1024 * 1024)) + return; + if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, GetCurrentProcess(), + &pipe[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) + return; + } + + CloseHandle(tmpHandle); +#else + Q_UNUSED(pipe); + Q_UNUSED(in); +#endif +} + +/* + Create the pipes to a QProcessPrivate::Channel. + + This function must be called in order: stdin, stdout, stderr +*/ +bool QProcessPrivate::createChannel(Channel &channel) +{ + Q_Q(QProcess); + + if (&channel == &stderrChannel && processChannelMode == QProcess::MergedChannels) { + return DuplicateHandle(GetCurrentProcess(), stdoutChannel.pipe[1], GetCurrentProcess(), + &stderrChannel.pipe[1], 0, TRUE, DUPLICATE_SAME_ACCESS); + } + + if (channel.type == Channel::Normal) { + // we're piping this channel to our own process + qt_create_pipe(channel.pipe, &channel == &stdinChannel); + + return true; + } else if (channel.type == Channel::Redirect) { + // we're redirecting the channel to/from a file + SECURITY_ATTRIBUTES secAtt = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; + + if (&channel == &stdinChannel) { + // try to open in read-only mode + channel.pipe[1] = INVALID_Q_PIPE; + QT_WA({ + channel.pipe[0] = + CreateFileW((TCHAR*)QFSFileEnginePrivate::longFileName(channel.file).utf16(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &secAtt, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + }, { + channel.pipe[0] = + CreateFileA(QFSFileEnginePrivate::win95Name(channel.file), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &secAtt, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + }); + if (channel.pipe[0] != INVALID_Q_PIPE) + return true; + + q->setErrorString(QProcess::tr("Could not open input redirection for reading")); + } else { + // open in write mode + channel.pipe[0] = INVALID_Q_PIPE; + DWORD creation; + if (channel.append) + creation = OPEN_ALWAYS; + else + creation = CREATE_ALWAYS; + + QT_WA({ + channel.pipe[1] = + CreateFileW((TCHAR*)QFSFileEnginePrivate::longFileName(channel.file).utf16(), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &secAtt, + creation, + FILE_ATTRIBUTE_NORMAL, + NULL); + }, { + channel.pipe[1] = + CreateFileA(QFSFileEnginePrivate::win95Name(channel.file), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &secAtt, + creation, + FILE_ATTRIBUTE_NORMAL, + NULL); + }); + if (channel.pipe[1] != INVALID_Q_PIPE) { + if (channel.append) { + SetFilePointer(channel.pipe[1], 0, NULL, FILE_END); + } + return true; + } + + q->setErrorString(QProcess::tr("Could not open output redirection for writing")); + } + + // could not open file + processError = QProcess::FailedToStart; + emit q->error(processError); + cleanup(); + return false; + } else { + Q_ASSERT_X(channel.process, "QProcess::start", "Internal error"); + + Channel *source; + Channel *sink; + + if (channel.type == Channel::PipeSource) { + // we are the source + source = &channel; + sink = &channel.process->stdinChannel; + + if (source->pipe[1] != INVALID_Q_PIPE) { + // already constructed by the sink + // make it inheritable + HANDLE tmpHandle = source->pipe[1]; + if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, + GetCurrentProcess(), &source->pipe[1], + 0, TRUE, DUPLICATE_SAME_ACCESS)) + return false; + + CloseHandle(tmpHandle); + return true; + } + + Q_ASSERT(source == &stdoutChannel); + Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink); + + qt_create_pipe(source->pipe, /* in = */ false); // source is stdout + sink->pipe[0] = source->pipe[0]; + source->pipe[0] = INVALID_Q_PIPE; + + return true; + } else { + // we are the sink; + source = &channel.process->stdoutChannel; + sink = &channel; + + if (sink->pipe[0] != INVALID_Q_PIPE) { + // already constructed by the source + // make it inheritable + HANDLE tmpHandle = sink->pipe[0]; + if (!DuplicateHandle(GetCurrentProcess(), tmpHandle, + GetCurrentProcess(), &sink->pipe[0], + 0, TRUE, DUPLICATE_SAME_ACCESS)) + return false; + + CloseHandle(tmpHandle); + return true; + } + Q_ASSERT(sink == &stdinChannel); + Q_ASSERT(source->process == this && source->type == Channel::PipeSource); + + qt_create_pipe(sink->pipe, /* in = */ true); // sink is stdin + source->pipe[1] = sink->pipe[1]; + sink->pipe[1] = INVALID_Q_PIPE; + + return true; + } + } +} + +void QProcessPrivate::destroyPipe(Q_PIPE pipe[2]) +{ + if (pipe[0] == stdinChannel.pipe[0] && pipe[1] == stdinChannel.pipe[1] && pipeWriter) { + delete pipeWriter; + pipeWriter = 0; + } + + if (pipe[0] != INVALID_Q_PIPE) { + CloseHandle(pipe[0]); + pipe[0] = INVALID_Q_PIPE; + } + if (pipe[1] != INVALID_Q_PIPE) { + CloseHandle(pipe[1]); + pipe[1] = INVALID_Q_PIPE; + } +} + + +static QString qt_create_commandline(const QString &program, const QStringList &arguments) +{ + QString args; + if (!program.isEmpty()) { + QString programName = program; + if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1String(" "))) + programName = QLatin1String("\"") + programName + QLatin1String("\""); + programName.replace(QLatin1String("/"), QLatin1String("\\")); + + // add the prgram as the first arg ... it works better + args = programName + QLatin1String(" "); + } + + for (int i=0; i0 && tmp.at(i-1) == QLatin1Char('\\')) { + --i; + endQuote += QLatin1String("\\"); + } + args += QLatin1String(" \"") + tmp.left(i) + endQuote; + } else { + args += QLatin1Char(' ') + tmp; + } + } + return args; +} + +static QByteArray qt_create_environment(const QStringList &environment) +{ + QByteArray envlist; + if (!environment.isEmpty()) { + QStringList envStrings = environment; + int pos = 0; + // add PATH if necessary (for DLL loading) + if (envStrings.filter(QRegExp(QLatin1String("^PATH="),Qt::CaseInsensitive)).isEmpty()) { + QByteArray path = qgetenv("PATH"); + if (!path.isEmpty()) + envStrings.prepend(QString(QLatin1String("PATH=%1")).arg(QString::fromLocal8Bit(path))); + } + // add systemroot if needed + if (envStrings.filter(QRegExp(QLatin1String("^SystemRoot="),Qt::CaseInsensitive)).isEmpty()) { + QByteArray systemRoot = qgetenv("SystemRoot"); + if (!systemRoot.isEmpty()) + envStrings.prepend(QString(QLatin1String("SystemRoot=%1")).arg(QString::fromLocal8Bit(systemRoot))); + } +#ifdef UNICODE + if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) { + for (QStringList::ConstIterator it = envStrings.constBegin(); it != envStrings.constEnd(); ++it) { + QString tmp = *it; + uint tmpSize = sizeof(TCHAR) * (tmp.length()+1); + envlist.resize(envlist.size() + tmpSize); + memcpy(envlist.data()+pos, tmp.utf16(), tmpSize); + pos += tmpSize; + } + // add the 2 terminating 0 (actually 4, just to be on the safe side) + envlist.resize( envlist.size()+4 ); + envlist[pos++] = 0; + envlist[pos++] = 0; + envlist[pos++] = 0; + envlist[pos++] = 0; + } else +#endif // UNICODE + { + for (QStringList::ConstIterator it = envStrings.constBegin(); it != envStrings.constEnd(); it++) { + QByteArray tmp = (*it).toLocal8Bit(); + uint tmpSize = tmp.length() + 1; + envlist.resize(envlist.size() + tmpSize); + memcpy(envlist.data()+pos, tmp.data(), tmpSize); + pos += tmpSize; + } + // add the terminating 0 (actually 2, just to be on the safe side) + envlist.resize(envlist.size()+2); + envlist[pos++] = 0; + envlist[pos++] = 0; + } + } + return envlist; +} + +void QProcessPrivate::startProcess() +{ + Q_Q(QProcess); + + bool success = false; + + if (pid) { + CloseHandle(pid->hThread); + CloseHandle(pid->hProcess); + delete pid; + pid = 0; + } + pid = new PROCESS_INFORMATION; + memset(pid, 0, sizeof(PROCESS_INFORMATION)); + + q->setProcessState(QProcess::Starting); + + if (!createChannel(stdinChannel) || + !createChannel(stdoutChannel) || + !createChannel(stderrChannel)) + return; + +#if defined(Q_OS_WINCE) + QString args = qt_create_commandline(QString(), arguments); +#else + QString args = qt_create_commandline(program, arguments); + QByteArray envlist = qt_create_environment(environment); +#endif + +#if defined QPROCESS_DEBUG + qDebug("Creating process"); + qDebug(" program : [%s]", program.toLatin1().constData()); + qDebug(" args : %s", args.toLatin1().constData()); + qDebug(" pass environment : %s", environment.isEmpty() ? "no" : "yes"); +#endif + + DWORD dwCreationFlags = 0; + if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) + dwCreationFlags |= CREATE_NO_WINDOW; + +#ifdef UNICODE + if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) { +#if defined(Q_OS_WINCE) + QString fullPathProgram = program; + if (!QDir::isAbsolutePath(fullPathProgram)) + fullPathProgram = QFileInfo(fullPathProgram).absoluteFilePath(); + fullPathProgram.replace(QLatin1String("/"), QLatin1String("\\")); + success = CreateProcessW((WCHAR*)fullPathProgram.utf16(), + (WCHAR*)args.utf16(), + 0, 0, false, 0, 0, 0, 0, pid); +#else + dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT; + STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + 0, 0, 0, + STARTF_USESTDHANDLES, + 0, 0, 0, + stdinChannel.pipe[0], stdoutChannel.pipe[1], stderrChannel.pipe[1] + }; + success = CreateProcessW(0, (WCHAR*)args.utf16(), + 0, 0, TRUE, dwCreationFlags, + environment.isEmpty() ? 0 : envlist.data(), + workingDirectory.isEmpty() ? 0 + : (WCHAR*)QDir::toNativeSeparators(workingDirectory).utf16(), + &startupInfo, pid); +#endif + } else +#endif // UNICODE + { +#ifndef Q_OS_WINCE + STARTUPINFOA startupInfo = { sizeof( STARTUPINFOA ), 0, 0, 0, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + 0, 0, 0, + STARTF_USESTDHANDLES, + 0, 0, 0, + stdinChannel.pipe[0], stdoutChannel.pipe[1], stderrChannel.pipe[1] + }; + + success = CreateProcessA(0, args.toLocal8Bit().data(), + 0, 0, TRUE, dwCreationFlags, environment.isEmpty() ? 0 : envlist.data(), + workingDirectory.isEmpty() ? 0 + : QDir::toNativeSeparators(workingDirectory).toLocal8Bit().data(), + &startupInfo, pid); +#endif // Q_OS_WINCE + } +#ifndef Q_OS_WINCE + if (stdinChannel.pipe[0] != INVALID_Q_PIPE) { + CloseHandle(stdinChannel.pipe[0]); + stdinChannel.pipe[0] = INVALID_Q_PIPE; + } + if (stdoutChannel.pipe[1] != INVALID_Q_PIPE) { + CloseHandle(stdoutChannel.pipe[1]); + stdoutChannel.pipe[1] = INVALID_Q_PIPE; + } + if (stderrChannel.pipe[1] != INVALID_Q_PIPE) { + CloseHandle(stderrChannel.pipe[1]); + stderrChannel.pipe[1] = INVALID_Q_PIPE; + } +#endif // Q_OS_WINCE + + if (!success) { + cleanup(); + processError = QProcess::FailedToStart; + q->setErrorString(QProcess::tr("Process failed to start")); + emit q->error(processError); + q->setProcessState(QProcess::NotRunning); + return; + } + + q->setProcessState(QProcess::Running); + // User can call kill()/terminate() from the stateChanged() slot + // so check before proceeding + if (!pid) + return; + + if (threadData->eventDispatcher) { + processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q); + QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_processDied())); + processFinishedNotifier->setEnabled(true); + notifier = new QTimer(q); + QObject::connect(notifier, SIGNAL(timeout()), q, SLOT(_q_notified())); + notifier->start(NOTIFYTIMEOUT); + } + + // give the process a chance to start ... + Sleep(SLEEPMIN*2); + _q_startupNotification(); +} + +bool QProcessPrivate::processStarted() +{ + return processState == QProcess::Running; +} + +qint64 QProcessPrivate::bytesAvailableFromStdout() const +{ + if (stdoutChannel.pipe[0] == INVALID_Q_PIPE) + return 0; + + DWORD bytesAvail = 0; +#if !defined(Q_OS_WINCE) + PeekNamedPipe(stdoutChannel.pipe[0], 0, 0, 0, &bytesAvail, 0); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::bytesAvailableFromStdout() == %d", bytesAvail); +#endif + if (processChannelMode == QProcess::ForwardedChannels && bytesAvail > 0) { + QByteArray buf(bytesAvail, 0); + DWORD bytesRead = 0; + if (ReadFile(stdoutChannel.pipe[0], buf.data(), buf.size(), &bytesRead, 0) && bytesRead > 0) { + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + if (hStdout) { + DWORD bytesWritten = 0; + WriteFile(hStdout, buf.data(), bytesRead, &bytesWritten, 0); + } + } + bytesAvail = 0; + } +#endif + return bytesAvail; +} + +qint64 QProcessPrivate::bytesAvailableFromStderr() const +{ + if (stderrChannel.pipe[0] == INVALID_Q_PIPE) + return 0; + + DWORD bytesAvail = 0; +#if !defined(Q_OS_WINCE) + PeekNamedPipe(stderrChannel.pipe[0], 0, 0, 0, &bytesAvail, 0); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::bytesAvailableFromStderr() == %d", bytesAvail); +#endif + if (processChannelMode == QProcess::ForwardedChannels && bytesAvail > 0) { + QByteArray buf(bytesAvail, 0); + DWORD bytesRead = 0; + if (ReadFile(stderrChannel.pipe[0], buf.data(), buf.size(), &bytesRead, 0) && bytesRead > 0) { + HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE); + if (hStderr) { + DWORD bytesWritten = 0; + WriteFile(hStderr, buf.data(), bytesRead, &bytesWritten, 0); + } + } + bytesAvail = 0; + } +#endif + return bytesAvail; +} + +qint64 QProcessPrivate::readFromStdout(char *data, qint64 maxlen) +{ + DWORD read = qMin(maxlen, bytesAvailableFromStdout()); + DWORD bytesRead = 0; + + if (read > 0 && !ReadFile(stdoutChannel.pipe[0], data, read, &bytesRead, 0)) + return -1; + return bytesRead; +} + +qint64 QProcessPrivate::readFromStderr(char *data, qint64 maxlen) +{ + DWORD read = qMin(maxlen, bytesAvailableFromStderr()); + DWORD bytesRead = 0; + + if (read > 0 && !ReadFile(stderrChannel.pipe[0], data, read, &bytesRead, 0)) + return -1; + return bytesRead; +} + + +static BOOL CALLBACK qt_terminateApp(HWND hwnd, LPARAM procId) +{ + DWORD currentProcId = 0; + GetWindowThreadProcessId(hwnd, ¤tProcId); + if (currentProcId == (DWORD)procId) + PostMessage(hwnd, WM_CLOSE, 0, 0); + + return TRUE; +} + +void QProcessPrivate::terminateProcess() +{ + if (pid) { + EnumWindows(qt_terminateApp, (LPARAM)pid->dwProcessId); + PostThreadMessage(pid->dwThreadId, WM_CLOSE, 0, 0); + } +} + +void QProcessPrivate::killProcess() +{ + if (pid) + TerminateProcess(pid->hProcess, 0xf291); +} + +bool QProcessPrivate::waitForStarted(int) +{ + Q_Q(QProcess); + + if (processStarted()) + return true; + + if (processError == QProcess::FailedToStart) + return false; + + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + +bool QProcessPrivate::waitForReadyRead(int msecs) +{ + Q_Q(QProcess); + +#if defined(Q_OS_WINCE) + processError = QProcess::ReadError; + q->setErrorString(QProcess::tr("Error reading from process")); + emit q->error(processError); + return false; +#endif + + QIncrementalSleepTimer timer(msecs); + + forever { + if (!writeBuffer.isEmpty() && !_q_canWrite()) + return false; + if (pipeWriter && pipeWriter->waitForWrite(0)) + timer.resetIncrements(); + bool readyReadEmitted = false; + if (bytesAvailableFromStdout() != 0) { + readyReadEmitted = _q_canReadStandardOutput() ? true : readyReadEmitted; + timer.resetIncrements(); + } + + if (bytesAvailableFromStderr() != 0) { + readyReadEmitted = _q_canReadStandardError() ? true : readyReadEmitted; + timer.resetIncrements(); + } + + if (readyReadEmitted) + return true; + + if (!pid) + return false; + if (WaitForSingleObject(pid->hProcess, 0) == WAIT_OBJECT_0) { + // find the return value if there is noew data to read + _q_processDied(); + return false; + } + + Sleep(timer.nextSleepTime()); + if (timer.hasTimedOut()) + break; + } + + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + +bool QProcessPrivate::waitForBytesWritten(int msecs) +{ + Q_Q(QProcess); + +#if defined(Q_OS_WINCE) + processError = QProcess::ReadError; + q->setErrorString(QProcess::tr("Error reading from process")); + emit q->error(processError); + return false; +#endif + + QIncrementalSleepTimer timer(msecs); + + forever { + // Check if we have any data pending: the pipe writer has + // bytes waiting to written, or it has written data since the + // last time we called pipeWriter->waitForWrite(). + bool pendingDataInPipe = pipeWriter && (pipeWriter->bytesToWrite() || pipeWriter->hadWritten()); + + // If we don't have pending data, and our write buffer is + // empty, we fail. + if (!pendingDataInPipe && writeBuffer.isEmpty()) + return false; + + // If we don't have pending data and we do have data in our + // write buffer, try to flush that data over to the pipe + // writer. Fail on error. + if (!pendingDataInPipe) { + if (!_q_canWrite()) + return false; + } + + // Wait for the pipe writer to acknowledge that it has + // written. This will succeed if either the pipe writer has + // already written the data, or if it manages to write data + // within the given timeout. If the write buffer was non-empty + // and the pipeWriter is now dead, that means _q_canWrite() + // destroyed the writer after it successfully wrote the last + // batch. + if (!pipeWriter || pipeWriter->waitForWrite(0)) + return true; + + // If we wouldn't write anything, check if we can read stdout. + if (bytesAvailableFromStdout() != 0) { + _q_canReadStandardOutput(); + timer.resetIncrements(); + } + + // Check if we can read stderr. + if (bytesAvailableFromStderr() != 0) { + _q_canReadStandardError(); + timer.resetIncrements(); + } + + // Check if the process died while reading. + if (!pid) + return false; + + // Wait for the process to signal any change in its state, + // such as incoming data, or if the process died. + if (WaitForSingleObject(pid->hProcess, 0) == WAIT_OBJECT_0) { + _q_processDied(); + return false; + } + + // Only wait for as long as we've been asked. + if (timer.hasTimedOut()) + break; + } + + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + + +bool QProcessPrivate::waitForFinished(int msecs) +{ + Q_Q(QProcess); +#if defined QPROCESS_DEBUG + qDebug("QProcessPrivate::waitForFinished(%d)", msecs); +#endif + + QIncrementalSleepTimer timer(msecs); + + forever { + if (!writeBuffer.isEmpty() && !_q_canWrite()) + return false; + if (pipeWriter && pipeWriter->waitForWrite(0)) + timer.resetIncrements(); + + if (bytesAvailableFromStdout() != 0) { + _q_canReadStandardOutput(); + timer.resetIncrements(); + } + + if (bytesAvailableFromStderr() != 0) { + _q_canReadStandardError(); + timer.resetIncrements(); + } + + if (!pid) + return true; + + if (WaitForSingleObject(pid->hProcess, timer.nextSleepTime()) == WAIT_OBJECT_0) { + _q_processDied(); + return true; + } + + if (timer.hasTimedOut()) + break; + } + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + + +void QProcessPrivate::findExitCode() +{ + DWORD theExitCode; + if (GetExitCodeProcess(pid->hProcess, &theExitCode)) { + exitCode = theExitCode; + //### for now we assume a crash if exit code is less than -1 or the magic number + crashed = (exitCode == 0xf291 || (int)exitCode < 0); + } +} + +void QProcessPrivate::flushPipeWriter() +{ + if (pipeWriter && pipeWriter->bytesToWrite() > 0) { + pipeWriter->waitForWrite(ULONG_MAX); + } +} + +qint64 QProcessPrivate::pipeWriterBytesToWrite() const +{ + return pipeWriter ? pipeWriter->bytesToWrite() : qint64(0); +} + +qint64 QProcessPrivate::writeToStdin(const char *data, qint64 maxlen) +{ + Q_Q(QProcess); + +#if defined(Q_OS_WINCE) + processError = QProcess::WriteError; + q->setErrorString(QProcess::tr("Error writing to process")); + emit q->error(processError); + return -1; +#endif + + if (!pipeWriter) { + pipeWriter = new QWindowsPipeWriter(stdinChannel.pipe[1], q); + pipeWriter->start(); + } + + return pipeWriter->write(data, maxlen); +} + +bool QProcessPrivate::waitForWrite(int msecs) +{ + Q_Q(QProcess); + + if (!pipeWriter || pipeWriter->waitForWrite(msecs)) + return true; + + processError = QProcess::Timedout; + q->setErrorString(QProcess::tr("Process operation timed out")); + return false; +} + +void QProcessPrivate::_q_notified() +{ + notifier->stop(); + + if (!writeBuffer.isEmpty() && (!pipeWriter || pipeWriter->waitForWrite(0))) + _q_canWrite(); + + if (bytesAvailableFromStdout()) + _q_canReadStandardOutput(); + + if (bytesAvailableFromStderr()) + _q_canReadStandardError(); + + if (processState != QProcess::NotRunning) + notifier->start(NOTIFYTIMEOUT); +} + +bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDir, qint64 *pid) +{ +#if defined(Q_OS_WINCE) + Q_UNUSED(workingDir); + QString args = qt_create_commandline(QString(), arguments); +#else + QString args = qt_create_commandline(program, arguments); +#endif + + bool success = false; + + PROCESS_INFORMATION pinfo; + +#ifdef UNICODE + if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) { +#if defined(Q_OS_WINCE) + QString fullPathProgram = program; + if (!QDir::isAbsolutePath(fullPathProgram)) + fullPathProgram.prepend(QDir::currentPath().append(QLatin1String("/"))); + fullPathProgram.replace(QLatin1String("/"), QLatin1String("\\")); + success = CreateProcessW((WCHAR*)fullPathProgram.utf16(), + (WCHAR*)args.utf16(), + 0, 0, false, CREATE_NEW_CONSOLE, 0, 0, 0, &pinfo); +#else + STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + success = CreateProcessW(0, (WCHAR*)args.utf16(), + 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, 0, + workingDir.isEmpty() ? (const WCHAR *)0 : (const WCHAR *)workingDir.utf16(), + &startupInfo, &pinfo); +#endif + } else +#endif // UNICODE + { +#ifndef Q_OS_WINCE + STARTUPINFOA startupInfo = { sizeof( STARTUPINFOA ), 0, 0, 0, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + success = CreateProcessA(0, args.toLocal8Bit().data(), + 0, 0, FALSE, CREATE_NEW_CONSOLE, 0, + workingDir.isEmpty() ? (LPCSTR)0 : workingDir.toLocal8Bit().constData(), + &startupInfo, &pinfo); +#endif // Q_OS_WINCE + } + + if (success) { + CloseHandle(pinfo.hThread); + CloseHandle(pinfo.hProcess); + if (pid) + *pid = pinfo.dwProcessId; + } + + return success; +} + +QT_END_NAMESPACE + +#endif // QT_NO_PROCESS diff --git a/libk3b/tools/qprocess/qringbuffer_p.h b/libk3b/tools/qprocess/qringbuffer_p.h new file mode 100644 index 000000000..4b5ea4848 --- /dev/null +++ b/libk3b/tools/qprocess/qringbuffer_p.h @@ -0,0 +1,319 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRINGBUFFER_P_H +#define QRINGBUFFER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of a number of Qt sources files. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_CORE_EXPORT QRingBuffer +{ +public: + inline QRingBuffer(int growth = 4096) : basicBlockSize(growth) { + buffers << QByteArray(); + clear(); + } + + inline int nextDataBlockSize() const { + return (tailBuffer == 0 ? tail : buffers.first().size()) - head; + } + + inline const char *readPointer() const { + return buffers.isEmpty() ? 0 : (buffers.first().constData() + head); + } + + inline void free(int bytes) { + bufferSize -= bytes; + if (bufferSize < 0) + bufferSize = 0; + + for (;;) { + int nextBlockSize = nextDataBlockSize(); + if (bytes < nextBlockSize) { + head += bytes; + if (head == tail && tailBuffer == 0) + head = tail = 0; + break; + } + + bytes -= nextBlockSize; + if (buffers.count() == 1) { + if (buffers.at(0).size() != basicBlockSize) + buffers[0].resize(basicBlockSize); + head = tail = 0; + tailBuffer = 0; + break; + } + + buffers.removeAt(0); + --tailBuffer; + head = 0; + } + } + + inline char *reserve(int bytes) { + bufferSize += bytes; + + // if there is already enough space, simply return. + if (tail + bytes <= buffers.at(tailBuffer).size()) { + char *writePtr = buffers[tailBuffer].data() + tail; + tail += bytes; + return writePtr; + } + + // if our buffer isn't half full yet, simply resize it. + if (tail < buffers.at(tailBuffer).size() / 2) { + buffers[tailBuffer].resize(tail + bytes); + char *writePtr = buffers[tailBuffer].data() + tail; + tail += bytes; + return writePtr; + } + + // shrink this buffer to its current size + buffers[tailBuffer].resize(tail); + + // create a new QByteArray with the right size + buffers << QByteArray(); + ++tailBuffer; + buffers[tailBuffer].resize(qMax(basicBlockSize, bytes)); + tail = bytes; + return buffers[tailBuffer].data(); + } + + inline void truncate(int pos) { + if (pos < size()) + chop(size() - pos); + } + + inline void chop(int bytes) { + bufferSize -= bytes; + if (bufferSize < 0) + bufferSize = 0; + + for (;;) { + // special case: head and tail are in the same buffer + if (tailBuffer == 0) { + tail -= bytes; + if (tail <= head) + tail = head = 0; + return; + } + + if (bytes <= tail) { + tail -= bytes; + return; + } + + bytes -= tail; + buffers.removeAt(tailBuffer); + + --tailBuffer; + tail = buffers.at(tailBuffer).size(); + } + } + + inline bool isEmpty() const { + return tailBuffer == 0 && tail == 0; + } + + inline int getChar() { + if (isEmpty()) + return -1; + char c = *readPointer(); + free(1); + return int(uchar(c)); + } + + inline void putChar(char c) { + char *ptr = reserve(1); + *ptr = c; + } + + inline void ungetChar(char c) { + --head; + if (head < 0) { + buffers.prepend(QByteArray()); + buffers[0].resize(basicBlockSize); + head = basicBlockSize - 1; + ++tailBuffer; + } + buffers[0][head] = c; + ++bufferSize; + } + + inline int size() const { + return bufferSize; + } + + inline void clear() { + if(!buffers.isEmpty()) { + QByteArray tmp = buffers[0]; + buffers.clear(); + buffers << tmp; + if (buffers.at(0).size() != basicBlockSize) + buffers[0].resize(basicBlockSize); + } + head = tail = 0; + tailBuffer = 0; + bufferSize = 0; + } + + inline int indexOf(char c) const { + int index = 0; + for (int i = 0; i < buffers.size(); ++i) { + int start = 0; + int end = buffers.at(i).size(); + + if (i == 0) + start = head; + if (i == tailBuffer) + end = tail; + const char *ptr = buffers.at(i).data() + start; + for (int j = start; j < end; ++j) { + if (*ptr++ == c) + return index; + ++index; + } + } + return -1; + } + + inline int read(char *data, int maxLength) { + int bytesToRead = qMin(size(), maxLength); + int readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readPointer(); + int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, nextDataBlockSize()); + if (data) + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + free(bytesToReadFromThisBlock); + } + return readSoFar; + } + + inline QByteArray read(int maxLength) { + QByteArray tmp; + tmp.resize(qMin(maxLength, size())); + read(tmp.data(), tmp.size()); + return tmp; + } + + inline QByteArray readAll() { + return read(size()); + } + + inline QByteArray peek(int maxLength) const { + int bytesToRead = qMin(size(), maxLength); + if(maxLength <= 0) + return QByteArray(); + QByteArray ret; + ret.resize(bytesToRead); + int readSoFar = 0; + for (int i = 0; readSoFar < bytesToRead && i < buffers.size(); ++i) { + int start = 0; + int end = buffers.at(i).size(); + if (i == 0) + start = head; + if (i == tailBuffer) + end = tail; + const int len = qMin(ret.size()-readSoFar, end-start); + memcpy(ret.data()+readSoFar, buffers.at(i).constData()+start, len); + readSoFar += len; + } + Q_ASSERT(readSoFar == ret.size()); + return ret; + } + + inline int skip(int length) { + return read(0, length); + } + + inline int readLine(char *data, int maxLength) { + int index = indexOf('\n'); + if (index == -1) + return read(data, maxLength); + if (maxLength <= 0) + return -1; + + int readSoFar = 0; + while (readSoFar < index + 1 && readSoFar < maxLength - 1) { + int bytesToRead = qMin((index + 1) - readSoFar, nextDataBlockSize()); + bytesToRead = qMin(bytesToRead, (maxLength - 1) - readSoFar); + memcpy(data + readSoFar, readPointer(), bytesToRead); + readSoFar += bytesToRead; + free(bytesToRead); + } + + // Terminate it. + data[readSoFar] = '\0'; + return readSoFar; + } + + inline bool canReadLine() const { + return indexOf('\n') != -1; + } + +private: + QList buffers; + int head, tail; + int tailBuffer; + int basicBlockSize; + int bufferSize; +}; + +QT_END_NAMESPACE + +#endif // QRINGBUFFER_P_H diff --git a/libk3bdevice/k3bdevice.cpp b/libk3bdevice/k3bdevice.cpp index e45f3e34e..7b25b972d 100644 --- a/libk3bdevice/k3bdevice.cpp +++ b/libk3bdevice/k3bdevice.cpp @@ -1,3735 +1,3735 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bdevice.h" #include "k3bdeviceglobals.h" #include "k3btrack.h" #include "k3btoc.h" #include "k3bdiskinfo.h" #include "k3bdiskinfo_p.h" #include "k3bmmc.h" #include "k3bscsicommand.h" #include "k3bcrc.h" #include "config-k3b.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_LINUX #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,70) typedef unsigned char u8; #endif #undef __STRICT_ANSI__ #include #define __STRICT_ANSI__ #endif // Q_OS_LINUX #ifdef Q_OS_FREEBSD #include #include #define CD_FRAMESIZE_RAW 2352 #endif #ifdef Q_OS_NETBSD #include #endif #ifdef HAVE_RESMGR extern "C" { #include } #endif // // Very evil hacking: force the speed values to be acurate // as long as "they" do not introduce other "broken" DVD // speeds like 2.4 this works fine // namespace { int fixupDvdWritingSpeed( int speed ) { // // Some writers report their speeds in 1000 bytes per second instead of 1024. // if( speed % 1385 == 0 ) return speed; else if( speed % 1352 == 0 ) return speed*1385/1352; // has to be 2.4x speed else return 3324; } } #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) int K3b::Device::openDevice( const char* name, bool write ) { int fd = -1; int flags = O_NONBLOCK; if( write ) flags |= O_RDWR; else flags |= O_RDONLY; #ifdef HAVE_RESMGR // first try resmgr fd = ::rsm_open_device( name, flags ); // kDebug() << "(K3b::Device::Device) resmgr open: " << fd; #endif if( fd < 0 ) fd = ::open( name, flags ); if( fd < 0 ) { kDebug() << "(K3b::Device::Device) could not open device " << name << ( write ? " for writing" : " for reading" ) << endl; kDebug() << " (" << strerror(errno) << ")"; fd = -1; // at least open it read-only (which is sufficient for kernels < 2.6.8 anyway) if( write ) return openDevice( name, false ); } return fd; } #endif class K3b::Device::Device::Private { public: Private() : supportedProfiles(0), #ifdef Q_OS_LINUX deviceFd(-1), #endif #ifdef Q_OS_NETBSD deviceFd(-1), #endif #ifdef Q_OS_FREEBSD cam(0), #endif openedReadWrite(false), burnfree(false) { } Solid::Device solidDevice; QString vendor; QString description; QString version; int maxReadSpeed; int maxWriteSpeed; int currentWriteSpeed; bool dvdMinusTestwrite; int bufferSize; WritingModes writeModes; // only needed on FreeBSD QString passDevice; QString blockDevice; QString genericDevice; MediaTypes readCapabilities; MediaTypes writeCapabilities; MediaTypes supportedProfiles; #ifdef Q_OS_LINUX int deviceFd; #endif #ifdef Q_OS_NETBSD int deviceFd; #endif #ifdef Q_OS_FREEBSD struct cam_device *cam; #endif bool openedReadWrite; bool burnfree; QMutex mutex; QMutex openCloseMutex; }; K3b::Device::Device::Device( const Solid::Device& dev ) { d = new Private; d->solidDevice = dev; d->blockDevice = dev.as()->device(); d->writeModes = 0; d->maxWriteSpeed = 0; d->maxReadSpeed = 0; d->burnfree = false; d->dvdMinusTestwrite = true; d->bufferSize = 0; } K3b::Device::Device::~Device() { close(); delete d; } QString K3b::Device::Device::vendor() const { return d->vendor; } QString K3b::Device::Device::description() const { return d->description; } QString K3b::Device::Device::version() const { return d->version; } bool K3b::Device::Device::dvdMinusTestwrite() const { return d->dvdMinusTestwrite; } int K3b::Device::Device::maxReadSpeed() const { return d->maxReadSpeed; } int K3b::Device::Device::currentWriteSpeed() const { return d->currentWriteSpeed; } int K3b::Device::Device::bufferSize() const { return d->bufferSize; } QString K3b::Device::Device::blockDeviceName() const { return d->blockDevice; } int K3b::Device::Device::maxWriteSpeed() const { return d->maxWriteSpeed; } void K3b::Device::Device::setCurrentWriteSpeed( int s ) { d->currentWriteSpeed = s; } void K3b::Device::Device::setMaxReadSpeed( int s ) { d->maxReadSpeed = s; } void K3b::Device::Device::setMaxWriteSpeed( int s ) { d->maxWriteSpeed = s; } Solid::Device K3b::Device::Device::solidDevice() const { return d->solidDevice; } Solid::StorageAccess* K3b::Device::Device::solidStorage() const { QList storages = Solid::Device::listFromType( Solid::DeviceInterface::StorageAccess, d->solidDevice.udi() ); if( storages.isEmpty() ) return 0; else return storages.first().as(); } bool K3b::Device::Device::init( bool bCheckWritingModes ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": init()"; // // they all should read CD-ROM. // d->readCapabilities = MEDIA_CD_ROM; d->writeCapabilities = 0; d->supportedProfiles = 0; if( !open() ) return false; // // inquiry // use a 36 bytes buffer since not all devices return the full inquiry struct // ScsiCommand cmd( this ); unsigned char buf[36]; cmd.clear(); ::memset( buf, 0, sizeof(buf) ); struct inquiry* inq = (struct inquiry*)buf; cmd[0] = MMC_INQUIRY; cmd[4] = sizeof(buf); cmd[5] = 0; if( cmd.transport( TR_DIR_READ, buf, sizeof(buf) ) ) { kError() << "(K3b::Device::Device) Unable to do inquiry." << endl; close(); return false; } else { d->vendor = QString::fromLatin1( (char*)(inq->vendor), 8 ).trimmed(); d->description = QString::fromLatin1( (char*)(inq->product), 16 ).trimmed(); d->version = QString::fromLatin1( (char*)(inq->revision), 4 ).trimmed(); } if( d->vendor.isEmpty() ) d->vendor = "UNKNOWN"; if( d->description.isEmpty() ) d->description = "UNKNOWN"; // // We probe all features of the device. Since not all devices support the GET CONFIGURATION command // we also query the mode page 2A and use the cdrom.h stuff to get as much information as possible // checkFeatures(); // // Check the supported write modes (WRITINGMODE_TAO, WRITINGMODE_SAO, WRITINGMODE_RAW) by trying to set them // We do this before checking mode page 2A in case some readers allow changin // the write parameter page // if( bCheckWritingModes ) checkWritingModes(); // // Most current drives support the 2A mode page // Here we can get some more information (cdrecord -prcap does exactly this) // checkFor2AFeatures(); d->maxWriteSpeed = determineMaximalWriteSpeed(); // // Check Just-Link via Ricoh mode page 0x30 // if( !d->burnfree ) checkForJustLink(); // // Support for some very old drives // checkForAncientWriters(); // // If it can be written it can also be read // d->readCapabilities |= d->writeCapabilities; close(); return furtherInit(); } bool K3b::Device::Device::furtherInit() { #ifdef Q_OS_LINUX // // Since all CDR drives at least support WRITINGMODE_TAO, all CDRW drives should support // mode page 2a and all DVD writer should support mode page 2a or the GET CONFIGURATION // command this is redundant and may be removed for BSD ports or even completely // // We just keep it here because of the "should" in the sentence above. If someone can tell me // that the linux driver does nothing more we can remove it completely. // open(); int drivetype = ::ioctl( handle(), CDROM_GET_CAPABILITY, CDSL_CURRENT ); if( drivetype < 0 ) { kDebug() << "Error while retrieving capabilities."; close(); return false; } d->readCapabilities |= MEDIA_CD_ROM; if( drivetype & CDC_CD_R ) d->writeCapabilities |= MEDIA_CD_R; if( drivetype & CDC_CD_RW ) d->writeCapabilities |= MEDIA_CD_RW; if( drivetype & CDC_DVD_R ) d->writeCapabilities |= MEDIA_DVD_R; if( drivetype & CDC_DVD ) d->readCapabilities |= MEDIA_DVD_ROM; close(); #endif // Q_OS_LINUX return true; } void K3b::Device::Device::checkForAncientWriters() { // TODO: add a boolean which determines if this device is non-MMC so we may warn the user at K3b startup about it // // There are a lot writers out there which behave like the TEAC R5XS // if( ( vendor().startsWith("TEAC") && ( description().startsWith("CD-R50S") || description().startsWith("CD-R55S") ) ) || ( vendor().startsWith("SAF") && ( description().startsWith("CD-R2006PLUS") || description().startsWith("CD-RW226") || description().startsWith("CD-R4012") ) ) || ( vendor().startsWith("JVC") && ( description().startsWith("XR-W2001") || description().startsWith("XR-W2010") || description().startsWith("R2626") ) ) || ( vendor().startsWith("PINNACLE") && ( description().startsWith("RCD-1000") || description().startsWith("RCD5020") || description().startsWith("RCD5040") || description().startsWith("RCD 4X4") ) ) || ( vendor().startsWith("Traxdata") && description().startsWith("CDR4120") ) ) { d->writeModes = WRITINGMODE_TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->maxWriteSpeed = 4; d->maxReadSpeed = 12; d->bufferSize = 1024; d->burnfree = false; } else if( vendor().startsWith("TEAC") ) { if( description().startsWith("CD-R56S") ) { d->writeModes |= TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->maxWriteSpeed = 6; d->maxReadSpeed = 24; d->bufferSize = 1302; d->burnfree = false; } if( description().startsWith("CD-R58S") ) { d->writeModes |= TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->maxWriteSpeed = 8; d->maxReadSpeed = 24; d->bufferSize = 4096; d->burnfree = false; } } else if( vendor().startsWith("MATSHITA") ) { if( description().startsWith("CD-R CW-7501") ) { d->writeModes = WRITINGMODE_TAO|WRITINGMODE_SAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->maxWriteSpeed = 2; d->maxReadSpeed = 4; d->bufferSize = 1024; d->burnfree = false; } if( description().startsWith("CD-R CW-7502") ) { d->writeModes = WRITINGMODE_TAO|WRITINGMODE_SAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->maxWriteSpeed = 4; d->maxReadSpeed = 8; d->bufferSize = 1024; d->burnfree = false; } else if( description().startsWith("CD-R56S") ) { d->writeModes |= WRITINGMODE_TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->maxWriteSpeed = 6; d->maxReadSpeed = 24; d->bufferSize = 1302; d->burnfree = false; } } else if( vendor().startsWith("HP") ) { if( description().startsWith("CD-Writer 6020") ) { d->writeModes = WRITINGMODE_TAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->maxWriteSpeed = 2; d->maxReadSpeed = 6; d->bufferSize = 1024; d->burnfree = false; } } else if( vendor().startsWith( "PHILIPS" ) ) { if( description().startsWith( "CDD2600" ) ) { d->writeModes = WRITINGMODE_TAO|WRITINGMODE_SAO; d->readCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->writeCapabilities = MEDIA_CD_ROM|MEDIA_CD_R; d->maxWriteSpeed = 2; d->maxReadSpeed = 6; d->bufferSize = 1024; d->burnfree = false; } } } bool K3b::Device::Device::dao() const { return d->writeModes & WRITINGMODE_SAO; } bool K3b::Device::Device::supportsRawWriting() const { return( writingModes() & (WRITINGMODE_RAW|WRITINGMODE_RAW_R16|WRITINGMODE_RAW_R96P|WRITINGMODE_RAW_R96R) ); } bool K3b::Device::Device::writesCd() const { return ( d->writeCapabilities & MEDIA_CD_R ) && ( d->writeModes & WRITINGMODE_TAO ); } bool K3b::Device::Device::burner() const { return ( writesCd() || writesDvd() ); } bool K3b::Device::Device::writesCdrw() const { return d->writeCapabilities & MEDIA_CD_RW; } bool K3b::Device::Device::writesDvd() const { return ( writesDvdPlus() || writesDvdMinus() ); } bool K3b::Device::Device::writesDvdPlus() const { return d->writeCapabilities & (MEDIA_DVD_PLUS_R|MEDIA_DVD_PLUS_RW); } bool K3b::Device::Device::writesDvdMinus() const { return d->writeCapabilities & (MEDIA_DVD_R|MEDIA_DVD_RW); } bool K3b::Device::Device::readsDvd() const { return d->readCapabilities & MEDIA_DVD_ROM; } K3b::Device::DeviceTypes K3b::Device::Device::type() const { DeviceTypes r = 0; if( readCapabilities() & MEDIA_CD_ROM ) r |= DEVICE_CD_ROM; if( writeCapabilities() & MEDIA_CD_R ) r |= DEVICE_CD_R; if( writeCapabilities() & MEDIA_CD_RW ) r |= DEVICE_CD_RW; if( readCapabilities() & MEDIA_DVD_ROM ) r |= DEVICE_DVD_ROM; if( writeCapabilities() & MEDIA_DVD_RAM ) r |= DEVICE_DVD_RAM; if( writeCapabilities() & MEDIA_DVD_R ) r |= DEVICE_DVD_R; if( writeCapabilities() & MEDIA_DVD_RW ) r |= DEVICE_DVD_RW; if( writeCapabilities() & MEDIA_DVD_R_DL ) r |= DEVICE_DVD_R_DL; if( writeCapabilities() & MEDIA_DVD_PLUS_R ) r |= DEVICE_DVD_PLUS_R; if( writeCapabilities() & MEDIA_DVD_PLUS_RW ) r |= DEVICE_DVD_PLUS_RW; if( writeCapabilities() & MEDIA_DVD_PLUS_R_DL ) r |= DEVICE_DVD_PLUS_R_DL; if( readCapabilities() & MEDIA_HD_DVD_ROM ) r |= DEVICE_HD_DVD_ROM; if( writeCapabilities() & MEDIA_HD_DVD_R ) r |= DEVICE_HD_DVD_R; if( writeCapabilities() & MEDIA_HD_DVD_RAM ) r |= DEVICE_HD_DVD_RAM; if( readCapabilities() & MEDIA_BD_ROM ) r |= DEVICE_BD_ROM; if( writeCapabilities() & MEDIA_BD_R ) r |= DEVICE_BD_R; if( writeCapabilities() & MEDIA_BD_RE ) r |= DEVICE_BD_RE; return r; } K3b::Device::MediaTypes K3b::Device::Device::readCapabilities() const { return d->readCapabilities; } K3b::Device::MediaTypes K3b::Device::Device::writeCapabilities() const { return d->writeCapabilities; } K3b::Device::WritingModes K3b::Device::Device::writingModes() const { return d->writeModes; } bool K3b::Device::Device::burnproof() const { return burnfree(); } bool K3b::Device::Device::burnfree() const { return d->burnfree; } bool K3b::Device::Device::isDVD() const { if( readsDvd() ) return( mediaType() & MEDIA_DVD_ALL ); else return false; } int K3b::Device::Device::isEmpty() const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); int ret = STATE_UNKNOWN; if( !open() ) return STATE_UNKNOWN; if( !testUnitReady() ) return STATE_NO_MEDIA; unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscInformation( &data, dataLen ) ) { disc_info_t* inf = (disc_info_t*)data; switch( inf->status ) { case 0: ret = STATE_EMPTY; break; case 1: ret = STATE_INCOMPLETE; break; case 2: ret = STATE_COMPLETE; break; default: ret = STATE_UNKNOWN; break; } delete [] data; } if( needToClose ) close(); return ret; } int K3b::Device::Device::numSessions() const { // // Session Info // ============ // Byte 0-1: Data Length // Byte 2: First Complete Session Number (Hex) - always 1 // Byte 3: Last Complete Session Number (Hex) // int ret = -1; unsigned char* data = 0; unsigned int len = 0; int m = mediaType(); if( m & MEDIA_CD_ALL ) { // // Althought disk_info should get the real value without ide-scsi // I keep getting wrong values (the value is too high. I think the leadout // gets counted as session sometimes :() // if( readTocPmaAtip( &data, len, 1, 0, 0 ) ) { ret = data[3]; delete [] data; } else { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": could not get session info !"; } } else if ( m & ( MEDIA_DVD_PLUS_RW|MEDIA_DVD_RW_OVWR|MEDIA_BD_RE ) ) { // fabricate value int e = isEmpty(); return ( e == STATE_COMPLETE || e == STATE_COMPLETE ? 1 : 0 ); } else { if( readDiscInformation( &data, len ) ) { ret = (int)( data[9]<<8 | data[4] ); // do only count complete sessions if( (data[2]>>2) != 3 ) ret--; delete [] data; } } return ret; } K3b::Device::Track::DataMode K3b::Device::Device::getDataMode( const K3b::Msf& sector ) const { bool needToClose = !isOpen(); Track::DataMode ret = Track::UNKNOWN; if( !open() ) return ret; // we use readCdMsf here since it's defined mandatory in MMC1 and // we only use this method for CDs anyway unsigned char data[2352]; bool readSuccess = readCdMsf( data, 2352, 0, // all sector types false, // no dap sector, sector+1, true, // SYNC true, // HEADER true, // SUBHEADER true, // USER DATA true, // EDC/ECC 0, // no c2 info 0 ); if( readSuccess ) { if ( data[15] == 0x1 ) ret = Track::MODE1; else if ( data[15] == 0x2 ) ret = Track::MODE2; if ( ret == Track::MODE2 ) { if ( data[16] == data[20] && data[17] == data[21] && data[18] == data[22] && data[19] == data[23] ) { if ( data[18] & 0x20 ) ret = Track::XA_FORM2; else ret = Track::XA_FORM1; } } } if( needToClose ) close(); return ret; } K3b::Device::Track::DataMode K3b::Device::Device::getTrackDataMode( const K3b::Device::Track& track ) const { return getDataMode( track.firstSector() ); } K3b::Device::Toc K3b::Device::Device::readToc() const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); Toc toc; if( !open() ) return toc; int mt = mediaType(); // // Use the profile if available because DVD-ROM units need to treat DVD+-R(W) media as DVD-ROM // if supported at all // if( currentProfile() == MEDIA_DVD_ROM ) mt = MEDIA_DVD_ROM; if( mt & (MEDIA_DVD_MINUS_ALL|MEDIA_DVD_PLUS_RW|MEDIA_DVD_ROM) ) { if( !readFormattedToc( toc, mt ) ) { K3b::Msf size; if( readCapacity( size ) ) { Track track; track.setFirstSector( 0 ); track.setLastSector( size.lba() ); track.setSession( 1 ); track.setType( Track::TYPE_DATA ); track.setMode( Track::DVD ); track.setCopyPermitted( mt != MEDIA_DVD_ROM ); track.setPreEmphasis( mt != MEDIA_DVD_ROM ); toc.append( track ); } else kDebug() << "(K3b::Device::Device) " << blockDeviceName() << "READ CAPACITY for toc failed." << endl; } } else if( mt & (MEDIA_DVD_PLUS_R|MEDIA_DVD_PLUS_R_DL) ) { // // a DVD+R disk may have multiple sessions // every session may contain up to 16 fragments // if the disk is open there is one open session // every closed session is viewed as a track whereas // every fragment of the open session is viewed as a track // // We may use // READ DISK INFORMATION // READ TRACK INFORMATION: track number FFh, however, does not refer to the invisible track // READ TOC/PMA/ATIP: form 0 refers to all closed sessions // form 1 refers to the last closed session // readFormattedToc( toc, mt ); } else if( mt & MEDIA_BD_ALL ) { readFormattedToc( toc, mt ); } else if( mt == MEDIA_DVD_RAM ) { kDebug() << "(K3b::Device::readDvdToc) no dvdram support"; } else if( mt & MEDIA_CD_ALL ) { bool success = readRawToc( toc ); if( !success ) { success = readFormattedToc( toc, mt ); #ifdef Q_OS_LINUX if( !success ) { kDebug() << "(K3b::Device::Device) MMC READ TOC failed. falling back to cdrom.h."; readTocLinux(toc); } #endif if( success ) fixupToc( toc ); } } if( needToClose ) close(); return toc; } void K3b::Device::Device::readIsrcMcn( K3b::Device::Toc& toc ) const { // read MCN and ISRC of all tracks QByteArray mcn; if( readMcn( mcn ) ) { toc.setMcn( mcn ); kDebug() << "(K3b::Device::Device) found MCN: " << mcn; } else kDebug() << "(K3b::Device::Device) no MCN found."; for( int i = 1; i <= toc.count(); ++i ) { QByteArray isrc; if( toc[i-1].type() == Track::TYPE_AUDIO ) { if( readIsrc( i, isrc ) ) { kDebug() << "(K3b::Device::Device) found ISRC for track " << i << ": " << isrc; toc[i-1].setIsrc( isrc ); } else kDebug() << "(K3b::Device::Device) no ISRC found for track " << i; } } } bool K3b::Device::Device::readFormattedToc( K3b::Device::Toc& toc, int mt ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); bool success = false; toc.clear(); int lastTrack = 0; unsigned char* data = 0; unsigned int dataLen = 0; if( !(mt & MEDIA_CD_ALL) ) { // // on DVD-R(W) multisession disks only two sessions are represented as tracks in the readTocPmaAtip // response (fabricated TOC). Thus, we use readDiscInformation for DVD media to get the proper number of tracks // if( readDiscInformation( &data, dataLen ) ) { lastTrack = (int)( data[11]<<8 | data[6] ); delete [] data; if( readTrackInformation( &data, dataLen, 1, lastTrack ) ) { track_info_t* trackInfo = (track_info_t*)data; if( trackInfo->blank ) { lastTrack--; } delete [] data; success = true; } else return false; } else return false; } else { if( readTocPmaAtip( &data, dataLen, 0, 0, 1 ) ) { if( dataLen < 4 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": formatted toc data too small."; } else if( dataLen != ( (unsigned int)sizeof(toc_track_descriptor) * ((unsigned int)data[3]+1) ) + 4 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": invalid formatted toc data length: " << (dataLen-2) << endl; } else { lastTrack = data[3]; toc_track_descriptor* td = (toc_track_descriptor*)&data[4]; for( int i = 0; i < lastTrack; ++i ) { Track track; unsigned int control = 0; // // In case READ TRACK INFORMATION fails: // no session number info // no track length and thus possibly incorrect last sector for // multisession disks // track.setFirstSector( from4Byte( td[i].start_adr ) ); track.setLastSector( from4Byte( td[i+1].start_adr ) - 1 ); control = td[i].control; track.setType( (control & 0x4) ? Track::TYPE_DATA : Track::TYPE_AUDIO ); track.setMode( getTrackDataMode( track ) ); track.setCopyPermitted( control & 0x2 ); track.setPreEmphasis( control & 0x1 ); toc.append( track ); } success = true; } delete [] data; } } // // Try to get information for all the tracks // for( int i = 0; i < lastTrack; ++i ) { if( toc.count() < i+1 ) toc.append( Track() ); unsigned char* trackData = 0; unsigned int trackDataLen = 0; if( readTrackInformation( &trackData, trackDataLen, 1, i+1 ) ) { track_info_t* trackInfo = (track_info_t*)trackData; toc[i].setFirstSector( from4Byte( trackInfo->track_start ) ); if( i > 0 && toc[i-1].lastSector() == 0 ) toc[i-1].setLastSector( toc[i].firstSector() - 1 ); // There are drives that return 0 track length here! // Some drives even return an invalid length here. :( if( from4Byte( trackInfo->track_size ) > 0 ) toc[i].setLastSector( toc[i].firstSector() + from4Byte( trackInfo->track_size ) - 1 ); if( trackInfo->nwa_v ) { toc[i].setNextWritableAddress( from4Byte( trackInfo->next_writable ) ); toc[i].setFreeBlocks( from4Byte( trackInfo->free_blocks ) ); } toc[i].setSession( (int)((trackInfo->session_number_m<<8 & 0xf0) | (trackInfo->session_number_l & 0x0f)) ); //FIXME: is this BCD? int control = trackInfo->track_mode; if( mt & MEDIA_CD_ALL ) { toc[i].setType( (control & 0x4) ? Track::TYPE_DATA : Track::TYPE_AUDIO ); toc[i].setMode( getTrackDataMode( toc[i] ) ); } else { toc[i].setType( Track::TYPE_DATA ); toc[i].setMode( Track::DVD ); } toc[i].setCopyPermitted( control & 0x2 ); toc[i].setPreEmphasis( control & 0x1 ); delete [] trackData; } else if( !(mt & MEDIA_CD_ALL) ) { success = false; } } // this can only happen with DVD media if( !toc.isEmpty() && toc.last().lastSector() == 0 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " no track length for the last non-empty track."; unsigned char* trackData = 0; unsigned int trackDataLen = 0; if( readTrackInformation( &trackData, trackDataLen, 1, lastTrack+1 ) ) { track_info_t* trackInfo = (track_info_t*)trackData; toc.last().setLastSector( from4Byte( trackInfo->track_start ) - 1 ); delete [] trackData; } } if( needToClose ) close(); return success; } bool K3b::Device::Device::readRawToc( K3b::Device::Toc& toc ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); bool success = false; toc.clear(); if( open() ) { // // Read Raw TOC (format: 0010b) // // For POINT from 01h-63h we get all the tracks // POINT a1h gices us the last track number in the session in PMIN // POINT a2h gives the start of the session lead-out in PMIN,PSEC,PFRAME // unsigned char* data = 0; unsigned int dataLen = 0; if( readTocPmaAtip( &data, dataLen, 2, false, 1 ) ) { if( dataLen > 4 ) { success = true; toc_raw_track_descriptor* tr = (toc_raw_track_descriptor*)&data[4]; // // debug the raw toc data // kDebug() << "Session | ADR | CONTROL| TNO | POINT | Min | Sec | Frame | Zero | PMIN | PSEC | PFRAME |"; for( unsigned int i = 0; i < (dataLen-4)/(int)sizeof(toc_raw_track_descriptor); ++i ) { QString s; s += QString( " %1 |" ).arg( (int)tr[i].session_number, 6 ); s += QString( " %1 |" ).arg( (int)tr[i].adr, 6 ); s += QString( " %1 |" ).arg( (int)tr[i].control, 6 ); s += QString( " %1 |" ).arg( (int)tr[i].tno, 6 ); s += QString( " %1 |" ).arg( (int)tr[i].point, 6, 16 ); s += QString( " %1 |" ).arg( (int)tr[i].min, 6 ); s += QString( " %1 |" ).arg( (int)tr[i].sec, 6 ); s += QString( " %1 |" ).arg( (int)tr[i].frame, 6 ); s += QString( " %1 |" ).arg( (int)tr[i].zero, 6, 16 ); s += QString( " %1 |" ).arg( (int)tr[i].p_min, 6 ); s += QString( " %1 |" ).arg( (int)tr[i].p_sec, 6 ); s += QString( " %1 |" ).arg( (int)tr[i].p_frame, 6 ); kDebug() << s; } // // First we try to determine if the raw toc data uses BCD values // int isBcd = rawTocDataWithBcdValues( data, dataLen ); if( isBcd == -1 ) { delete [] data; return false; } K3b::Msf sessionLeadOut; for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 && tr[i].point <= 0x63 ) { // track Track track; track.setSession( tr[i].session_number ); // :( We use 00:00:00 == 0 lba) if( isBcd ) track.setFirstSector( K3b::Msf( K3b::Device::fromBcd(tr[i].p_min), K3b::Device::fromBcd(tr[i].p_sec), K3b::Device::fromBcd(tr[i].p_frame) ) - 150 ); else track.setFirstSector( K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ) - 150 ); track.setType( tr[i].control & 0x4 ? Track::TYPE_DATA : Track::TYPE_AUDIO ); track.setMode( track.type() == Track::TYPE_DATA ? getTrackDataMode(track) : Track::UNKNOWN ); track.setCopyPermitted( tr[i].control & 0x2 ); track.setPreEmphasis( tr[i].control & 0x1 ); // // only do this within a session because otherwise we already set the last sector with the session leadout // if( !toc.isEmpty() ) if( toc[toc.count()-1].session() == track.session() ) toc[toc.count()-1].setLastSector( track.firstSector() - 1 ); toc.append(track); } else if( tr[i].point == 0xa2 ) { // // since the session is always reported before the tracks this is where we do this: // set the previous session's last tracks's last sector to the first sector of the // session leadout (which was reported before the tracks) // // This only happens on multisession CDs // if( !toc.isEmpty() ) toc[toc.count()-1].setLastSector( sessionLeadOut - 1 ); // this is save since the descriptors are reported in ascending order of the session number // :( We use 00:00:00 == 0 lba) if( isBcd ) sessionLeadOut = K3b::Msf( K3b::Device::fromBcd(tr[i].p_min), K3b::Device::fromBcd(tr[i].p_sec), K3b::Device::fromBcd(tr[i].p_frame) ) - 150; else sessionLeadOut = K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ) - 150; } } kDebug() << blockDeviceName() << ": setting last sector of last track to " << (sessionLeadOut-1).lba(); // set the last track's last sector if( !toc.isEmpty() ) toc[toc.count()-1].setLastSector( sessionLeadOut - 1 ); } else kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " empty raw toc."; delete [] data; } } if( needToClose ) close(); return success; } int K3b::Device::Device::rawTocDataWithBcdValues( unsigned char* data, unsigned int dataLen ) const { toc_raw_track_descriptor* tr = (toc_raw_track_descriptor*)&data[4]; bool notBcd = false; bool notHex = false; // // in most cases this will already tell us if a drive does not provide bcd numbers // (which should be all newer MMC drives) // for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 && tr[i].point <= 0xa2) { if( !K3b::Device::isValidBcd(tr[i].p_min) || !K3b::Device::isValidBcd(tr[i].p_sec) || !K3b::Device::isValidBcd(tr[i].p_frame) ) { notBcd = true; break; } // we only need to check sec and frame since min needs to be <= 99 // and bcd values are never bigger than 99. else if( (int)K3b::Device::fromBcd(tr[i].p_sec) >= 60 || (int)K3b::Device::fromBcd(tr[i].p_frame) >= 75 ) { notBcd = true; break; } } } // // all values are valid bcd values but we still don't know for sure if they are really // used as bcd. So we also check the HEX values. // for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 && tr[i].point <= 0xa2 ) { if( (int)tr[i].p_min > 99 || (int)tr[i].p_sec >= 60 || (int)tr[i].p_frame >= 75 ) { notHex = true; break; } } } // // If all values are valid bcd and valid hex we check the start sectors of the tracks. // if( !notHex || !notBcd ) { K3b::Msf sessionLeadOutHex, sessionLeadOutBcd; K3b::Msf lastTrackHex, lastTrackBcd; for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 ) { if( tr[i].point < 0x64 ) { // check hex values if( K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ) < lastTrackHex ) notHex = true; // check bcd values if( K3b::Msf( K3b::Device::fromBcd(tr[i].p_min), K3b::Device::fromBcd(tr[i].p_sec), K3b::Device::fromBcd(tr[i].p_frame) ) < lastTrackBcd ) notBcd = true; lastTrackBcd = K3b::Msf( K3b::Device::fromBcd(tr[i].p_min), K3b::Device::fromBcd(tr[i].p_sec), K3b::Device::fromBcd(tr[i].p_frame) ); lastTrackHex = K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ); } else if( tr[i].point == 0xa2 ) { if( sessionLeadOutHex < lastTrackHex ) notHex = true; if( sessionLeadOutBcd < lastTrackBcd ) notBcd = true; sessionLeadOutHex = K3b::Msf( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ); sessionLeadOutBcd = K3b::Msf( K3b::Device::fromBcd(tr[i].p_min), K3b::Device::fromBcd(tr[i].p_sec), K3b::Device::fromBcd(tr[i].p_frame) ); } } } // check the last track if( sessionLeadOutHex < lastTrackHex ) notHex = true; if( sessionLeadOutBcd < lastTrackBcd ) notBcd = true; } if( !notBcd && !notHex ) { kDebug() << "(K3b::Device::Device) need to compare raw toc to formatted toc. :("; // // All values are valid bcd and valid HEX values so we compare with the formatted toc. // This slows us down a lot but in most cases this should not be reached anyway. // // TODO: also check the bcd values // K3b::Device::Toc formattedToc; if( readFormattedToc( formattedToc, MEDIA_CD_ROM ) ) { for( unsigned int i = 0; i < (dataLen-4)/(unsigned int)sizeof(toc_raw_track_descriptor); ++i ) { if( tr[i].adr == 1 && tr[i].point < 0x64 ) { unsigned int track = (int)tr[i].point; // FIXME: do bcd drive also encode the track number in bcd? If so test it, too. if( ( int )track > formattedToc.count() ) { notHex = true; break; } K3b::Msf posHex( tr[i].p_min, tr[i].p_sec, tr[i].p_frame ); K3b::Msf posBcd( K3b::Device::fromBcd(tr[i].p_min), K3b::Device::fromBcd(tr[i].p_sec), K3b::Device::fromBcd(tr[i].p_frame) ); posHex -= 150; posBcd -= 150; if( posHex != formattedToc[track-1].firstSector() ) notHex = true; if( posBcd != formattedToc[track-1].firstSector() ) notBcd = true; } } } } if( notBcd ) kDebug() << "(K3b::Device::Device) found invalid bcd values. No bcd toc."; if( notHex ) kDebug() << "(K3b::Device::Device) found invalid hex values. No hex toc."; if( notBcd == notHex ) { kDebug() << "(K3b::Device::Device) unable to determine if hex (" << notHex << ") or bcd (" << notBcd << ")."; if( !notHex ) { kDebug() << "Assuming hex encoding in favor of newer drives and the more reliable raw toc."; return 0; } return -1; } else if( notBcd ) return 0; else return 1; } K3b::Device::CdText K3b::Device::Device::readCdText() const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); K3b::Device::CdText textData; if( open() ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readTocPmaAtip( &data, dataLen, 5, false, 0 ) ) { textData.setRawPackData( data, dataLen ); delete [] data; } if( needToClose ) close(); } return textData; } #ifdef Q_OS_LINUX // fallback bool K3b::Device::Device::readTocLinux( K3b::Device::Toc& toc ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); bool success = true; toc.clear(); struct cdrom_tochdr tochdr; struct cdrom_tocentry tocentry; usageLock(); if( open() ) { // // CDROMREADTOCHDR ioctl returns: // cdth_trk0: First Track Number // cdth_trk1: Last Track Number // if( ::ioctl( d->deviceFd, CDROMREADTOCHDR, &tochdr ) ) { kDebug() << "(K3b::Device::Device) could not get toc header !"; success = false; } else { Track lastTrack; for (int i = tochdr.cdth_trk0; i <= tochdr.cdth_trk1 + 1; i++) { ::memset(&tocentry,0,sizeof (struct cdrom_tocentry)); // get Lead-Out Information too tocentry.cdte_track = (i<=tochdr.cdth_trk1) ? i : CDROM_LEADOUT; tocentry.cdte_format = CDROM_LBA; // // CDROMREADTOCENTRY ioctl returns: // cdte_addr.lba: Start Sector Number (LBA Format requested) // cdte_ctrl: 4 ctrl bits // 00x0b: 2 audio Channels(no pre-emphasis) // 00x1b: 2 audio Channels(pre-emphasis) // 10x0b: audio Channels(no pre-emphasis),reserved in cd-rw // 10x1b: audio Channels(pre-emphasis),reserved in cd-rw // 01x0b: data track, recorded uninterrupted // 01x1b: data track, recorded incremental // 11xxb: reserved // xx0xb: digital copy prohibited // xx1xb: digital copy permitted // cdte_addr: 4 addr bits (type of Q-Subchannel data) // 0000b: no Information // 0001b: current position data // 0010b: MCN // 0011b: ISRC // 0100b-1111b: reserved // cdte_datamode: 0: Data Mode1 // 1: CD-I // 2: CD-XA Mode2 // if( ::ioctl( d->deviceFd, CDROMREADTOCENTRY, &tocentry ) ) { kDebug() << "(K3b::Device::Device) error reading tocentry " << i; success = false; break; } int startSec = tocentry.cdte_addr.lba; int control = tocentry.cdte_ctrl & 0x0f; int mode = tocentry.cdte_datamode; if( i > tochdr.cdth_trk0 ) { Track track( lastTrack.firstSector(), startSec-1, lastTrack.type(), lastTrack.mode() ); track.setPreEmphasis( control & 0x1 ); track.setCopyPermitted( control & 0x2 ); toc.append( track ); } Track::TrackType trackType = Track::TYPE_UNKNOWN; Track::DataMode trackMode = Track::UNKNOWN; if( (control & 0x04 ) && (tocentry.cdte_track != CDROM_LEADOUT) ) { trackType = Track::TYPE_DATA; if( mode == 1 ) trackMode = Track::MODE1; else if( mode == 2 ) trackMode = Track::MODE2; Track::DataMode tm = getDataMode(startSec); if( tm != Track::UNKNOWN ) trackMode = tm; } else trackType = Track::TYPE_AUDIO; lastTrack = Track( startSec, startSec, trackType, trackMode ); } } if( needToClose ) close(); } else success = false; usageUnlock(); return success; } #endif // Q_OS_LINUX bool K3b::Device::Device::fixupToc( K3b::Device::Toc& toc ) const { bool success = false; // // This is a very lame method of fixing the TOC of an Advanced Audio CD // (a CD with two sessions: one with audio tracks and one with the data track) // If a drive does not support reading raw toc or reading track info we only // get every track's first sector. But between sessions there is a gap which is used // for ms stuff. In this case it's 11400 sectors in size. When ripping ausio we would // include these 11400 sectors which would result in a strange ending audio file. // if( numSessions() > 1 || toc.contentType() == MIXED ) { kDebug() << "(K3b::Device::Device) fixup multisession toc..."; // // we need to update the last sector of every last track in every session // for now we only update the track before the last session... // This is the most often case: Advanced Audio CD // unsigned char* data = 0; unsigned int dataLen = 0; if( readTocPmaAtip( &data, dataLen, 1, false, 0 ) ) { // // data[6] - first track number in last complete session // data[8-11] - start address of first track in last session // toc[(unsigned int)data[6]-2].setLastSector( from4Byte( &data[8] ) - 11400 - 1 ); delete [] data; success = true; } else kDebug() << "(K3b::Device::Device) FIXUP TOC failed."; } return success; } bool K3b::Device::Device::block( bool b ) const { // // For some reason the Scsi Command does not work here. // So we use the ioctl on Linux systems // #if defined(Q_OS_LINUX) bool success = false; bool needToClose = !isOpen(); usageLock(); if( open() ) { success = ( ::ioctl( d->deviceFd, CDROM_LOCKDOOR, b ? 1 : 0 ) == 0 ); if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #elif defined(Q_OS_NETBSD) bool success = false; bool needToClose = !isOpen(); int arg = b ? 1 : 0; usageLock(); if( open() ) { success = ( ::ioctl( d->deviceFd, DIOCLOCK, &arg ) == 0 ); if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #endif ScsiCommand cmd( this ); cmd[0] = MMC_PREVENT_ALLOW_MEDIUM_REMOVAL; cmd[5] = 0; // Necessary to set the proper command length if( b ) cmd[4] = 0x01; int r = cmd.transport( TR_DIR_WRITE ); if( r ) kDebug() << "(K3b::Device::Device) MMC ALLOW MEDIA REMOVAL failed."; return ( r == 0 ); } bool K3b::Device::Device::rewritable() const { unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscInformation( &data, dataLen ) ) { disc_info_t* inf = (disc_info_t*)data; bool e = inf->erasable; delete [] data; return e; } else return false; } bool K3b::Device::Device::eject() const { +#if 0 #ifdef Q_OS_NETBSD bool success = false; bool needToClose = !isOpen(); int arg = 0; usageLock(); if( open() ) { if ( ::ioctl( d->deviceFd, DIOCEJECT, &arg ) >= 0) success = true; if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #elif defined(Q_OS_LINUX) bool success = false; bool needToClose = !isOpen(); usageLock(); if( open() ) { if( ::ioctl( d->deviceFd, CDROMEJECT ) >= 0 ) success = true; if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #endif - +#endif + kDebug(); ScsiCommand cmd( this ); cmd[0] = MMC_PREVENT_ALLOW_MEDIUM_REMOVAL; cmd[5] = 0; // Necessary to set the proper command length - cmd.transport(); + cmd.transport( TR_DIR_WRITE ); cmd[0] = MMC_START_STOP_UNIT; cmd[5] = 0; // Necessary to set the proper command length - cmd[4] = 0x1; // Start unit - cmd.transport(); - - cmd[4] = 0x2; // LoEj = 1, Start = 0 - - return !cmd.transport(); + cmd[4] = 0x2; // eject medium LoEj = 1, Start = 0 + return !cmd.transport( TR_DIR_WRITE ); } bool K3b::Device::Device::load() const { +#if 0 #ifdef Q_OS_NETBSD bool success = false; bool needToClose = !isOpen(); int arg = 0; usageLock(); if( open() ) { if ( ::ioctl( d->deviceFd, CDIOCCLOSE, &arg ) >= 0) success = true; if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #elif defined(Q_OS_LINUX) bool success = false; bool needToClose = !isOpen(); usageLock(); if( open() ) { if( ::ioctl( d->deviceFd, CDROMCLOSETRAY ) >= 0 ) success = true; if( needToClose ) close(); } usageUnlock(); if ( success ) return success; #endif - +#endif + kDebug(); ScsiCommand cmd( this ); cmd[0] = MMC_START_STOP_UNIT; cmd[4] = 0x3; // LoEj = 1, Start = 1 cmd[5] = 0; // Necessary to set the proper command length return !cmd.transport(); } bool K3b::Device::Device::setAutoEjectEnabled( bool enabled ) const { bool success = false; #ifdef Q_OS_LINUX bool needToClose = !isOpen(); usageLock(); if ( open() ) { success = ( ::ioctl( d->deviceFd, CDROMEJECT_SW, enabled ? 1 : 0 ) == 0 ); if ( needToClose ) { close(); } } usageUnlock(); #endif return success; } K3b::Device::Device::Handle K3b::Device::Device::handle() const { #ifdef Q_OS_FREEBSD return d->cam; #else return d->deviceFd; #endif } bool K3b::Device::Device::open( bool write ) const { if( d->openedReadWrite != write ) close(); QMutexLocker ml( &d->openCloseMutex ); d->openedReadWrite = write; #ifdef Q_OS_FREEBSD if( !d->cam ) { d->cam = cad->open_pass (d->passDevice.toLatin1(), O_RDWR,0 /* NULL */); kDebug() << "(K3b::Device::openDevice) open device " << d->passDevice << ((d->cam)?" succeeded.":" failed.") << endl; } return (d->cam != 0); #endif #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) if( d->deviceFd == -1 ) d->deviceFd = openDevice( QFile::encodeName(blockDeviceName()), write ); return ( d->deviceFd != -1 ); #endif } void K3b::Device::Device::close() const { QMutexLocker ml( &d->openCloseMutex ); #ifdef Q_OS_FREEBSD if( d->cam ) { cad->close_device(d->cam); d->cam = 0; } #endif #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) if( d->deviceFd != -1 ) { ::close( d->deviceFd ); d->deviceFd = -1; } #endif } bool K3b::Device::Device::isOpen() const { #ifdef Q_OS_FREEBSD return d->cam; #endif #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) return ( d->deviceFd != -1 ); #endif } int K3b::Device::Device::supportedProfiles() const { return d->supportedProfiles; } int K3b::Device::Device::currentProfile() const { unsigned char profileBuf[8]; ::memset( profileBuf, 0, 8 ); ScsiCommand cmd( this ); cmd[0] = MMC_GET_CONFIGURATION; cmd[1] = 1; cmd[8] = 8; cmd[9] = 0; // Necessary to set the proper command length if( cmd.transport( TR_DIR_READ, profileBuf, 8 ) ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " GET_CONFIGURATION failed." << endl; return MEDIA_UNKNOWN; } else { short profile = from2Byte( &profileBuf[6] ); // // Plextor drives might not set a current profile // In that case we get the list of all current profiles // and simply use the first one in that list. // if( profile == 0x00 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " current profile 0. Checking current profile list instead." << endl; unsigned char* data; unsigned int len = 0; if( getFeature( &data, len, FEATURE_PROFILE_LIST ) ) { int featureLen( data[11] ); for( int j = 0; j < featureLen; j+=4 ) { // use the first current profile we encounter if( data[12+j+2] & 0x1 ) { profile = from2Byte( &data[12+j] ); break; } } delete[] data; } } switch (profile) { case 0x00: return MEDIA_NONE; case 0x08: return MEDIA_CD_ROM; case 0x09: return MEDIA_CD_R; case 0x0A: return MEDIA_CD_RW; case 0x10: return MEDIA_DVD_ROM; case 0x11: return MEDIA_DVD_R_SEQ; case 0x12: return MEDIA_DVD_RAM; case 0x13: return MEDIA_DVD_RW_OVWR; case 0x14: return MEDIA_DVD_RW_SEQ; case 0x15: return MEDIA_DVD_R_DL_SEQ; case 0x16: return MEDIA_DVD_R_DL_JUMP; case 0x1A: return MEDIA_DVD_PLUS_RW; case 0x1B: return MEDIA_DVD_PLUS_R; case 0x2B: return MEDIA_DVD_PLUS_R_DL; case 0x40: return MEDIA_BD_ROM; case 0x41: { if( featureCurrent( FEATURE_BD_PSEUDO_OVERWRITE ) == 1 ) return MEDIA_BD_R_SRM_POW; else return MEDIA_BD_R_SRM; } case 0x42: return MEDIA_BD_R_RRM; case 0x43: return MEDIA_BD_RE; case 0x50: return MEDIA_HD_DVD_ROM; case 0x51: return MEDIA_HD_DVD_R; case 0x52: return MEDIA_HD_DVD_RAM; default: return MEDIA_UNKNOWN; } } } K3b::Device::DiskInfo K3b::Device::Device::diskInfo() const { DiskInfo inf; // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( open() ) { unsigned char* data = 0; unsigned int dataLen = 0; // // The first thing to do should be: checking if a media is loaded // We cannot rely on the profile here since at least some Plextor // drives return the NO MEDIUM profile for CD media // if( !testUnitReady() ) { // no disk or tray open inf.d->diskState = STATE_NO_MEDIA; inf.d->mediaType = MEDIA_NONE; inf.d->currentProfile = MEDIA_NONE; } else inf.d->currentProfile = currentProfile(); if( inf.diskState() != STATE_NO_MEDIA ) { if( readDiscInformation( &data, dataLen ) ) { disc_info_t* dInf = (disc_info_t*)data; // // Copy the needed values from the disk_info struct // switch( dInf->status ) { case 0: inf.d->diskState = STATE_EMPTY; break; case 1: inf.d->diskState = STATE_INCOMPLETE; break; case 2: inf.d->diskState = STATE_COMPLETE; break; default: inf.d->diskState = STATE_UNKNOWN; break; } switch( dInf->border ) { case 0: inf.d->lastSessionState = STATE_EMPTY; break; case 1: inf.d->lastSessionState = STATE_INCOMPLETE; break; case 2: inf.d->lastSessionState = STATE_COMPLETE; break; default: inf.d->lastSessionState = STATE_UNKNOWN; break; } switch( dInf->bg_f_status&0x3 ) { case 0x0: inf.d->bgFormatState = BG_FORMAT_NONE; break; case 0x1: inf.d->bgFormatState = BG_FORMAT_INCOMPLETE; break; case 0x2: inf.d->bgFormatState = BG_FORMAT_IN_PROGRESS; break; case 0x3: inf.d->bgFormatState = BG_FORMAT_COMPLETE; break; } inf.d->numTracks = (dInf->last_track_l & 0xff) | (dInf->last_track_m<<8 & 0xff00); if( inf.diskState() == STATE_EMPTY ) inf.d->numTracks = 0; // FIXME: I am not sure if this is accurate. Better test the last track's RT field else if( inf.diskState() == STATE_INCOMPLETE ) inf.d->numTracks--; // do not count the invisible track inf.d->rewritable = dInf->erasable; // // This is the Last Possible Lead-Out Start Address in HMSF format // This is only valid for CD-R(W) and DVD+R media. // For complete media this shall be filled with 0xff // if( dInf->lead_out_m != 0xff && dInf->lead_out_r != 0xff && dInf->lead_out_s != 0xff && dInf->lead_out_f != 0xff ) inf.d->capacity = K3b::Msf( dInf->lead_out_m + dInf->lead_out_r*60, dInf->lead_out_s, dInf->lead_out_f ) - 150; // // This is the position where the next Session shall be recorded in HMSF format // This is only valid for CD-R(W) and DVD+R media. // For complete media this shall be filled with 0xff // if( dInf->lead_in_m != 0xff && dInf->lead_in_r != 0xff && dInf->lead_in_s != 0xff && dInf->lead_in_f != 0xff ) inf.d->usedCapacity = K3b::Msf( dInf->lead_in_m + dInf->lead_in_r*60, dInf->lead_in_s, dInf->lead_in_f ) - 4500; delete [] data; } else { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " fabricating disk information for a stupid device." << endl; Toc toc = readToc(); if( !toc.isEmpty() ) { inf.d->diskState = STATE_COMPLETE; inf.d->lastSessionState = STATE_COMPLETE; inf.d->numTracks = toc.count(); inf.d->capacity = inf.d->usedCapacity = toc.length(); } } // // The mediatype needs to be set // inf.d->mediaType = mediaType(); // At least some Plextor drives return profile NONE for CD media // or CD_ROM for writable media if( inf.d->mediaType & (MEDIA_UNKNOWN|MEDIA_NONE|MEDIA_CD_ROM) ) { // probably it is a CD if( inf.rewritable() ) inf.d->mediaType = MEDIA_CD_RW; else if( inf.empty() || inf.appendable() ) inf.d->mediaType = MEDIA_CD_R; else inf.d->mediaType = MEDIA_CD_ROM; } if( inf.d->mediaType & MEDIA_DVD_ALL ) { if( readDvdStructure( &data, dataLen ) ) { // some debugging stuff K3b::Msf sda, eda, ea0; sda = ( data[4+5]<<16 | data[4+6] << 8 | data[4+7] ); eda = ( data[4+9]<<16 | data[4+10] << 8 | data[4+11] ); ea0 = ( data[4+13]<<16 | data[4+14] << 8 | data[4+15] ); kDebug() << "First sec data area: " << sda.toString() << " (LBA " << QString::number(sda.lba()) << ") (" << QString::number(sda.mode1Bytes()) << endl; kDebug() << "Last sec data area: " << eda.toString() << " (LBA " << QString::number(eda.lba()) << ") (" << QString::number(eda.mode1Bytes()) << " Bytes)" << endl; kDebug() << "Last sec layer 1: " << ea0.toString() << " (LBA " << QString::number(ea0.lba()) << ") (" << QString::number(ea0.mode1Bytes()) << " Bytes)" << endl; K3b::Msf da0 = ea0 - sda + 1; K3b::Msf da1 = eda - ea0; kDebug() << "Layer 1 length: " << da0.toString() << " (LBA " << QString::number(da0.lba()) << ") (" << QString::number(da0.mode1Bytes()) << " Bytes)" << endl; kDebug() << "Layer 2 length: " << da1.toString() << " (LBA " << QString::number(da1.lba()) << ") (" << QString::number(da1.mode1Bytes()) << " Bytes)" << endl; inf.d->numLayers = ((data[6]&0x60) == 0 ? 1 : 2); bool otp = (data[4+2] & 0xF); // ea0 is 0 if the medium does not use Opposite track path if( otp && ea0 > 0 ) inf.d->firstLayerSize = da0; else inf.d->firstLayerSize = 0; delete [] data; } else { kDebug() << "(K3b::Device::Device) Unable to read DVD structure for num of layers."; inf.d->numLayers = ( (inf.d->mediaType & MEDIA_WRITABLE_DVD_DL) ? 2 : 1 ); } } // // Number of sessions for non-empty disks // if( inf.diskState() != STATE_EMPTY ) { int sessions = numSessions(); if( sessions >= 0 ) inf.d->numSessions = sessions; else kDebug() << "(K3b::Device::Device) could not get session info via READ TOC/PMA/ATIP."; } else inf.d->numSessions = 0; inf.d->mediaId = mediaId( inf.mediaType() ); // // Now we determine the size: // for all empty and appendable media READ FORMAT CAPACITIES should return the proper unformatted size // for complete disks we may use the READ_CAPACITY command or the start sector from the leadout // int media = inf.mediaType(); // // Use the profile if available because DVD-ROM units need to treat DVD+-R(W) media as DVD-ROM // if supported at all // if( inf.currentProfile() == MEDIA_DVD_ROM ) media = MEDIA_DVD_ROM; switch( media ) { case MEDIA_CD_R: case MEDIA_CD_RW: if( inf.d->capacity == 0 ) { if( readTocPmaAtip( &data, dataLen, 0x4, true, 0 ) ) { struct atip_descriptor* atip = (struct atip_descriptor*)data; if( dataLen >= 11 ) { inf.d->capacity = K3b::Msf( atip->lead_out_m, atip->lead_out_s, atip->lead_out_f ) - 150; debugBitfield( &atip->lead_out_m, 3 ); kDebug() << blockDeviceName() << ": ATIP capacity: " << inf.d->capacity.toString(); } delete [] data; } } // // for empty and appendable media capacity and usedCapacity should be filled in from // diskinfo above. If not they are both still 0 // if( inf.d->capacity != 0 && ( inf.diskState() == STATE_EMPTY || inf.d->usedCapacity != 0 ) ) { // done. break; } default: case MEDIA_CD_ROM: if( inf.d->capacity > 0 && inf.d->usedCapacity == 0 ) inf.d->usedCapacity = inf.d->capacity; if( inf.d->usedCapacity == 0 ) { K3b::Msf readCap; if( readCapacity( readCap ) ) { kDebug() << "(K3b::Device::Device) READ CAPACITY: " << readCap.toString() << " other capacity: " << inf.d->capacity.toString() << endl; // // READ CAPACITY returns the last written sector // that means the size is actually readCap + 1 // inf.d->usedCapacity = readCap + 1; } else { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " Falling back to readToc for capacity." << endl; inf.d->usedCapacity = readToc().length(); } } case MEDIA_DVD_ROM: { K3b::Msf readCap; if( readCapacity( readCap ) ) { kDebug() << "(K3b::Device::Device) READ CAPACITY: " << readCap.toString() << " other capacity: " << inf.d->capacity.toString() << endl; // // READ CAPACITY returns the last written sector // that means the size is actually readCap + 1 // inf.d->usedCapacity = readCap + 1; } else { // // Only one track, use it's size // if( readTrackInformation( &data, dataLen, 0x1, 0x1 ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.d->usedCapacity = from4Byte( trackInfo->track_size ); delete [] data; } else kDebug() << "(K3b::Device::Device) " << blockDeviceName() << "READ TRACK INFORMATION for DVD-ROM failed." << endl; } break; } case MEDIA_DVD_PLUS_R: case MEDIA_DVD_PLUS_R_DL: if( inf.appendable() || inf.empty() ) { // // get remaining space via the invisible track // if( readTrackInformation( &data, dataLen, 0x1, /*0xff*/ inf.numTracks()+1 ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.d->usedCapacity = from4Byte( trackInfo->track_start ); inf.d->capacity = from4Byte( trackInfo->track_start ) + from4Byte( trackInfo->track_size ); delete [] data; } } else { if( readTrackInformation( &data, dataLen, 0x1, inf.numTracks() ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.d->capacity = inf.d->usedCapacity = from4Byte( trackInfo->track_start ) + from4Byte( trackInfo->track_size ); delete [] data; } } break; case MEDIA_DVD_R: case MEDIA_DVD_R_SEQ: case MEDIA_DVD_R_DL: case MEDIA_DVD_R_DL_JUMP: case MEDIA_DVD_R_DL_SEQ: // // get data from the incomplete track (which is NOT the invisible track 0xff) // This will fail in case the media is complete! // if( readTrackInformation( &data, dataLen, 0x1, inf.numTracks()+1 ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.d->usedCapacity = from4Byte( trackInfo->track_start ); inf.d->capacity = from4Byte( trackInfo->free_blocks ) + from4Byte( trackInfo->track_start ); delete [] data; } // // Get the "really" used space without border-out // if( !inf.empty() ) { K3b::Msf readCap; if( readCapacity( readCap ) ) { // // READ CAPACITY returns the last written sector // that means the size is actually readCap + 1 // inf.d->usedCapacity = readCap + 1; } else kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " READ CAPACITY for DVD-R failed." << endl; } break; case MEDIA_DVD_RW_OVWR: inf.d->numSessions = 1; case MEDIA_DVD_RW: case MEDIA_DVD_RW_SEQ: // only one track on a DVD-RW media if( readTrackInformation( &data, dataLen, 0x1, 0x1 ) ) { track_info_t* trackInfo = (track_info_t*)data; inf.d->capacity = from4Byte( trackInfo->track_size ); if( !inf.empty() ) { if( readFormatCapacity( 0x10, inf.d->capacity ) ) kDebug() << blockDeviceName() << ": Format capacity 0x10: " << inf.d->capacity.toString(); inf.d->usedCapacity = from4Byte( trackInfo->track_size ); } delete [] data; } break; case MEDIA_DVD_PLUS_RW: { K3b::Msf currentMax; int currentMaxFormat = 0; if( readFormatCapacity( 0x26, inf.d->capacity, ¤tMax, ¤tMaxFormat ) ) { if( currentMaxFormat == 0x1 ) { // unformatted or blank media inf.d->usedCapacity = 0; inf.d->capacity = currentMax; } else { inf.d->usedCapacity = currentMax; // Plextor drives tend to screw things up and report invalid values // for the max format capacity of 1.4 GB DVD media if ( inf.bgFormatState() == BG_FORMAT_COMPLETE ) { inf.d->capacity = currentMax; } } } else kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " READ FORMAT CAPACITIES for DVD+RW failed." << endl; break; } case MEDIA_BD_R: case MEDIA_BD_R_SRM: case MEDIA_BD_R_SRM_POW: case MEDIA_BD_R_RRM: case MEDIA_BD_RE: // // get the invisible track's first sector // or the next writable address of the last open track // if( readDiscInformation( &data, dataLen ) ) { int lastTrack = (int)( data[11]<<8 | data[6] ); delete [] data; if( readTrackInformation( &data, dataLen, 1, lastTrack ) ) { // capacity: last track's start address + last track's size inf.d->capacity = from4Byte( data+8 ) + from4Byte( data+24 ); if( data[6] & 0x80 ) inf.d->usedCapacity = from4Byte( data+8 ); else if( data[7] & 0x1 ) inf.d->usedCapacity = from4Byte( data+12 ); delete [] data; } } break; case MEDIA_BD_ROM: { K3b::Msf readCap; if( readCapacity( readCap ) ) { // // READ CAPACITY returns the last written sector // that means the size is actually readCap + 1 // inf.d->usedCapacity = readCap + 1; } break; } } } if( needToClose ) close(); } return inf; } K3b::Device::MediaType K3b::Device::Device::mediaType() const { K3b::Device::MediaType m = MEDIA_UNKNOWN; if( testUnitReady() ) { int p = currentProfile(); if ( p != -1 ) m = ( MediaType )p; if( m & (MEDIA_UNKNOWN|MEDIA_DVD_ROM|MEDIA_HD_DVD_ROM) ) { // // We prefere the mediatype as reported by the media since this way // even ROM drives may report the correct type of writable media. // // 4 bytes header + 2048 bytes layer descriptor unsigned char* data = 0; unsigned int dataLen = 0; if( readDvdStructure( &data, dataLen ) ) { switch( data[4]&0xF0 ) { case 0x00: m = MEDIA_DVD_ROM; break; case 0x10: m = MEDIA_DVD_RAM; break; case 0x20: m = MEDIA_DVD_R; break; // there seems to be no value for DVD-R DL, it reports DVD-R case 0x30: m = MEDIA_DVD_RW; break; case 0x40: m = MEDIA_HD_DVD_ROM; break; case 0x50: m = MEDIA_HD_DVD_R; break; case 0x60: m = MEDIA_HD_DVD_RAM; break; case 0x90: m = MEDIA_DVD_PLUS_RW; break; case 0xA0: m = MEDIA_DVD_PLUS_R; break; case 0xE0: m = MEDIA_DVD_PLUS_R_DL; break; default: kDebug() << "(K3b::Device::Device) unknown dvd media type: " << QString::number(data[4]&0xF0, 8); break; // unknown } delete [] data; } } if( m & (MEDIA_UNKNOWN|MEDIA_BD_ROM) ) { // // We prefere the mediatype as reported by the media since this way // even ROM drives may report the correct type of writable media. // unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscStructure( &data, dataLen, 1, 0 ) ) { if( dataLen > 4+12 && data[4+8] == 'B' && data[4+9] == 'D' ) { switch( data[4+10] ) { case 'O': m = MEDIA_BD_ROM; break; case 'W': m = MEDIA_BD_RE; break; case 'R': m = MEDIA_BD_R; break; } } delete [] data; } } // // Only old CD or DVD devices do not report a current profile // or report CD-ROM profile for all CD types // if( m & (MEDIA_UNKNOWN|MEDIA_CD_ROM) ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readTocPmaAtip( &data, dataLen, 4, false, 0 ) ) { if( (data[6]>>6)&1 ) m = MEDIA_CD_RW; else m = MEDIA_CD_R; delete [] data; } else m = MEDIA_CD_ROM; } } return m; } bool K3b::Device::Device::readSectorsRaw( unsigned char *buf, int start, int count ) const { return readCd( buf, count*2352, 0, // all sector types false, // no dap start, count, true, // SYNC true, // HEADER true, // SUBHEADER true, // USER DATA true, // EDC/ECC 0, // no c2 info 0 ); } void K3b::Device::Device::checkForJustLink() { unsigned char* ricoh = 0; unsigned int ricohLen = 0; if( modeSense( &ricoh, ricohLen, 0x30 ) ) { // // 8 byte mode header + 6 byte page data // if( ricohLen >= 14 ) { ricoh_mode_page_30* rp = (ricoh_mode_page_30*)(ricoh+8); d->burnfree = rp->BUEFS; } delete [] ricoh; } } void K3b::Device::Device::checkFeatures() { unsigned char header[1024]; ::memset( header, 0, 1024 ); ScsiCommand cmd( this ); cmd[0] = MMC_GET_CONFIGURATION; cmd[1] = 2; cmd[9] = 0; // Necessary to set the proper command length // // CD writing features // cmd[2] = FEATURE_CD_MASTERING>>8; cmd[3] = FEATURE_CD_MASTERING; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "CD Mastering"; #ifdef WORDS_BIGENDIAN struct cd_mastering_feature { unsigned char reserved1 : 1; unsigned char BUF : 1; // Burnfree unsigned char SAO : 1; // Session At Once writing unsigned char raw_ms : 1; // Writing Multisession in Raw Writing Mode unsigned char raw : 1; // Writing in WRITINGMODE_RAW mode unsigned char testwrite : 1; // Simulation write support unsigned char cd_rw : 1; // CD-RW support unsigned char rw_sub : 1; // Write R-W sub channels with user data unsigned char max_cue_length[3]; }; #else struct cd_mastering_feature { unsigned char rw_sub : 1; // Write R-W sub channels with user data unsigned char cd_rw : 1; // CD-RW support unsigned char testwrite : 1; // Simulation write support unsigned char raw : 1; // Writing in WRITINGMODE_RAW mode unsigned char raw_ms : 1; // Writing Multisession in Raw Writing Mode unsigned char SAO : 1; // Session At Once writing unsigned char BUF : 1; // Burnfree unsigned char reserved1 : 1; unsigned char max_cue_length[3]; }; #endif struct cd_mastering_feature* p = (struct cd_mastering_feature*)&header[12]; if( p->BUF ) d->burnfree = true; d->writeCapabilities |= MEDIA_CD_R; if( p->cd_rw ) d->writeCapabilities |= MEDIA_CD_RW; // if( p->WRITINGMODE_SAO ) d->writeModes |= WRITINGMODE_SAO; // if( p->raw || p->raw_ms ) d->writeModes |= WRITINGMODE_RAW; // WRITINGMODE_RAW16 always supported when raw is supported? } } cmd[2] = FEATURE_CD_TRACK_AT_ONCE>>8; cmd[3] = FEATURE_CD_TRACK_AT_ONCE; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "CD Track At Once"; #ifdef WORDS_BIGENDIAN struct cd_track_at_once_feature { unsigned char reserved1 : 1; unsigned char BUF : 1; // Burnfree unsigned char reserved2 : 1; unsigned char rw_raw : 1; // Writing R-W subcode in Raw mode unsigned char rw_pack : 1; // Writing R-W subcode in Packet mode unsigned char testwrite : 1; // Simulation write support unsigned char cd_rw : 1; // CD-RW support unsigned char rw_sub : 1; // Write R-W sub channels with user data unsigned char reserved3; unsigned char data_type[2]; }; #else struct cd_track_at_once_feature { unsigned char rw_sub : 1; // Write R-W sub channels with user data unsigned char cd_rw : 1; // CD-RW support unsigned char testwrite : 1; // Simulation write support unsigned char rw_pack : 1; // Writing R-W subcode in Packet mode unsigned char rw_raw : 1; // Writing R-W subcode in Raw mode unsigned char reserved2 : 1; unsigned char BUF : 1; // Burnfree unsigned char reserved1 : 1; unsigned char reserved3; unsigned char data_type[2]; }; #endif struct cd_track_at_once_feature* p = (struct cd_track_at_once_feature*)&header[12]; d->writeModes |= WRITINGMODE_TAO; if( p->BUF ) d->burnfree = true; d->writeCapabilities |= MEDIA_CD_R; if( p->cd_rw ) d->writeCapabilities |= MEDIA_CD_RW; // is the following correct? What exactly does rw_sub tell us? // if( d->writeModes & WRITINGMODE_RAW ) { // if( p->rw_raw ) d->writeModes |= WRITINGMODE_RAW_R96R; // if( p->rw_pack ) d->writeModes |= WRITINGMODE_RAW_R96P; // } // // check the data types for 1, 2, and 3 (raw16, raw96p, and raw96r) // debugBitfield( p->data_type, 2 ); // if( d->writeModes & WRITINGMODE_RAW ) { // if( p->data_type[1] & 0x20 ) d->writeModes |= WRITINGMODE_RAW_R16; // if( p->data_type[1] & 0x40 ) d->writeModes |= WRITINGMODE_RAW_R96P; // if( p->data_type[1] & 0x80 ) d->writeModes |= WRITINGMODE_RAW_R96R; // } } } cmd[2] = FEATURE_CD_RW_MEDIA_WRITE_SUPPORT>>8; cmd[3] = FEATURE_CD_RW_MEDIA_WRITE_SUPPORT; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "CD-RW Media Write Support"; d->writeCapabilities |= (MEDIA_CD_R|MEDIA_CD_RW); } } // // DVD-ROM // // FIXME: since MMC5 the feature descr. is 8 bytes in length including a dvd dl read bit at byte 6 cmd[2] = FEATURE_DVD_READ>>8; cmd[3] = FEATURE_DVD_READ; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD Read (MMC5)"; d->readCapabilities |= MEDIA_DVD_ROM; if( header[8+6] & 0x1 ) d->readCapabilities |= MEDIA_WRITABLE_DVD_DL; } } else { // retry with pre-MMC5 length cmd[8] = 8+4; if( !cmd.transport( TR_DIR_READ, header, 12 ) ) { unsigned int len = from4Byte( header ); if( len >= 8 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD Read (pre-MMC5)"; d->readCapabilities |= MEDIA_DVD_ROM; } } } // // DVD+R(W) writing features // cmd[2] = FEATURE_DVD_PLUS_R>>8; cmd[3] = FEATURE_DVD_PLUS_R; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD+R"; d->readCapabilities |= MEDIA_DVD_PLUS_R; if( header[12] & 0x1 ) d->writeCapabilities |= MEDIA_DVD_PLUS_R; } } cmd[2] = FEATURE_DVD_PLUS_RW>>8; cmd[3] = FEATURE_DVD_PLUS_RW; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD+RW"; #ifdef WORDS_BIGENDIAN struct dvd_plus_rw_feature { unsigned char reserved1 : 7; unsigned char write : 1; unsigned char reserved2 : 6; unsigned char quick_start : 1; unsigned char close_only : 1; // and some stuff we do not use here... }; #else struct dvd_plus_rw_feature { unsigned char write : 1; unsigned char reserved1 : 7; unsigned char close_only : 1; unsigned char quick_start : 1; unsigned char reserved2 : 6; // and some stuff we do not use here... }; #endif struct dvd_plus_rw_feature* p = (struct dvd_plus_rw_feature*)&header[12]; d->readCapabilities |= MEDIA_DVD_PLUS_RW; if( p->write ) d->writeCapabilities |= MEDIA_DVD_PLUS_RW; } } // some older DVD-ROM drives claim to support DVD+R DL if( d->writeCapabilities & MEDIA_DVD_PLUS_R ) { cmd[2] = FEATURE_DVD_PLUS_RW_DUAL_LAYER>>8; cmd[3] = FEATURE_DVD_PLUS_RW_DUAL_LAYER; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD+RW Double Layer"; d->readCapabilities |= MEDIA_DVD_PLUS_RW_DL; if( header[12] & 0x1 ) d->writeCapabilities |= MEDIA_DVD_PLUS_RW_DL; } } cmd[2] = FEATURE_DVD_PLUS_R_DUAL_LAYER>>8; cmd[3] = FEATURE_DVD_PLUS_R_DUAL_LAYER; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD+R Double Layer"; d->readCapabilities |= MEDIA_DVD_PLUS_R_DL; if( header[12] & 0x1 ) d->writeCapabilities |= MEDIA_DVD_PLUS_R_DL; } } } // // Blue Ray // // We do not care for the different BD classes and versions here // cmd[2] = FEATURE_BD_READ>>8; cmd[3] = FEATURE_BD_READ; cmd[8] = 8+32; if( !cmd.transport( TR_DIR_READ, header, 40 ) ) { unsigned int len = from4Byte( header ); if( len >= 36 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "BD Read"; if( header[8+8] || header[8+9] || header[8+10] || header[8+11] || header[8+12] || header[8+13] || header[8+14] || header[8+15] ) d->readCapabilities |= MEDIA_BD_RE; if( header[8+16] || header[8+17] || header[8+18] || header[8+19] || header[8+20] || header[8+21] || header[8+22] || header[8+23] ) d->readCapabilities |= MEDIA_BD_R; if( header[8+24] || header[8+25] || header[8+26] || header[8+27] || header[8+28] || header[8+29] || header[8+30] || header[8+31] ) d->readCapabilities |= MEDIA_BD_ROM; } } cmd[2] = FEATURE_BD_WRITE>>8; cmd[3] = FEATURE_BD_WRITE; cmd[8] = 8+24; if( !cmd.transport( TR_DIR_READ, header, 32 ) ) { unsigned int len = from4Byte( header ); if( len >= 28 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "BD Write"; if( header[8+8] || header[8+9] || header[8+10] || header[8+11] || header[8+12] || header[8+13] || header[8+14] || header[8+15] ) d->writeCapabilities |= MEDIA_BD_RE; if( header[8+16] || header[8+17] || header[8+18] || header[8+19] || header[8+20] || header[8+21] || header[8+22] || header[8+23] ) { d->writeCapabilities |= MEDIA_BD_R; d->writeModes |= WRITINGMODE_SRM; cmd[2] = FEATURE_BD_PSEUDO_OVERWRITE>>8; cmd[3] = FEATURE_BD_PSEUDO_OVERWRITE; cmd[8] = 8+8; if( !cmd.transport( TR_DIR_READ, header, 8+8 ) ) { unsigned int len = from4Byte( header ); if( len >= 4+8 ) { d->writeModes |= WRITINGMODE_SRM_POW; } } cmd[2] = FEATURE_RANDOM_WRITABLE>>8; cmd[3] = FEATURE_RANDOM_WRITABLE; cmd[8] = 8+16; if( !cmd.transport( TR_DIR_READ, header, 8+16 ) ) { unsigned int len = from4Byte( header ); if( len >= 4+16 ) { d->writeModes |= WRITINGMODE_RRM; } } } } } // // DVD-R(W) // cmd[2] = FEATURE_DVD_R_RW_WRITE>>8; cmd[3] = FEATURE_DVD_R_RW_WRITE; cmd[8] = 16; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "DVD-R/-RW Write"; #ifdef WORDS_BIGENDIAN struct dvd_r_rw_write_feature { unsigned char reserved1 : 1; unsigned char BUF : 1; // Burnfree unsigned char reserved2 : 2; unsigned char RDL : 1; unsigned char testwrite : 1; // Simulation write support unsigned char dvd_rw : 1; // DVD-RW Writing unsigned char reserved3 : 1; unsigned char reserved4[3]; }; #else struct dvd_r_rw_write_feature { unsigned char reserved3 : 1; unsigned char dvd_rw : 1; // DVD-RW Writing unsigned char testwrite : 1; // Simulation write support unsigned char RDL : 1; unsigned char reserved2 : 2; unsigned char BUF : 1; // Burnfree unsigned char reserved1 : 1; unsigned char reserved4[3]; }; #endif struct dvd_r_rw_write_feature* p = (struct dvd_r_rw_write_feature*)&header[12]; if( p->BUF ) d->burnfree = true; d->writeCapabilities |= (MEDIA_DVD_R|MEDIA_DVD_R_SEQ); if( p->dvd_rw ) d->writeCapabilities |= (MEDIA_DVD_RW|MEDIA_DVD_RW_SEQ); if( p->RDL ) d->writeCapabilities |= (MEDIA_DVD_R_DL|MEDIA_DVD_R_DL_SEQ); d->dvdMinusTestwrite = p->testwrite; } } // // DVD-RW restricted overwrite check // cmd[2] = FEATURE_RIGID_RESTRICTED_OVERWRITE>>8; cmd[3] = FEATURE_RIGID_RESTRICTED_OVERWRITE; cmd[8] = 16; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "Rigid Restricted Overwrite"; d->writeModes |= WRITINGMODE_RES_OVWR; d->writeCapabilities |= (MEDIA_DVD_RW|MEDIA_DVD_RW_OVWR); } } // // DVD-R Dual Layer Layer // cmd[2] = FEATURE_LAYER_JUMP_RECORDING>>8; cmd[3] = FEATURE_LAYER_JUMP_RECORDING; cmd[8] = 12; if( !cmd.transport( TR_DIR_READ, header, 12 ) ) { // Now the jump feature is longer than 4 bytes but we don't need the link sizes. unsigned int len = from4Byte( header ); if( len >= 8 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "Layer Jump Recording"; d->writeCapabilities |= (MEDIA_DVD_R_DL|MEDIA_DVD_R_DL_JUMP); d->writeModes |= WRITINGMODE_LAYER_JUMP; } } // // HD-DVD-ROM // cmd[2] = FEATURE_HD_DVD_READ>>8; cmd[3] = FEATURE_HD_DVD_READ; cmd[8] = 16; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "HD-DVD Read"; d->readCapabilities |= MEDIA_HD_DVD_ROM; if( header[8+4] & 0x1 ) d->readCapabilities |= MEDIA_HD_DVD_R; if( header[8+6] & 0x1 ) d->readCapabilities |= MEDIA_HD_DVD_RAM; } } // // HD-DVD-R(AM) // cmd[2] = FEATURE_HD_DVD_WRITE>>8; cmd[3] = FEATURE_HD_DVD_WRITE; cmd[8] = 16; if( !cmd.transport( TR_DIR_READ, header, 16 ) ) { unsigned int len = from4Byte( header ); if( len >= 12 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " feature: " << "HD-DVD Write"; if( header[8+4] & 0x1 ) d->writeCapabilities |= MEDIA_HD_DVD_R; if( header[8+6] & 0x1 ) d->writeCapabilities |= MEDIA_HD_DVD_RAM; } } // // Get the profiles // // the max len of the returned data is 8 (header) + 4 (feature) + 255 (additional length) // cmd[2] = FEATURE_PROFILE_LIST>>8; cmd[3] = FEATURE_PROFILE_LIST; cmd[8] = 12; // get the number of returned profiles first if( !cmd.transport( TR_DIR_READ, header, 12 ) ) { unsigned int len = from4Byte( header ) + 4; if( len >= 12 ) { cmd[7] = len>>8; cmd[8] = len; if( !cmd.transport( TR_DIR_READ, header, len ) ) { int featureLen( header[11] ); for( int j = 0; j < featureLen; j+=4 ) { short profile = from2Byte( &header[12+j] ); switch (profile) { case 0x08: d->supportedProfiles |= MEDIA_CD_ROM; break; case 0x09: d->supportedProfiles |= MEDIA_CD_R; break; case 0x0A: d->supportedProfiles |= MEDIA_CD_RW; break; case 0x10: d->supportedProfiles |= MEDIA_DVD_ROM; // d->readCapabilities |= MEDIA_DVD_ROM; break; case 0x11: d->supportedProfiles |= MEDIA_DVD_R_SEQ; // d->writeCapabilities |= (MEDIA_DVD_R|MEDIA_DVD_R_SEQ); break; case 0x12: d->supportedProfiles |= MEDIA_DVD_RAM; // d->readCapabilities |= (MEDIA_DVD_RAM|MEDIA_DVD_ROM); // d->writeCapabilities |= MEDIA_DVD_RAM; break; case 0x13: d->supportedProfiles |= MEDIA_DVD_RW_OVWR; // d->writeCapabilities |= (MEDIA_DVD_RW|MEDIA_DVD_RW_OVWR); break; case 0x14: d->supportedProfiles |= MEDIA_DVD_RW_SEQ; // d->writeCapabilities |= (MEDIA_DVD_RW|MEDIA_DVD_R|MEDIA_DVD_RW_SEQ|MEDIA_DVD_R_SEQ); break; case 0x15: d->supportedProfiles |= MEDIA_DVD_R_DL_SEQ; // d->writeCapabilities |= (MEDIA_DVD_R|MEDIA_DVD_R_DL|MEDIA_DVD_R_SEQ|MEDIA_DVD_R_DL_SEQ); break; case 0x16: d->supportedProfiles |= MEDIA_DVD_R_DL_JUMP; // d->writeCapabilities |= (MEDIA_DVD_R|MEDIA_DVD_R_DL||MEDIA_DVD_R_DL_JUMP); break; case 0x1A: d->supportedProfiles |= MEDIA_DVD_PLUS_RW; // d->writeCapabilities |= MEDIA_DVD_PLUS_RW; break; case 0x1B: d->supportedProfiles |= MEDIA_DVD_PLUS_R; // d->writeCapabilities |= MEDIA_DVD_PLUS_R; break; case 0x2A: d->supportedProfiles |= MEDIA_DVD_PLUS_RW_DL; // d->writeCapabilities |= MEDIA_DVD_PLUS_RW_DL; break; case 0x2B: d->supportedProfiles |= MEDIA_DVD_PLUS_R_DL; // d->writeCapabilities |= MEDIA_DVD_PLUS_R_DL; break; case 0x40: d->supportedProfiles |= MEDIA_BD_ROM; break; case 0x41: d->supportedProfiles |= MEDIA_BD_R_SRM; break; case 0x42: d->supportedProfiles |= MEDIA_BD_R_RRM; break; case 0x43: d->supportedProfiles |= MEDIA_BD_RE; break; case 0x50: d->supportedProfiles |= MEDIA_HD_DVD_ROM; break; case 0x51: d->supportedProfiles |= MEDIA_HD_DVD_R; break; case 0x52: d->supportedProfiles |= MEDIA_HD_DVD_RAM; break; default: kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " unknown profile: " << profile << endl; } } // some older DVD-ROM drives claim to support DVD+R DL if( !(d->supportedProfiles & MEDIA_DVD_PLUS_R) ) { // remove DVD+R DL capability // d->writeCapabilities &= ~MEDIA_DVD_PLUS_R_DL; d->supportedProfiles &= ~MEDIA_DVD_PLUS_R_DL; } } } } } void K3b::Device::Device::checkFor2AFeatures() { unsigned char* mm_cap_buffer = 0; unsigned int mm_cap_len = 0; if( modeSense( &mm_cap_buffer, mm_cap_len, 0x2A ) ) { mm_cap_page_2A* mm_p = (mm_cap_page_2A*)(mm_cap_buffer+8); if( mm_p->BUF ) d->burnfree = true; if( mm_p->cd_r_write ) d->writeCapabilities |= MEDIA_CD_R; else d->writeCapabilities &= ~MEDIA_CD_R; if( mm_p->cd_rw_write ) d->writeCapabilities |= MEDIA_CD_RW; else d->writeCapabilities &= ~MEDIA_CD_RW; if( mm_p->dvd_r_write ) d->writeCapabilities |= MEDIA_DVD_R; else d->writeCapabilities &= ~MEDIA_DVD_R; if( mm_p->dvd_rom_read || mm_p->dvd_r_read ) d->readCapabilities |= MEDIA_DVD_ROM; d->maxReadSpeed = from2Byte(mm_p->max_read_speed); d->bufferSize = from2Byte( mm_p->buffer_size ); delete [] mm_cap_buffer; } else { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": read mode page 2A failed!"; } } void K3b::Device::Device::checkWritingModes() { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( !open() ) return; // header size is 8 unsigned char* buffer = 0; unsigned int dataLen = 0; if( !modeSense( &buffer, dataLen, 0x05 ) ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": modeSense 0x05 failed!" << endl << "(K3b::Device::Device) " << blockDeviceName() << ": Cannot check write modes." << endl; } else if( dataLen < 18 ) { // 8 bytes header + 10 bytes used modepage kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": Missing modepage 0x05 data." << endl << "(K3b::Device::Device) " << blockDeviceName() << ": Cannot check write modes." << endl; } else { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": dataLen: " << dataLen; wr_param_page_05* mp = (struct wr_param_page_05*)(buffer+8); // reset some stuff to be on the safe side mp->PS = 0; mp->BUFE = 0; mp->multi_session = 0; mp->test_write = 0; mp->LS_V = 0; mp->copy = 0; mp->fp = 0; mp->host_appl_code= 0; mp->session_format = 0; mp->audio_pause_len[0] = 0; mp->audio_pause_len[1] = 150; // WRITINGMODE_TAO mp->write_type = 0x01; // Track-at-once mp->track_mode = 4; // MMC-4 says: 5, cdrecord uses 4 ? mp->dbtype = 8; // Mode 1 // kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": modeselect WRITINGMODE_TAO data: "; // debugBitfield( buffer, dataLen ); // // if a writer does not support WRITINGMODE_TAO it surely does not support WRITINGMODE_SAO or WRITINGMODE_RAW writing since WRITINGMODE_TAO is the minimal // requirement // kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for TAO"; if( modeSelect( buffer, dataLen, 1, 0 ) ) { d->writeModes |= WRITINGMODE_TAO; d->writeCapabilities |= MEDIA_CD_R; // WRITINGMODE_SAO mp->write_type = 0x02; // Session-at-once kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for SAO"; if( modeSelect( buffer, dataLen, 1, 0 ) ) d->writeModes |= WRITINGMODE_SAO; // mp->dbtype = 1; // Raw data with P and Q Sub-channel (2368 bytes) // if( modeSelect( buffer, dataLen, 1, 0 ) ) { // d->writeModes |= WRITINGMODE_RAW_R16; // } kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for SAO_R96P"; mp->dbtype = 2; // Raw data with P-W Sub-channel (2448 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { d->writeModes |= WRITINGMODE_SAO_R96P; } kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for SAO_R96R"; mp->dbtype = 3; // Raw data with P-W raw Sub-channel (2448 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { d->writeModes |= WRITINGMODE_SAO_R96R; } kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for RAW_R16"; // WRITINGMODE_RAW mp->write_type = 0x03; // WRITINGMODE_RAW mp->dbtype = 1; // Raw data with P and Q Sub-channel (2368 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { d->writeModes |= WRITINGMODE_RAW; d->writeModes |= WRITINGMODE_RAW_R16; } kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for RAW_R96P"; mp->dbtype = 2; // Raw data with P-W Sub-channel (2448 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { d->writeModes |= WRITINGMODE_RAW; d->writeModes |= WRITINGMODE_RAW_R96P; } kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": checking for RAW_R96R"; mp->dbtype = 3; // Raw data with P-W raw Sub-channel (2448 bytes) if( modeSelect( buffer, dataLen, 1, 0 ) ) { d->writeModes |= WRITINGMODE_RAW; d->writeModes |= WRITINGMODE_RAW_R96R; } } else { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": modeSelect with WRITINGMODE_TAO failed. No writer"; } delete [] buffer; } if( needToClose ) close(); } int K3b::Device::Device::getMaxWriteSpeedVia2A() const { int ret = 0; unsigned char* data = 0; unsigned int dataLen = 0; if( modeSense( &data, dataLen, 0x2A ) ) { mm_cap_page_2A* mm = (mm_cap_page_2A*)&data[8]; // MMC1 used byte 18 and 19 for the max write speed if( dataLen > 19 ) ret = from2Byte( mm->max_write_speed ); delete [] data; } return ret; } int K3b::Device::Device::determineMaximalWriteSpeed() const { int ret = 0; if( mediaType() & MEDIA_CD_ALL ) { ret = getMaxWriteSpeedVia2A(); if ( ret > 0 ) return ret; } QList list = determineSupportedWriteSpeeds(); if( !list.isEmpty() ) { for( QList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it ) ret = qMax( ret, *it ); } if( ret > 0 ) return ret; else return d->maxWriteSpeed; } QList K3b::Device::Device::determineSupportedWriteSpeeds() const { QList ret; if( burner() ) { // // Tests with all my drives resulted in 2A for CD and GET PERFORMANCE for DVD media // as the valid method of speed detection. // MediaType m = mediaType(); if( m & MEDIA_CD_ALL ) { if( !getSupportedWriteSpeedsVia2A( ret, m ) ) getSupportedWriteSpeedsViaGP( ret, m ); // restrict to max speed, although deprecated in MMC3 is still used everywhere and // cdrecord also uses it as the max writing speed. int max = 0; unsigned char* data = 0; unsigned int dataLen = 0; if( modeSense( &data, dataLen, 0x2A ) ) { mm_cap_page_2A* mm = (mm_cap_page_2A*)&data[8]; // MMC1 used byte 18 and 19 for the max write speed if( dataLen > 19 ) max = from2Byte( mm->max_write_speed ); delete [] data; if( max > 0 ) { while( !ret.isEmpty() && ret.last() > max ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " writing speed " << ret.last() << " higher than max " << max << endl; ret.pop_back(); } } } } else { if( !getSupportedWriteSpeedsViaGP( ret, m ) ) getSupportedWriteSpeedsVia2A( ret, m ); } // construct writing speeds for old devices if ( ret.isEmpty() && K3b::Device::isCdMedia( m ) ) { int max = getMaxWriteSpeedVia2A(); for ( int i = 1; i <= max/SPEED_FACTOR_CD; i *= 2 ) { ret.append( i * SPEED_FACTOR_CD ); } } } return ret; } bool K3b::Device::Device::getSupportedWriteSpeedsVia2A( QList& list, MediaType mediaType ) const { unsigned char* data = 0; unsigned int dataLen = 0; if( modeSense( &data, dataLen, 0x2A ) ) { mm_cap_page_2A* mm = (mm_cap_page_2A*)&data[8]; if( dataLen > 32 ) { // we have descriptors unsigned int numDesc = from2Byte( mm->num_wr_speed_des ); // Some CDs writer returns the number of bytes that contain // the descriptors rather than the number of descriptors // Ensure number of descriptors claimed actually fits in the data // returned by the mode sense command. if( numDesc > ((dataLen - 32 - 8) / 4) ) numDesc = (dataLen - 32 - 8) / 4; cd_wr_speed_performance* wr = (cd_wr_speed_performance*)mm->wr_speed_des; kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": Number of supported write speeds via 2A: " << numDesc << endl; for( unsigned int i = 0; i < numDesc; ++i ) { int s = (int)from2Byte( wr[i].wr_speed_supp ); // // some DVD writers report CD writing speeds here // If that is the case we cannot rely on the reported speeds // and need to use the values gained from GET PERFORMANCE. // if( isDvdMedia( mediaType ) && s < 1352 ) { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " Invalid DVD speed: " << s << " KB/s" << endl; list.clear(); break; } else { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " : " << s << " KB/s" << endl; if( isDvdMedia( mediaType ) ) s = fixupDvdWritingSpeed( s ); // sort the list QList::iterator it = list.begin(); while( it != list.end() && *it < s ) ++it; list.insert( it, s ); } } } delete [] data; } return !list.isEmpty(); } bool K3b::Device::Device::getSupportedWriteSpeedsViaGP( QList& list, MediaType mediaType ) const { unsigned char* data = 0; unsigned int dataLen = 0; if( getPerformance( &data, dataLen, 0x3, 0x0 ) ) { int numDesc = (dataLen - 8)/16; kDebug() << "(K3b::Device::Device) " << blockDeviceName() << ": Number of supported write speeds via GET PERFORMANCE: " << numDesc << endl; for( int i = 0; i < numDesc; ++i ) { int s = from4Byte( &data[20+i*16] ); // Looks as if the code below does not make sense with most drives // if( !( data[4+i*16] & 0x2 ) ) { // kDebug() << "(K3b::Device::Device) " << blockDeviceName() // << " No write speed: " << s << " KB/s" << endl; // continue; // } if( isDvdMedia( mediaType ) && s < 1352 ) { // // Does this ever happen? // kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " Invalid DVD speed: " << s << " KB/s" << endl; } else { kDebug() << "(K3b::Device::Device) " << blockDeviceName() << " : " << s << " KB/s" << endl; if( isDvdMedia( mediaType ) ) s = fixupDvdWritingSpeed( s ); QList::iterator it = list.begin(); while( it != list.end() && *it < s ) ++it; // the speed might already have been found in the 2a modepage if( it == list.end() || *it != s ) list.insert( it, s ); } } delete [] data; } return !list.isEmpty(); } int K3b::Device::Device::getIndex( unsigned long lba ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( !open() ) return -1; int ret = -1; // // first try readCd // unsigned char readData[16]; ::memset( readData, 0, 16 ); // // The index is found in the Mode-1 Q which occupies at least 9 out of 10 successive CD frames // It can be indentified by ADR == 1 // // So if the current sector does not provide Mode-1 Q subchannel we try the previous. // if( readCd( readData, 16, 1, // CD-DA 0, // no DAP lba, 1, false, false, false, false, false, 0, 2 // Q-Subchannel ) ) { // byte 0: 4 bits CONTROL (MSB) + 4 bits ADR (LSB) if( (readData[0]&0x0f) == 0x1 ) ret = readData[2]; // search previous sector for Mode1 Q Subchannel else if( readCd( readData, 16, 1, // CD-DA 0, // no DAP lba-1, 1, false, false, false, false, false, 0, 2 // Q-Subchannel ) ) { if( (readData[0]&0x0f) == 0x1 ) ret = readData[2]; else ret = -2; } } else { kDebug() << "(K3b::Device::Device::getIndex) readCd failed. Trying seek."; unsigned char* data = 0; unsigned int dataLen = 0; if( seek( lba ) && readSubChannel( &data, dataLen, 1, 0 ) ) { // byte 5: 4 bits ADR (MSB) + 4 bits CONTROL (LSB) if( dataLen > 7 && (data[5]>>4 & 0x0F) == 0x1 ) { ret = data[7]; } else if( seek( lba-1 ) && readSubChannel( &data, dataLen, 1, 0 ) ) { if( dataLen > 7 && (data[5]>>4 & 0x0F) == 0x1 ) ret = data[7]; else ret = -2; } delete [] data; } else kDebug() << "(K3b::Device::Device::getIndex) seek or readSubChannel failed."; } if( needToClose ) close(); return ret; } bool K3b::Device::Device::searchIndex0( unsigned long startSec, unsigned long endSec, long& pregapStart ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( !open() ) return false; bool ret = false; int lastIndex = getIndex( endSec ); if( lastIndex == 0 ) { // there is a pregap // let's find the position where the index turns to 0 // we jump in 1 sec steps backwards until we find an index > 0 unsigned long sector = endSec; while( lastIndex == 0 && sector > startSec ) { sector -= 75; if( sector < startSec ) sector = startSec; lastIndex = getIndex(sector); } if( lastIndex == 0 ) { kDebug() << "(K3b::Device::Device) warning: no index != 0 found."; } else { // search forward to the first index = 0 while( getIndex( sector ) != 0 && sector < endSec ) sector++; pregapStart = sector; ret = true; } } else if( lastIndex > 0 ) { // no pregap pregapStart = -1; ret = true; } if( needToClose ) close(); return ret; } bool K3b::Device::Device::indexScan( K3b::Device::Toc& toc ) const { // if the device is already opened we do not close it // to allow fast multiple method calls in a row bool needToClose = !isOpen(); if( !open() ) return false; bool ret = true; for( Toc::iterator it = toc.begin(); it != toc.end(); ++it ) { Track& track = *it; if( track.type() == Track::TYPE_AUDIO ) { track.setIndices( QList() ); long index0 = -1; if( searchIndex0( track.firstSector().lba(), track.lastSector().lba(), index0 ) ) { kDebug() << "(K3b::Device::Device) found index 0: " << index0; } if( index0 > 0 ) track.setIndex0( K3b::Msf( index0 - track.firstSector().lba() ) ); else track.setIndex0( 0 ); if( index0 > 0 ) searchIndexTransitions( track.firstSector().lba(), index0-1, track ); else searchIndexTransitions( track.firstSector().lba(), track.lastSector().lba(), track ); } } if( needToClose ) close(); return ret; } void K3b::Device::Device::searchIndexTransitions( long start, long end, K3b::Device::Track& track ) const { kDebug() << "(K3b::Device::Device) searching for index transitions between " << start << " and " << end << endl; int startIndex = getIndex( start ); int endIndex = getIndex( end ); if( startIndex < 0 || endIndex < 0 ) { kDebug() << "(K3b::Device::Device) could not retrieve index values."; } else { kDebug() << "(K3b::Device::Device) indices: " << start << " - " << startIndex << " and " << end << " - " << endIndex << endl; if( startIndex != endIndex ) { if( start+1 == end ) { QList indices = track.indices(); kDebug() << "(K3b::Device::Device) found index transition: " << endIndex << " " << end; while ( indices.count() < endIndex ) indices.append( K3b::Msf() ); // we save the index relative to the first sector indices[endIndex-1] = K3b::Msf( end ) - track.firstSector(); track.setIndices( indices ); // FIXME: better API } else { searchIndexTransitions( start, start+(end-start)/2, track ); searchIndexTransitions( start+(end-start)/2, end, track ); } } } } int K3b::Device::Device::copyrightProtectionSystemType() const { unsigned char* dvdheader = 0; unsigned int dataLen = 0; if( readDvdStructure( &dvdheader, dataLen, 0x1 ) ) { int ret = -1; if( dataLen >= 6 ) ret = dvdheader[4]; delete [] dvdheader; return ret; } else return -1; } bool K3b::Device::Device::getNextWritableAdress( unsigned int& lastSessionStart, unsigned int& nextWritableAdress ) const { bool success = false; // FIXME: add CD media handling int m = mediaType(); if( m & MEDIA_DVD_ALL ) { // DVD+RW always returns complete if( m & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) ) return false; unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscInformation( &data, dataLen ) ) { disc_info_t* inf = (disc_info_t*)data; // // The state of the last session has to be "empty" (0x0) or "incomplete" (0x1) // The procedure here is taken from the dvd+rw-tools // if( !(inf->border & 0x2) ) { // the incomplete track number is the first track in the last session (the empty session) int nextTrack = inf->first_track_l|inf->first_track_m<<8; unsigned char* trackData = 0; unsigned int trackDataLen = 0; // Read start address of the incomplete track if( readTrackInformation( &trackData, trackDataLen, 0x1, nextTrack ) ) { nextWritableAdress = from4Byte( &trackData[8] ); delete [] trackData; // Read start address of the first track in the last session if( readTocPmaAtip( &trackData, trackDataLen, 0x1, false, 0x0 ) ) { lastSessionStart = from4Byte( &trackData[8] ); delete [] trackData; success = true; } } } } delete [] data; } return success; } int K3b::Device::Device::nextWritableAddress() const { unsigned char* data = 0; unsigned int dataLen = 0; int nwa = -1; if( readDiscInformation( &data, dataLen ) ) { disc_info_t* inf = (disc_info_t*)data; // // The state of the last session has to be "empty" (0x0) or "incomplete" (0x1) // The procedure here is taken from the dvd+rw-tools and wodim // if( !(inf->border & 0x2) ) { // the incomplete track number is the first track in the last session (the empty session) int nextTrack = inf->first_track_l|inf->first_track_m<<8; unsigned char* trackData = 0; unsigned int trackDataLen = 0; // Read start address of the incomplete track if( readTrackInformation( &trackData, trackDataLen, 0x1, nextTrack ) ) { nwa = from4Byte( &trackData[8] ); delete [] trackData; } // Read start address of the invisible track else if ( readTrackInformation( &trackData, trackDataLen, 0x1, 0xff ) ) { nwa = from4Byte( &trackData[8] ); delete [] trackData; } } delete [] data; } return nwa; } QByteArray K3b::Device::Device::mediaId( int mediaType ) const { QString id; if( mediaType & MEDIA_CD_ALL ) { // FIXME: } else if( mediaType & MEDIA_DVD_MINUS_ALL ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readDvdStructure( &data, dataLen, 0x0E ) ) { if( data[4+16] == 3 && data[4+24] == 4 ) { id.sprintf( "%6.6s%-6.6s", data+4+17, data+4+25 ); } delete [] data; } } else if( mediaType & MEDIA_DVD_PLUS_ALL ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readDvdStructure( &data, dataLen, 0x11 ) || readDvdStructure( &data, dataLen, 0x0 ) ) { id.sprintf( "%8.8s/%3.3s", data+23, data+31 ); delete [] data; } } else if( mediaType & MEDIA_BD_ALL ) { unsigned char* data = 0; unsigned int dataLen = 0; if( readDiscStructure( &data, dataLen, 1, 0 ) ) { if( data[4+0] == 'D' && data[4+1] == 'I' ) id.sprintf ("%6.6s/%-3.3s", data+4+100, data+4+106 ); delete [] data; } } return id.toLatin1(); } // int K3b::Device::Device::ioctl( int request, ... ) const // { // int r = -1; // #if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD) // d->mutex.lock(); // va_list ap; // va_start( ap, request ); // r = ::ioctl( d->deviceFd, request, ap ); // va_end( ap ); // d->mutex.unlock(); // #endif // return r; // } void K3b::Device::Device::usageLock() const { d->mutex.lock(); } void K3b::Device::Device::usageUnlock() const { d->mutex.unlock(); } diff --git a/libk3bdevice/k3bhalconnection.cpp b/libk3bdevice/k3bhalconnection.cpp index 311752c09..479fd050c 100644 --- a/libk3bdevice/k3bhalconnection.cpp +++ b/libk3bdevice/k3bhalconnection.cpp @@ -1,128 +1,132 @@ /* * * Copyright (C) 2005-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bhalconnection.h" #include "k3bdevice.h" #include #include #include #include #include Q_GLOBAL_STATIC( K3b::Device::HalConnection, s_instance ) class K3b::Device::HalConnection::Private { public: }; K3b::Device::HalConnection* K3b::Device::HalConnection::instance() { return s_instance(); } K3b::Device::HalConnection::HalConnection( QObject* parent ) : QObject( parent ) { d = new Private(); } K3b::Device::HalConnection::~HalConnection() { delete d; } int K3b::Device::HalConnection::lock( Device* dev ) { + kDebug() << dev->blockDeviceName(); + QDBusInterface halIface( "org.freedesktop.Hal", dev->solidDevice().udi(), "org.freedesktop.Hal.Device", QDBusConnection::systemBus() ); if ( halIface.isValid() ) { QDBusMessage msg = halIface.call( QLatin1String( "Lock" ), QLatin1String( "Locked by the K3b libraries" ) ); if ( msg.type() == QDBusMessage::ErrorMessage ) { kDebug() << "Failed to lock device through HAL:" << msg.errorMessage(); if( msg.errorName() == QLatin1String( "org.freedesktop.Hal.NoSuchDevice" ) ) { return org_freedesktop_Hal_NoSuchDevice; } else if( msg.errorName() == QLatin1String( "org.freedesktop.Hal.DeviceAlreadyLocked" ) ) { return org_freedesktop_Hal_DeviceAlreadyLocked; } else if( msg.errorName() == QLatin1String( "org.freedesktop.Hal.PermissionDenied" ) ) { return org_freedesktop_Hal_PermissionDenied; } else { kDebug() << "Unkown HAL error:" << msg.errorName(); return -1; } } else { return org_freedesktop_Hal_Success; } } else { kDebug() << "Could not connect to device object:" << halIface.path(); return org_freedesktop_Hal_CommunicationError; } } int K3b::Device::HalConnection::unlock( Device* dev ) { + kDebug() << dev->blockDeviceName(); + QDBusInterface halIface( "org.freedesktop.Hal", dev->solidDevice().udi(), "org.freedesktop.Hal.Device", QDBusConnection::systemBus() ); if ( halIface.isValid() ) { QDBusMessage msg = halIface.call( QLatin1String( "Unlock" ) ); if ( msg.type() == QDBusMessage::ErrorMessage ) { kDebug() << "Failed to unlock device through HAL:" << msg.errorMessage(); if( msg.errorName() == QLatin1String( "org.freedesktop.Hal.NoSuchDevice" ) ) { return org_freedesktop_Hal_NoSuchDevice; } else if( msg.errorName() == QLatin1String( "org.freedesktop.Hal.DeviceAlreadyLocked" ) ) { return org_freedesktop_Hal_DeviceAlreadyLocked; } else if( msg.errorName() == QLatin1String( "org.freedesktop.Hal.PermissionDenied" ) ) { return org_freedesktop_Hal_PermissionDenied; } else { kDebug() << "Unkown HAL error:" << msg.errorName(); return -1; } } else { return org_freedesktop_Hal_Success; } } else { kDebug() << "Could not connect to device object:" << halIface.path(); return org_freedesktop_Hal_CommunicationError; } } #include "k3bhalconnection.moc" diff --git a/plugins/encoder/external/k3bexternalencoder.h b/plugins/encoder/external/k3bexternalencoder.h index d977e46b6..bf26b249d 100644 --- a/plugins/encoder/external/k3bexternalencoder.h +++ b/plugins/encoder/external/k3bexternalencoder.h @@ -1,60 +1,60 @@ /* * * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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. */ #ifndef _K3B_EXTERNAL_ENCODER_H_ #define _K3B_EXTERNAL_ENCODER_H_ #include -#include +#include "k3bprocess.h" class K3bExternalEncoder : public K3b::AudioEncoder { Q_OBJECT public: K3bExternalEncoder( QObject* parent, const QVariantList& ); ~K3bExternalEncoder(); QStringList extensions() const; QString fileTypeComment( const QString& ) const; int pluginSystemVersion() const { return K3B_PLUGIN_SYSTEM_VERSION; } /** * reimplemented since the external program is intended to write the file * TODO: allow writing to stdout. */ bool openFile( const QString& ext, const QString& filename, const K3b::Msf& length ); void closeFile(); private Q_SLOTS: void slotExternalProgramFinished( int, QProcess::ExitStatus ); void slotExternalProgramOutput(); private: void finishEncoderInternal(); bool initExternalEncoder( const QString& extension ); long encodeInternal( const char* data, Q_ULONG len ); void setMetaDataInternal( MetaDataField, const QString& ); bool writeWaveHeader(); class Private; Private* d; }; #endif diff --git a/plugins/encoder/sox/k3bsoxencoder.cpp b/plugins/encoder/sox/k3bsoxencoder.cpp index 4671e72f4..7041a6f8f 100644 --- a/plugins/encoder/sox/k3bsoxencoder.cpp +++ b/plugins/encoder/sox/k3bsoxencoder.cpp @@ -1,471 +1,453 @@ /* * * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bsoxencoder.h" #include -#include +#include "k3bprocess.h" #include #include #include #include #include -#include #include #include #include #include #include #include #include -//Added by qt3to4: #include #include K3B_EXPORT_PLUGIN( k3bsoxencoder, K3bSoxEncoder ) // the sox external program class K3bSoxProgram : public K3b::ExternalProgram { public: K3bSoxProgram() : K3b::ExternalProgram( "sox" ) { } bool scan( const QString& p ) { if( p.isEmpty() ) return false; QString path = p; QFileInfo fi( path ); if( fi.isDir() ) { if( path[path.length()-1] != '/' ) path.append("/"); path.append("sox"); } if( !QFile::exists( path ) ) return false; K3b::ExternalBin* bin = 0; // probe version KProcess vp; vp.setOutputChannelMode( KProcess::MergedChannels ); vp << path << "-h"; vp.start(); if( vp.waitForFinished( -1 ) ) { QByteArray out = vp.readAll(); int pos = out.indexOf( "sox: SoX Version" ); if ( pos < 0 ) pos = out.indexOf( "sox: SoX v" ); // newer sox versions int endPos = out.indexOf( '\n', pos ); if( pos > 0 && endPos > 0 ) { pos += 17; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = out.mid( pos, endPos-pos ); addBin( bin ); return true; } else { pos = out.indexOf( "sox: Version" ); endPos = out.indexOf( '\n', pos ); if( pos > 0 && endPos > 0 ) { pos += 13; bin = new K3b::ExternalBin( this ); bin->path = path; bin->version = out.mid( pos, endPos-pos ); addBin( bin ); return true; } else return false; } } else return false; } }; class K3bSoxEncoder::Private { public: - Private() - : process(0) { - } - - K3b::Process* process; + K3b::Process process; QString fileName; }; K3bSoxEncoder::K3bSoxEncoder( QObject* parent, const QVariantList& ) : K3b::AudioEncoder( parent ) { if( k3bcore->externalBinManager()->program( "sox" ) == 0 ) k3bcore->externalBinManager()->addProgram( new K3bSoxProgram() ); d = new Private(); + d->process.setSplitStdout(true); + + connect( &d->process, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(slotSoxFinished(int, QProcess::ExitStatus)) ); + connect( &d->process, SIGNAL(stdoutLine(QString)), + this, SLOT(slotSoxOutputLine(QString)) ); } K3bSoxEncoder::~K3bSoxEncoder() { - delete d->process; delete d; } void K3bSoxEncoder::finishEncoderInternal() { - if( d->process ) { - if( d->process->isRunning() ) { - d->process->closeWriteChannel(); + if( d->process.isRunning() ) { + d->process.closeWriteChannel(); - // this is kind of evil... - // but we need to be sure the process exited when this method returnes - d->process->waitForFinished(-1); - } + // this is kind of evil... + // but we need to be sure the process exited when this method returnes + d->process.waitForFinished(-1); } } void K3bSoxEncoder::slotSoxFinished( int exitCode, QProcess::ExitStatus exitStatus ) { if( (exitStatus != QProcess::NormalExit) || (exitCode != 0) ) kDebug() << "(K3bSoxEncoder) sox exited with error."; } bool K3bSoxEncoder::openFile( const QString& ext, const QString& filename, const K3b::Msf& length ) { d->fileName = filename; return initEncoderInternal( ext, length ); } void K3bSoxEncoder::closeFile() { finishEncoderInternal(); } bool K3bSoxEncoder::initEncoderInternal( const QString& extension, const K3b::Msf& /*length*/ ) { const K3b::ExternalBin* soxBin = k3bcore->externalBinManager()->binObject( "sox" ); if( soxBin ) { - delete d->process; - d->process = new K3b::Process(); - d->process->setSplitStdout(true); - d->process->setRawStdin(true); - - connect( d->process, SIGNAL(finished(int, QProcess::ExitStatus)), - this, SLOT(slotSoxFinished(int, QProcess::ExitStatus)) ); - connect( d->process, SIGNAL(stderrLine(const QString&)), - this, SLOT(slotSoxOutputLine(const QString&)) ); - connect( d->process, SIGNAL(stdoutLine(const QString&)), - this, SLOT(slotSoxOutputLine(const QString&)) ); // input settings - *d->process << soxBin->path - << "-t" << "raw" // raw samples - << "-r" << "44100" // samplerate - << "-s" // signed linear - << "-w" // 16-bit words - << "-c" << "2" // stereo - << "-"; // read from stdin + d->process << soxBin->path + << "-t" << "raw" // raw samples + << "-r" << "44100" // samplerate + << "-s" // signed linear + << "-w" // 16-bit words + << "-c" << "2" // stereo + << "-"; // read from stdin // output settings - *d->process << "-t" << extension; + d->process << "-t" << extension; KSharedConfig::Ptr c = KGlobal::config(); KConfigGroup grp(c,"K3bSoxEncoderPlugin" ); if( grp.readEntry( "manual settings", false ) ) { - *d->process << "-r" << QString::number( grp.readEntry( "samplerate", 44100 ) ) - << "-c" << QString::number( grp.readEntry( "channels", 2 ) ); + d->process << "-r" << QString::number( grp.readEntry( "samplerate", 44100 ) ) + << "-c" << QString::number( grp.readEntry( "channels", 2 ) ); int size = grp.readEntry( "data size", 16 ); - *d->process << ( size == 8 ? QString("-b") : ( size == 32 ? QString("-l") : QString("-w") ) ); + d->process << ( size == 8 ? QString("-b") : ( size == 32 ? QString("-l") : QString("-w") ) ); QString encoding = grp.readEntry( "data encoding", "signed" ); if( encoding == "unsigned" ) - *d->process << "-u"; + d->process << "-u"; else if( encoding == "u-law" ) - *d->process << "-U"; + d->process << "-U"; else if( encoding == "A-law" ) - *d->process << "-A"; + d->process << "-A"; else if( encoding == "ADPCM" ) - *d->process << "-a"; + d->process << "-a"; else if( encoding == "IMA_ADPCM" ) - *d->process << "-i"; + d->process << "-i"; else if( encoding == "GSM" ) - *d->process << "-g"; + d->process << "-g"; else if( encoding == "Floating-point" ) - *d->process << "-f"; + d->process << "-f"; else - *d->process << "-s"; + d->process << "-s"; } - *d->process << d->fileName; + d->process << d->fileName; kDebug() << "***** sox parameters:"; - QString s = d->process->joinedArgs(); + QString s = d->process.joinedArgs(); kDebug() << s << flush; - return d->process->start( K3Process::All ); + return d->process.start( KProcess::MergedChannels ); } else { kDebug() << "(K3bSoxEncoder) could not find sox bin."; return false; } } long K3bSoxEncoder::encodeInternal( const char* data, Q_ULONG len ) { - if( d->process ) { - if( d->process->isRunning() ) - return d->process->write( data, len ); - else - return -1; - } + if( d->process.isRunning() ) + return d->process.write( data, len ); else return -1; } void K3bSoxEncoder::slotSoxOutputLine( const QString& line ) { kDebug() << "(sox) " << line; } QStringList K3bSoxEncoder::extensions() const { static QStringList s_extensions; if( s_extensions.isEmpty() ) { s_extensions << "au" << "8svx" << "aiff" << "avr" << "cdr" << "cvs" << "dat" << "gsm" << "hcom" << "maud" << "sf" << "sph" << "smp" << "txw" << "vms" << "voc" << "wav" << "wve" << "raw"; } if( k3bcore->externalBinManager()->foundBin( "sox" ) ) return s_extensions; else return QStringList(); // no sox -> no encoding } QString K3bSoxEncoder::fileTypeComment( const QString& ext ) const { if( ext == "au" ) return i18n("Sun AU"); else if( ext == "8svx" ) return i18n("Amiga 8SVX"); else if( ext == "aiff" ) return i18n("AIFF"); else if( ext == "avr" ) return i18n("Audio Visual Research"); else if( ext == "cdr" ) return i18n("CD-R"); else if( ext == "cvs" ) return i18n("CVS"); else if( ext == "dat" ) return i18n("Text Data"); else if( ext == "gsm" ) return i18n("GSM Speech"); else if( ext == "hcom" ) return i18n("Macintosh HCOM"); else if( ext == "maud" ) return i18n("Maud (Amiga)"); else if( ext == "sf" ) return i18n("IRCAM"); else if( ext == "sph" ) return i18n("SPHERE"); else if( ext == "smp" ) return i18n("Turtle Beach SampleVision"); else if( ext == "txw" ) return i18n("Yamaha TX-16W"); else if( ext == "vms" ) return i18n("VMS"); else if( ext == "voc" ) return i18n("Sound Blaster VOC"); else if( ext == "wav" ) return i18n("Wave (Sox)"); else if( ext == "wve" ) return i18n("Psion 8-bit A-law"); else if( ext == "raw" ) return i18n("Raw"); else return i18n("Error"); } long long K3bSoxEncoder::fileSize( const QString&, const K3b::Msf& msf ) const { // for now we make a rough assumption based on the settings KSharedConfig::Ptr c = KGlobal::config(); KConfigGroup grp(c, "K3bSoxEncoderPlugin" ); if( grp.readEntry( "manual settings", false ) ) { int sr = grp.readEntry( "samplerate", 44100 ); int ch = grp.readEntry( "channels", 2 ); int wsize = grp.readEntry( "data size", 16 ); return msf.totalFrames()*sr*ch*wsize/75; } else { // fallback to raw return msf.audioBytes(); } } K3b::PluginConfigWidget* K3bSoxEncoder::createConfigWidget( QWidget* parent ) const { return new K3bSoxEncoderSettingsWidget( parent ); } K3bSoxEncoderSettingsWidget::K3bSoxEncoderSettingsWidget( QWidget* parent ) : K3b::PluginConfigWidget( parent ) { setupUi( this ); m_editSamplerate->setValidator( new QIntValidator( m_editSamplerate ) ); } K3bSoxEncoderSettingsWidget::~K3bSoxEncoderSettingsWidget() { } void K3bSoxEncoderSettingsWidget::loadConfig() { KSharedConfig::Ptr c = KGlobal::config(); KConfigGroup grp(c, "K3bSoxEncoderPlugin" ); m_checkManual->setChecked( grp.readEntry( "manual settings", false ) ); int channels = grp.readEntry( "channels", 2 ); m_comboChannels->setCurrentIndex( channels == 4 ? 2 : channels-1 ); m_editSamplerate->setText( QString::number( grp.readEntry( "samplerate", 44100 ) ) ); QString encoding = grp.readEntry( "data encoding", "signed" ); if( encoding == "unsigned" ) m_comboEncoding->setCurrentIndex(1); else if( encoding == "u-law" ) m_comboEncoding->setCurrentIndex(2); else if( encoding == "A-law" ) m_comboEncoding->setCurrentIndex(3); else if( encoding == "ADPCM" ) m_comboEncoding->setCurrentIndex(4); else if( encoding == "IMA_ADPCM" ) m_comboEncoding->setCurrentIndex(5); else if( encoding == "GSM" ) m_comboEncoding->setCurrentIndex(6); else if( encoding == "Floating-point" ) m_comboEncoding->setCurrentIndex(7); else m_comboEncoding->setCurrentIndex(0); int size = grp.readEntry( "data size", 16 ); m_comboSize->setCurrentIndex( size == 8 ? 0 : ( size == 32 ? 2 : 1 ) ); } void K3bSoxEncoderSettingsWidget::saveConfig() { KSharedConfig::Ptr c = KGlobal::config(); KConfigGroup grp (c, "K3bSoxEncoderPlugin" ); grp.writeEntry( "manual settings", m_checkManual->isChecked() ); grp.writeEntry( "channels", m_comboChannels->currentIndex() == 0 ? 1 : ( m_comboChannels->currentIndex() == 2 ? 4 : 2 ) ); grp.writeEntry( "data size", m_comboSize->currentIndex() == 0 ? 8 : ( m_comboSize->currentIndex() == 2 ? 32 : 16 ) ); grp.writeEntry( "samplerate", m_editSamplerate->text().toInt() ); QString enc; switch( m_comboEncoding->currentIndex() ) { case 1: enc = "unsigned"; break; case 2: enc = "u-law"; break; case 3: enc = "A-law"; break; case 4: enc = "ADPCM"; break; case 5: enc = "IMA_ADPCM"; break; case 6: enc = "GSM"; break; case 7: enc = "Floating-point"; break; default: enc = "signed"; break; } grp.writeEntry( "data encoding", enc ); } #include "k3bsoxencoder.moc" diff --git a/plugins/encoder/sox/k3bsoxencoder.h b/plugins/encoder/sox/k3bsoxencoder.h index 31f33d5a0..16bc27ba2 100644 --- a/plugins/encoder/sox/k3bsoxencoder.h +++ b/plugins/encoder/sox/k3bsoxencoder.h @@ -1,77 +1,77 @@ /* * * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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. */ #ifndef _K3B_SOX_ENCODER_H_ #define _K3B_SOX_ENCODER_H_ -#include +#include "k3bprocess.h" #include #include #include "ui_base_k3bsoxencoderconfigwidget.h" class K3bSoxEncoder : public K3b::AudioEncoder { Q_OBJECT public: K3bSoxEncoder( QObject* parent, const QVariantList& ); ~K3bSoxEncoder(); QStringList extensions() const; QString fileTypeComment( const QString& ) const; long long fileSize( const QString&, const K3b::Msf& msf ) const; int pluginSystemVersion() const { return K3B_PLUGIN_SYSTEM_VERSION; } K3b::PluginConfigWidget* createConfigWidget( QWidget* parent = 0) const; /** * reimplemented since sox writes the file itself */ bool openFile( const QString& ext, const QString& filename, const K3b::Msf& ); void closeFile(); private Q_SLOTS: void slotSoxFinished( int, QProcess::ExitStatus ); void slotSoxOutputLine( const QString& ); private: void finishEncoderInternal(); bool initEncoderInternal( const QString& extension, const K3b::Msf& length ); long encodeInternal( const char* data, Q_ULONG len ); class Private; Private* d; }; class K3bSoxEncoderSettingsWidget : public K3b::PluginConfigWidget, public Ui::base_K3bSoxEncoderConfigWidget { Q_OBJECT public: K3bSoxEncoderSettingsWidget( QWidget* parent = 0 ); ~K3bSoxEncoderSettingsWidget(); public Q_SLOTS: void loadConfig(); void saveConfig(); }; #endif diff --git a/src/k3b.cpp b/src/k3b.cpp index c0c89ef36..8baf189eb 100644 --- a/src/k3b.cpp +++ b/src/k3b.cpp @@ -1,1516 +1,1520 @@ /* * * Copyright (C) 1998-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 // include files for QT #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // application specific includes #include "k3b.h" #include "k3baction.h" #include "k3bappdevicemanager.h" #include "k3bapplication.h" #include "k3baudiodecoder.h" #include "k3baudiodoc.h" #include "k3baudiotrackdialog.h" #include "k3baudioview.h" #include "k3bcuefileparser.h" #include "k3bdatadoc.h" #include "k3bdataview.h" #include "k3bdeviceselectiondialog.h" #include "k3bdirview.h" #include "k3bexternalbinmanager.h" #include "k3bfiletreeview.h" #include "k3bglobals.h" #include "k3biso9660.h" #include "k3bjob.h" #include "k3bmediacache.h" #include "k3bmediaselectiondialog.h" #include "k3bmedium.h" #include "k3bmixeddoc.h" #include "k3bmixedview.h" #include "k3bmovixdoc.h" #include "k3bmovixview.h" #include "k3bprojectburndialog.h" #include "k3bpassivepopup.h" #include "k3bplugin.h" #include "k3bpluginmanager.h" #include "k3bprojectmanager.h" #include "k3bprojecttabwidget.h" #include "k3bsidepanel.h" #include "k3bsignalwaiter.h" #include "k3bstdguiitems.h" #include "k3bsystemproblemdialog.h" #include "k3bstatusbarmanager.h" #include "k3btempdirselectionwidget.h" #include "k3bthemedheader.h" #include "k3bthememanager.h" #include "k3burlnavigator.h" #include "k3bvcddoc.h" #include "k3bvcdview.h" #include "k3bvideodvddoc.h" #include "k3bvideodvdview.h" #include "k3bview.h" #include "k3bwelcomewidget.h" #include "misc/k3bimagewritingdialog.h" #include "misc/k3bmediacopydialog.h" #include "misc/k3bmediaformattingdialog.h" #include "option/k3boptiondialog.h" #include "projects/k3bdatamultisessionimportdialog.h" class K3b::MainWindow::Private { public: K3b::Doc* lastDoc; K3b::WelcomeWidget* welcomeWidget; QWidget* documentHull; }; K3b::MainWindow::MainWindow() : KXmlGuiWindow(0) { //setup splitter behavior //manager()->setSplitterHighResolution(true); //manager()->setSplitterOpaqueResize(true); //manager()->setSplitterKeepSize(true); d = new Private; d->lastDoc = 0; setPlainCaption( i18n("K3b - The CD and DVD Kreator") ); // ///////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts initActions(); initView(); initStatusBar(); createGUI(); // ///////////////////////////////////////////////////////////////// // incorporate Device Manager into main window factory()->addClient( k3bappcore->appDeviceManager() ); connect( k3bappcore->appDeviceManager(), SIGNAL(detectingDiskInfo(K3b::Device::Device*)), this, SLOT(showDiskInfo(K3b::Device::Device*)) ); // we need the actions for the welcomewidget KConfigGroup grp( config(), "Welcome Widget" ); d->welcomeWidget->loadConfig( grp ); // fill the tabs action menu m_documentTab->insertAction( actionFileSave ); m_documentTab->insertAction( actionFileSaveAs ); m_documentTab->insertAction( actionFileClose ); // ///////////////////////////////////////////////////////////////// // disable actions at startup slotStateChanged( "state_project_active", KXMLGUIClient::StateReverse ); connect( k3bappcore->projectManager(), SIGNAL(newProject(K3b::Doc*)), this, SLOT(createClient(K3b::Doc*)) ); connect( k3bcore->deviceManager(), SIGNAL(changed()), this, SLOT(slotCheckSystemTimed()) ); // FIXME: now make sure the welcome screen is displayed completely resize( 780, 550 ); // getMainDockWidget()->resize( getMainDockWidget()->size().expandedTo( d->welcomeWidget->sizeHint() ) ); // m_dirTreeDock->resize( QSize( m_dirTreeDock->sizeHint().width(), m_dirTreeDock->height() ) ); readOptions(); } K3b::MainWindow::~MainWindow() { delete d; } KSharedConfig::Ptr K3b::MainWindow::config() const { return KGlobal::config(); } void K3b::MainWindow::initActions() { // merge in the device actions from the device manager // operator+= is deprecated but I know no other way to do this. Why does the KDE app framework // need to have all actions in the mainwindow's actioncollection anyway (or am I just to stupid to // see the correct solution?) actionFileOpen = KStandardAction::open(this, SLOT(slotFileOpen()), actionCollection()); actionFileOpenRecent = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(const KUrl&)), actionCollection()); actionFileSave = KStandardAction::save(this, SLOT(slotFileSave()), actionCollection()); actionFileSaveAs = KStandardAction::saveAs(this, SLOT(slotFileSaveAs()), actionCollection()); actionFileSaveAll = K3b::createAction(this, i18n("Save All"), "document-save-all", 0, this, SLOT(slotFileSaveAll()), actionCollection(), "file_save_all" ); actionFileClose = KStandardAction::close(this, SLOT(slotFileClose()), actionCollection()); actionFileCloseAll = K3b::createAction(this, i18n("Close All"), 0, 0, this, SLOT(slotFileCloseAll()), actionCollection(), "file_close_all" ); actionFileQuit = KStandardAction::quit(this, SLOT(slotFileQuit()), actionCollection()); actionViewStatusBar = KStandardAction::showStatusbar(this, SLOT(slotViewStatusBar()), actionCollection()); actionSettingsConfigure = KStandardAction::preferences(this, SLOT(slotSettingsConfigure()), actionCollection() ); // the tip action (void)KStandardAction::tipOfDay(this, SLOT(slotShowTips()), actionCollection() ); (void)KStandardAction::keyBindings( this, SLOT( slotConfigureKeys() ), actionCollection() ); KStandardAction::configureToolbars(this, SLOT(slotEditToolbars()), actionCollection()); setStandardToolBarMenuEnabled(true); KStandardAction::showMenubar( this, SLOT(slotShowMenuBar()), actionCollection() ); //FIXME kde4 verify it actionFileNewMenu = new KActionMenu( i18n("&New Project"),this ); actionFileNewMenu->setIcon( KIcon( "document-new" ) ); actionCollection()->addAction( "file_new", actionFileNewMenu ); actionFileNewAudio = K3b::createAction(this,i18n("New &Audio CD Project"), "audiocd", 0, this, SLOT(slotNewAudioDoc()), actionCollection(), "file_new_audio"); actionFileNewData = K3b::createAction(this,i18n("New &Data Project"), "datacd", 0, this, SLOT(slotNewDataDoc()), actionCollection(), "file_new_data"); actionFileNewMixed = K3b::createAction(this,i18n("New &Mixed Mode CD Project"), "mixedcd", 0, this, SLOT(slotNewMixedDoc()), actionCollection(), "file_new_mixed"); actionFileNewVcd = K3b::createAction(this,i18n("New &Video CD Project"), "videocd", 0, this, SLOT(slotNewVcdDoc()), actionCollection(), "file_new_vcd"); actionFileNewMovix = K3b::createAction(this,i18n("New &eMovix Project"), "emovix", 0, this, SLOT(slotNewMovixDoc()), actionCollection(), "file_new_movix"); actionFileNewVideoDvd = K3b::createAction(this,i18n("New V&ideo DVD Project"), "videodvd", 0, this, SLOT(slotNewVideoDvdDoc()), actionCollection(), "file_new_video_dvd"); actionFileContinueMultisession = K3b::createAction(this,i18n("Continue Multisession Project"), "datacd", 0, this, SLOT(slotContinueMultisession()), actionCollection(), "file_continue_multisession" ); actionFileNewMenu->setDelayed( false ); actionFileNewMenu->addAction( actionFileNewData ); actionFileNewMenu->addAction( actionFileContinueMultisession ); actionFileNewMenu->addSeparator(); actionFileNewMenu->addAction( actionFileNewAudio ); actionFileNewMenu->addAction( actionFileNewMixed ); actionFileNewMenu->addSeparator(); actionFileNewMenu->addAction( actionFileNewVcd ); actionFileNewMenu->addAction( actionFileNewVideoDvd ); actionFileNewMenu->addSeparator(); actionFileNewMenu->addAction( actionFileNewMovix ); actionProjectAddFiles = K3b::createAction(this, i18n("&Add Files..."), "document-open", 0, this, SLOT(slotProjectAddFiles()), actionCollection(), "project_add_files"); KAction* actionClearProject = K3b::createAction(this,i18n("&Clear Project"), QApplication::isRightToLeft() ? "edit-clear-locationbar-rtl" : "edit-clear-locationbar-ltr", 0, this, SLOT(slotClearProject()), actionCollection(), "project_clear_project" ); actionViewDocumentHeader = new KToggleAction(i18n("Show Document Header"),this); QAction *action= actionCollection()->addAction("view_document_header",actionViewDocumentHeader); connect( action , SIGNAL(toggled(bool)) , this , SLOT(slotViewDocumentHeader()) ); KAction* actionToolsFormatMedium = K3b::createAction( this, i18n("&Format/Erase rewritable disk..."), "formatdvd", 0, this, SLOT(slotFormatMedium()), actionCollection(), "tools_format_medium" ); actionToolsFormatMedium->setIconText( i18n( "Format" ) ); actionToolsWriteImage = K3b::createAction( this, i18n("&Burn Image..."), "burn_cdimage", 0, this, SLOT(slotWriteImage()), actionCollection(), "tools_write_image" ); KAction* actionToolsMediaCopy = K3b::createAction( this, i18n("Copy &Medium..."), "cdcopy", 0, this, SLOT(slotMediaCopy()), actionCollection(), "tools_copy_medium" ); actionToolsMediaCopy->setIconText( i18n( "Copy" ) ); actionToolsCddaRip = K3b::createAction( this, i18n("Rip Audio CD..."), "cddarip", 0, this, SLOT(slotCddaRip()), actionCollection(), "tools_cdda_rip" ); actionToolsVideoDvdRip = K3b::createAction( this, i18n("Rip Video DVD..."), "videodvd", 0, this, SLOT(slotVideoDvdRip()), actionCollection(), "tools_videodvd_rip" ); actionToolsVideoCdRip = K3b::createAction( this, i18n("Rip Video CD..."), "videocd", 0, this, SLOT(slotVideoCdRip()), actionCollection(), "tools_videocd_rip" ); (void)K3b::createAction( this, i18n("System Check"), 0, 0, this, SLOT(slotManualCheckSystem()), actionCollection(), "help_check_system" ); #ifdef BUILD_K3BSETUP actionSettingsK3bSetup = K3b::createAction( this, i18n("&Setup System Permissions..."), "configure", 0, this, SLOT(slotK3bSetup()), actionCollection(), "settings_k3bsetup" ); #endif #ifdef K3B_DEBUG (void)K3b::createAction(this, "Test Media Selection ComboBox", 0, 0, this, SLOT(slotMediaSelectionTester()), actionCollection(), "test_media_selection" ); #endif actionFileNewMenu->setToolTip(i18n("Creates a new project")); actionFileNewData->setToolTip( i18n("Creates a new data project") ); actionFileNewAudio->setToolTip( i18n("Creates a new audio CD project") ); actionFileNewMovix->setToolTip( i18n("Creates a new eMovix project") ); actionFileNewVcd->setToolTip( i18n("Creates a new Video CD project") ); actionToolsFormatMedium->setToolTip( i18n("Open the rewritable disk formatting/erasing dialog") ); actionToolsWriteImage->setToolTip( i18n("Write an Iso9660, cue/bin, or cdrecord clone image to an optical disc") ); actionToolsMediaCopy->setToolTip( i18n("Open the media copy dialog") ); actionFileOpen->setToolTip(i18n("Opens an existing project")); actionFileOpenRecent->setToolTip(i18n("Opens a recently used file")); actionFileSave->setToolTip(i18n("Saves the current project")); actionFileSaveAs->setToolTip(i18n("Saves the current project to a new url")); actionFileSaveAll->setToolTip(i18n("Saves all open projects")); actionFileClose->setToolTip(i18n("Closes the current project")); actionFileCloseAll->setToolTip(i18n("Closes all open projects")); actionFileQuit->setToolTip(i18n("Quits the application")); actionSettingsConfigure->setToolTip( i18n("Configure K3b settings") ); #ifdef BUILD_K3BSETUP actionSettingsK3bSetup->setToolTip( i18n("Setup the system permissions (requires root privileges)") ); #endif actionToolsCddaRip->setToolTip( i18n("Digitally extract tracks from an audio CD") ); actionToolsVideoDvdRip->setToolTip( i18n("Transcode Video DVD titles") ); actionToolsVideoCdRip->setToolTip( i18n("Extract tracks from a Video CD") ); actionProjectAddFiles->setToolTip( i18n("Add files to the current project") ); actionClearProject->setToolTip( i18n("Clear the current project") ); //FIXME kde4 // make sure the tooltips are used for the menu //actionCollection()->setHighlightingEnabled( true ); } QList K3b::MainWindow::projects() const { return k3bappcore->projectManager()->projects(); } void K3b::MainWindow::slotConfigureKeys() { KShortcutsDialog::configure( actionCollection(),KShortcutsEditor::LetterShortcutsDisallowed, this ); } void K3b::MainWindow::initStatusBar() { m_statusBarManager = new K3b::StatusBarManager( this ); } void K3b::MainWindow::initView() { setDockOptions( AnimatedDocks ); // setup main docking things // --- Document Dock ---------------------------------------------------------------------------- d->documentHull = new QWidget( this ); setCentralWidget( d->documentHull ); QGridLayout* documentHullLayout = new QGridLayout( d->documentHull ); documentHullLayout->setMargin( 2 ); documentHullLayout->setSpacing( 0 ); m_documentHeader = new K3b::ThemedHeader( d->documentHull ); m_documentHeader->setTitle( i18n("Current Projects") ); m_documentHeader->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter ); m_documentHeader->setLeftPixmap( K3b::Theme::PROJECT_LEFT ); m_documentHeader->setRightPixmap( K3b::Theme::PROJECT_RIGHT ); // add the document tab to the styled document box m_documentTab = new K3b::ProjectTabWidget( d->documentHull ); documentHullLayout->addWidget( m_documentHeader, 0, 0 ); documentHullLayout->addWidget( m_documentTab, 1, 0 ); connect( m_documentTab, SIGNAL(currentChanged(QWidget*)), this, SLOT(slotCurrentDocChanged()) ); d->welcomeWidget = new K3b::WelcomeWidget( this, m_documentTab ); m_documentTab->addTab( d->welcomeWidget, i18n("Quickstart") ); // --------------------------------------------------------------------------------------------- // --- Directory Dock -------------------------------------------------------------------------- m_dirTreeDock = new QDockWidget( this ); m_dirTreeDock->setObjectName("dirtreedock"); m_dirTreeDock->setFeatures( QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable ); addDockWidget( Qt::TopDockWidgetArea, m_dirTreeDock ); QAction *action = m_dirTreeDock->toggleViewAction(); action->setText(i18n("Show Directories")); actionCollection()->addAction( "view_dir_tree", action ); K3b::FileTreeView* sidePanel = new K3b::FileTreeView( m_dirTreeDock ); //K3b::SidePanel* sidePanel = new K3b::SidePanel( this, m_dirTreeDock, "sidePanel" ); m_dirTreeDock->setWidget( sidePanel ); // --------------------------------------------------------------------------------------------- // --- Contents Dock --------------------------------------------------------------------------- m_contentsDock = new QDockWidget( this ); m_contentsDock->setObjectName("contentsdock"); m_contentsDock->setFeatures( QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable ); addDockWidget ( Qt::TopDockWidgetArea, m_contentsDock ); action = m_contentsDock->toggleViewAction(); action->setText( i18n("Show Contents") ); actionCollection()->addAction( "view_contents", action ); m_dirView = new K3b::DirView( sidePanel/*->fileTreeView()*/, m_contentsDock ); m_contentsDock->setWidget( m_dirView ); //m_contentsDock->manualDock( m_dirTreeDock, K3DockWidget::DockRight, 2000 ); // --- filetreecombobox-toolbar ---------------------------------------------------------------- KFilePlacesModel* filePlacesModel = new KFilePlacesModel; K3b::UrlNavigator* urlNavigator = new K3b::UrlNavigator( filePlacesModel, this ); connect( urlNavigator, SIGNAL(activated(const KUrl&)), m_dirView, SLOT(showUrl(const KUrl& )) ); connect( urlNavigator, SIGNAL(activated(K3b::Device::Device*)), m_dirView, SLOT(showDevice(K3b::Device::Device* )) ); connect( m_dirView, SIGNAL(urlEntered(const KUrl&)), urlNavigator, SLOT(setUrl(const KUrl&)) ); connect( m_dirView, SIGNAL(deviceSelected(K3b::Device::Device*)), urlNavigator, SLOT(setDevice(K3b::Device::Device*)) ); QWidgetAction * urlNavigatorAction = new QWidgetAction(this); urlNavigatorAction->setDefaultWidget(urlNavigator); urlNavigatorAction->setText(i18n("&Quick Dir Selector")); actionCollection()->addAction( "quick_dir_selector", urlNavigatorAction ); // --------------------------------------------------------------------------------------------- } void K3b::MainWindow::createClient( K3b::Doc* doc ) { kDebug(); // create the proper K3b::View (maybe we should put this into some other class like K3b::ProjectManager) K3b::View* view = 0; switch( doc->type() ) { case K3b::Doc::AUDIO: view = new K3b::AudioView( static_cast(doc), m_documentTab ); break; case K3b::Doc::DATA: view = new K3b::DataView( static_cast(doc), m_documentTab ); break; case K3b::Doc::MIXED: { K3b::MixedDoc* mixedDoc = static_cast(doc); view = new K3b::MixedView( mixedDoc, m_documentTab ); mixedDoc->dataDoc()->setView( view ); mixedDoc->audioDoc()->setView( view ); break; } case K3b::Doc::VCD: view = new K3b::VcdView( static_cast(doc), m_documentTab ); break; case K3b::Doc::MOVIX: view = new K3b::MovixView( static_cast(doc), m_documentTab ); break; case K3b::Doc::VIDEODVD: view = new K3b::VideoDvdView( static_cast(doc), m_documentTab ); break; } doc->setView( view ); view->setWindowTitle( doc->URL().fileName() ); m_documentTab->insertTab( doc ); m_documentTab->setCurrentWidget( view ); slotCurrentDocChanged(); } K3b::View* K3b::MainWindow::activeView() const { QWidget* w = m_documentTab->currentWidget(); if( K3b::View* view = dynamic_cast(w) ) return view; else return 0; } K3b::Doc* K3b::MainWindow::activeDoc() const { if( activeView() ) return activeView()->getDocument(); else return 0; } K3b::Doc* K3b::MainWindow::openDocument(const KUrl& url) { slotStatusMsg(i18n("Opening file...")); // // First we check if this is an iso image in case someone wants to open one this way // if( !isCdDvdImageAndIfSoOpenDialog( url ) ) { // see if it's an audio cue file K3b::CueFileParser parser( url.path() ); if( parser.isValid() && parser.toc().contentType() == K3b::Device::AUDIO ) { K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::AUDIO ); doc->addUrl( url ); return doc; } else { // check, if document already open. If yes, set the focus to the first view K3b::Doc* doc = k3bappcore->projectManager()->findByUrl( url ); if( doc ) { doc->view()->setFocus(); return doc; } doc = k3bappcore->projectManager()->openProject( url ); if( doc == 0 ) { KMessageBox::error (this,i18n("Could not open document."), i18n("Error")); return 0; } actionFileOpenRecent->addUrl(url); return doc; } } else return 0; } void K3b::MainWindow::saveOptions() { KConfigGroup recentGrp(config(),"Recent Files"); actionFileOpenRecent->saveEntries( recentGrp ); KConfigGroup grpFileView( config(), "file view" ); m_dirView->saveConfig( grpFileView ); KConfigGroup grpWindows(config(), "main_window_settings"); saveMainWindowSettings( grpWindows ); k3bcore->saveSettings( config() ); KConfigGroup grp(config(), "Welcome Widget" ); d->welcomeWidget->saveConfig( grp ); KConfigGroup grpOption( config(), "General Options" ); grpOption.writeEntry( "Show Document Header", actionViewDocumentHeader->isChecked() ); config()->sync(); } void K3b::MainWindow::readOptions() { KConfigGroup grp( config(), "General Options" ); bool bViewDocumentHeader = grp.readEntry("Show Document Header", true); actionViewDocumentHeader->setChecked(bViewDocumentHeader); // initialize the recent file list KConfigGroup recentGrp(config(), "Recent Files"); actionFileOpenRecent->loadEntries( recentGrp ); KConfigGroup grpWindow(config(), "main_window_settings"); applyMainWindowSettings( grpWindow ); KConfigGroup grpFileView( config(), "file view" ); m_dirView->readConfig( grpFileView ); slotViewDocumentHeader(); } void K3b::MainWindow::saveProperties( KConfigGroup& grp ) { // 1. put saved projects in the config // 2. save every modified project in "~/.kde/share/apps/k3b/sessions/" + KApp->sessionId() // 3. save the url of the project (might be something like "AudioCD1") in the config // 4. save the status of every project (modified/saved) QString saveDir = KGlobal::dirs()->saveLocation( "appdata", "sessions/" + qApp->sessionId() + "/", true ); // // FIXME: for some reason the config entries are not properly stored when using the default // // KMainWindow session config. Since I was not able to find the bug I use another config object // // ---------------------------------------------------------- // KConfig c( saveDir + "list", KConfig::SimpleConfig ); // KConfigGroup grp( &c, "Saved Session" ); // // ---------------------------------------------------------- QList docs = k3bappcore->projectManager()->projects(); grp.writeEntry( "Number of projects", docs.count() ); int cnt = 1; Q_FOREACH( K3b::Doc* doc, docs ) { // the "name" of the project (or the original url if isSaved()) grp.writePathEntry( QString("%1 url").arg(cnt), (doc)->URL().url() ); // is the doc modified grp.writeEntry( QString("%1 modified").arg(cnt), (doc)->isModified() ); // has the doc already been saved? grp.writeEntry( QString("%1 saved").arg(cnt), (doc)->isSaved() ); // where does the session management save it? If it's not modified and saved this is // the same as the url KUrl saveUrl = (doc)->URL(); if( !(doc)->isSaved() || (doc)->isModified() ) saveUrl = KUrl( saveDir + QString::number(cnt) ); grp.writePathEntry( QString("%1 saveurl").arg(cnt), saveUrl.url() ); // finally save it k3bappcore->projectManager()->saveProject( doc, saveUrl ); ++cnt; } // c.sync(); } // FIXME:move this to K3b::ProjectManager void K3b::MainWindow::readProperties( const KConfigGroup& grp ) { // FIXME: do not delete the files here. rather do it when the app is exited normally // since that's when we can be sure we never need the session stuff again. // 1. read all projects from the config // 2. simply open all of themg // 3. reset the saved urls and the modified state // 4. delete "~/.kde/share/apps/k3b/sessions/" + KApp->sessionId() QString saveDir = KGlobal::dirs()->saveLocation( "appdata", "sessions/" + qApp->sessionId() + "/", true ); // // FIXME: for some reason the config entries are not properly stored when using the default // // KMainWindow session config. Since I was not able to find the bug I use another config object // // ---------------------------------------------------------- // KConfig c( saveDir + "list"/*, true*/ ); // KConfigGroup grp( &c, "Saved Session" ); // // ---------------------------------------------------------- int cnt = grp.readEntry( "Number of projects", 0 ); /* kDebug() << "(K3b::MainWindow::readProperties) number of projects from last session in " << saveDir << ": " << cnt << endl << " read from config group " << c->group() << endl; */ for( int i = 1; i <= cnt; ++i ) { // in this case the constructor works since we saved as url() KUrl url = grp.readPathEntry( QString("%1 url").arg(i),QString() ); bool modified = grp.readEntry( QString("%1 modified").arg(i),false ); bool saved = grp.readEntry( QString("%1 saved").arg(i),false ); KUrl saveUrl = grp.readPathEntry( QString("%1 saveurl").arg(i),QString() ); // now load the project if( K3b::Doc* doc = k3bappcore->projectManager()->openProject( saveUrl ) ) { // reset the url doc->setURL( url ); doc->setModified( modified ); doc->setSaved( saved ); } else kDebug() << "(K3b::MainWindow) could not open session saved doc " << url.path(); // remove the temp file if( !saved || modified ) QFile::remove( saveUrl.path() ); } // and now remove the temp dir KIO::del( KUrl(saveDir), KIO::HideProgressInfo ); } bool K3b::MainWindow::queryClose() { // // Check if a job is currently running // For now K3b only allows for one major job at a time which means that we only need to cancel // this one job. // if( k3bcore->jobsRunning() ) { // pitty, but I see no possibility to make this work. It always crashes because of the event // management thing mentioned below. So until I find a solution K3b simply will refuse to close // while a job i running return false; // kDebug() << "(K3b::MainWindow::queryClose) jobs running."; // K3b::Job* job = k3bcore->runningJobs().getFirst(); // // now search for the major job (to be on the safe side although for now no subjobs register with the k3bcore) // K3b::JobHandler* jh = job->jobHandler(); // while( jh->isJob() ) { // job = static_cast( jh ); // jh = job->jobHandler(); // } // kDebug() << "(K3b::MainWindow::queryClose) main job found: " << job->jobDescription(); // // now job is the major job and jh should be a widget // QWidget* progressDialog = dynamic_cast( jh ); // kDebug() << "(K3b::MainWindow::queryClose) job active: " << job->active(); // // now ask the user if he/she really wants to cancel this job // if( job->active() ) { // if( KMessageBox::questionYesNo( progressDialog ? progressDialog : this, // i18n("Do you really want to cancel?"), // i18n("Cancel") ) == KMessageBox::Yes ) { // // cancel the job // kDebug() << "(K3b::MainWindow::queryClose) canceling job."; // job->cancel(); // // wait for the job to finish // kDebug() << "(K3b::MainWindow::queryClose) waiting for job to finish."; // K3b::SignalWaiter::waitForJob( job ); // // close the progress dialog // if( progressDialog ) { // kDebug() << "(K3b::MainWindow::queryClose) closing progress dialog."; // progressDialog->close(); // // // // now here we have the problem that due to the whole Qt event thing the exec call (or // // in this case most likely the startJob call) does not return until we leave this method. // // That means that the progress dialog might be deleted by it's parent below (when we // // close docs) before it is deleted by the creator (most likely a projectburndialog). // // That would result in a double deletion and thus a crash. // // So we just reparent the dialog to 0 here so it's (former) parent won't delete it. // // // progressDialog->reparent( 0, QPoint(0,0) ); // } // kDebug() << "(K3b::MainWindow::queryClose) job cleanup done."; // } // else // return false; // } } // // if we are closed by the session manager everything is fine since we store the // current state in saveProperties // if( kapp->sessionSaving() ) return true; // FIXME: do not close the docs here. Just ask for them to be saved and return false // if the user chose cancel for some doc // --------------------------------- // we need to manually close all the views to ensure that // each of them receives a close-event and // the user is asked for every modified doc to save the changes // --------------------------------- while( K3b::View* view = activeView() ) { if( !canCloseDocument(view->doc()) ) return false; closeProject(view->doc()); } return true; } bool K3b::MainWindow::canCloseDocument( K3b::Doc* doc ) { if( !doc->isModified() ) return true; if( !KConfigGroup( config(), "General Options" ).readEntry( "ask_for_saving_changes_on_exit", true ) ) return true; switch ( KMessageBox::warningYesNoCancel( this, i18n("%1 has unsaved data.", doc->URL().fileName() ), i18n("Closing Project"), KStandardGuiItem::save(), - KGuiItem( i18n("&Discard"), "editshred" ) ) ) - { + KGuiItem( i18n("&Discard"), "editshred" ) ) ) { case KMessageBox::Yes: - fileSave( doc ); + if ( !fileSave( doc ) ) + return false; case KMessageBox::No: return true; - default: return false; } } bool K3b::MainWindow::queryExit() { // TODO: call this in K3b::Application somewhere saveOptions(); return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void K3b::MainWindow::slotFileOpen() { slotStatusMsg(i18n("Opening file...")); KUrl::List urls = KFileDialog::getOpenUrls( KUrl(":k3b-projects-folder"), i18n("*.k3b|K3b Projects"), this, i18n("Open Files") ); for( KUrl::List::iterator it = urls.begin(); it != urls.end(); ++it ) { openDocument( *it ); actionFileOpenRecent->addUrl( *it ); } } void K3b::MainWindow::slotFileOpenRecent(const KUrl& url) { slotStatusMsg(i18n("Opening file...")); openDocument(url); } void K3b::MainWindow::slotFileSaveAll() { Q_FOREACH( K3b::Doc* doc, k3bappcore->projectManager()->projects() ) { fileSave( doc ); } } void K3b::MainWindow::slotFileSave() { if( K3b::Doc* doc = activeDoc() ) { fileSave( doc ); } } -void K3b::MainWindow::fileSave( K3b::Doc* doc ) +bool K3b::MainWindow::fileSave( K3b::Doc* doc ) { slotStatusMsg(i18n("Saving file...")); if( doc == 0 ) { doc = activeDoc(); } + if( doc != 0 ) { if( !doc->isSaved() ) - fileSaveAs( doc ); + return fileSaveAs( doc ); else if( !k3bappcore->projectManager()->saveProject( doc, doc->URL()) ) KMessageBox::error (this,i18n("Could not save the current document."), i18n("I/O Error")); } + + return false; } void K3b::MainWindow::slotFileSaveAs() { if( K3b::Doc* doc = activeDoc() ) { fileSaveAs( doc ); } } -void K3b::MainWindow::fileSaveAs( K3b::Doc* doc ) +bool K3b::MainWindow::fileSaveAs( K3b::Doc* doc ) { slotStatusMsg(i18n("Saving file with a new filename...")); - if( doc == 0 ) { + if( !doc ) { doc = activeDoc(); } - if( doc != 0 ) { + if( doc ) { // we do not use the static KFileDialog method here to be able to specify a filename suggestion KFileDialog dlg( KUrl(":k3b-projects-folder"), i18n("*.k3b|K3b Projects"), this); dlg.setCaption( i18n("Save As") ); dlg.setOperationMode( KFileDialog::Saving ); dlg.setSelection( doc->name() ); dlg.exec(); KUrl url = dlg.selectedUrl(); if( url.isValid() ) { KRecentDocument::add( url ); bool exists = KIO::NetAccess::exists( url, KIO::NetAccess::DestinationSide, 0 ); if( !exists || - ( exists && - KMessageBox::warningContinueCancel( this, i18n("Do you want to overwrite %1?", url.prettyUrl() ), - i18n("File Exists"), KGuiItem(i18n("Overwrite")) ) - == KMessageBox::Continue ) ) { + KMessageBox::warningContinueCancel( this, i18n("Do you want to overwrite %1?", url.prettyUrl() ), + i18n("File Exists"), KGuiItem(i18n("Overwrite")) ) + == KMessageBox::Continue ) { - if( !k3bappcore->projectManager()->saveProject( doc, url ) ) { + if( k3bappcore->projectManager()->saveProject( doc, url ) ) { + actionFileOpenRecent->addUrl(url); + return true; + } + else { KMessageBox::error (this,i18n("Could not save the current document."), i18n("I/O Error")); - return; } - else - actionFileOpenRecent->addUrl(url); } } } + + return false; } void K3b::MainWindow::slotFileClose() { slotStatusMsg(i18n("Closing file...")); if( K3b::View* pView = activeView() ) { if( pView ) { K3b::Doc* pDoc = pView->doc(); if( canCloseDocument(pDoc) ) { closeProject(pDoc); } } } slotCurrentDocChanged(); } void K3b::MainWindow::slotFileCloseAll() { while( K3b::View* pView = activeView() ) { if( pView ) { K3b::Doc* pDoc = pView->doc(); if( canCloseDocument(pDoc) ) closeProject(pDoc); else break; } } slotCurrentDocChanged(); } void K3b::MainWindow::closeProject( K3b::Doc* doc ) { // unplug the actions if( factory() ) { if( d->lastDoc == doc ) { factory()->removeClient( static_cast(d->lastDoc->view()) ); d->lastDoc = 0; } } // remove the view from the project tab m_documentTab->removePage( doc->view() ); // remove the project from the manager k3bappcore->projectManager()->removeProject( doc ); // delete view and doc delete doc->view(); delete doc; } void K3b::MainWindow::slotFileQuit() { close(); } void K3b::MainWindow::slotViewStatusBar() { //turn Statusbar on or off if(actionViewStatusBar->isChecked()) { statusBar()->show(); } else { statusBar()->hide(); } } void K3b::MainWindow::slotStatusMsg(const QString &text) { /////////////////////////////////////////////////////////////////// // change status message permanently // statusBar()->clear(); // statusBar()->setItemText(text,1); statusBar()->showMessage( text, 2000 ); } void K3b::MainWindow::slotSettingsConfigure() { K3b::OptionDialog d( this ); d.exec(); // emit a changed signal every time since we do not know if the user selected // "apply" and "cancel" or "ok" emit configChanged( config() ); } void K3b::MainWindow::showOptionDialog( K3b::OptionDialog::ConfigPage index ) { K3b::OptionDialog d( this); d.setCurrentPage( index ); d.exec(); // emit a changed signal every time since we do not know if the user selected // "apply" and "cancel" or "ok" emit configChanged( config() ); } K3b::Doc* K3b::MainWindow::slotNewAudioDoc() { slotStatusMsg(i18n("Creating new Audio CD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::AUDIO ); return doc; } K3b::Doc* K3b::MainWindow::slotNewDataDoc() { slotStatusMsg(i18n("Creating new Data CD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::DATA ); return doc; } K3b::Doc* K3b::MainWindow::slotContinueMultisession() { return K3b::DataMultisessionImportDialog::importSession( 0, this ); } K3b::Doc* K3b::MainWindow::slotNewVideoDvdDoc() { slotStatusMsg(i18n("Creating new Video DVD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::VIDEODVD ); return doc; } K3b::Doc* K3b::MainWindow::slotNewMixedDoc() { slotStatusMsg(i18n("Creating new Mixed Mode CD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::MIXED ); return doc; } K3b::Doc* K3b::MainWindow::slotNewVcdDoc() { slotStatusMsg(i18n("Creating new Video CD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::VCD ); return doc; } K3b::Doc* K3b::MainWindow::slotNewMovixDoc() { slotStatusMsg(i18n("Creating new eMovix Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::MOVIX ); return doc; } void K3b::MainWindow::slotCurrentDocChanged() { // check the doctype K3b::View* v = activeView(); if( v ) { k3bappcore->projectManager()->setActive( v->doc() ); // // There are two possiblities to plug the project actions: // 1. Through KXMLGUIClient::plugActionList // This way we just ask the View for the actionCollection (which it should merge with // the doc's) and plug it into the project menu. // Advantage: easy and clear to handle // Disadvantage: we may only plug all actions at once into one menu // // 2. Through merging the doc as a KXMLGUIClient // This way every view is a KXMLGUIClient and it's GUI is just merged into the MainWindow's. // Advantage: flexible // Disadvantage: every view needs it's own XML file // // if( factory() ) { if( d->lastDoc ) factory()->removeClient( static_cast(d->lastDoc->view()) ); factory()->addClient( v ); d->lastDoc = v->doc(); } else kDebug() << "(K3b::MainWindow) ERROR: could not get KXMLGUIFactory instance."; } else k3bappcore->projectManager()->setActive( 0L ); if( k3bappcore->projectManager()->isEmpty() ) { slotStateChanged( "state_project_active", KXMLGUIClient::StateReverse ); } else { slotStateChanged( "state_project_active", KXMLGUIClient::StateNoReverse ); } // make sure the document header is shown (or not) slotViewDocumentHeader(); } void K3b::MainWindow::slotEditToolbars() { KConfigGroup grp( config(), "main_window_settings" ); saveMainWindowSettings( grp ); KEditToolBar dlg( factory() ); connect( &dlg, SIGNAL(newToolbarConfig()), SLOT(slotNewToolBarConfig()) ); dlg.exec(); } void K3b::MainWindow::slotNewToolBarConfig() { KConfigGroup grp(config(), "main_window_settings"); applyMainWindowSettings(grp); } bool K3b::MainWindow::eject() { KConfigGroup c( config(), "General Options" ); return !c.readEntry( "No cd eject", false ); } void K3b::MainWindow::slotErrorMessage(const QString& message) { KMessageBox::error( this, message ); } void K3b::MainWindow::slotWarningMessage(const QString& message) { KMessageBox::sorry( this, message ); } void K3b::MainWindow::slotWriteImage() { K3b::ImageWritingDialog d( this ); d.exec(); } void K3b::MainWindow::slotWriteImage( const KUrl& url ) { K3b::ImageWritingDialog d( this ); d.setImage( url ); d.exec(); } void K3b::MainWindow::slotProjectAddFiles() { K3b::View* view = activeView(); if( view ) { const QStringList files = KFileDialog::getOpenFileNames( KUrl(":k3b-project-add-files"), i18n("*|All Files"), this, i18n("Select Files to Add to Project") ); KUrl::List urls; for( QStringList::ConstIterator it = files.constBegin(); it != files.constEnd(); it++ ) { KUrl url; url.setPath(*it); urls.append( url ); } if( !urls.isEmpty() ) view->addUrls( urls ); } else KMessageBox::error( this, i18n("Please create a project before adding files"), i18n("No Active Project")); } void K3b::MainWindow::slotK3bSetup() { QStringList args("kcmshell4 k3bsetup2 --lang " + KGlobal::locale()->language()); if( !KProcess::startDetached( K3b::findExe("kdesu"), args ) ) KMessageBox::error( 0, i18n("Could not find kdesu to run K3b::Setup with root privileges. " "Please run it manually as root.") ); } void K3b::MainWindow::formatMedium( K3b::Device::Device* dev ) { K3b::MediaFormattingDialog d( this ); d.setDevice( dev ); d.exec(); } void K3b::MainWindow::slotFormatMedium() { formatMedium( 0 ); } void K3b::MainWindow::mediaCopy( K3b::Device::Device* dev ) { K3b::MediaCopyDialog d( this ); d.setReadingDevice( dev ); d.exec(); } void K3b::MainWindow::slotMediaCopy() { mediaCopy( 0 ); } // void K3b::MainWindow::slotVideoDvdCopy() // { // K3b::VideoDvdCopyDialog d( this ); // d.exec(); // } void K3b::MainWindow::slotShowMenuBar() { if( menuBar()->isVisible() ) menuBar()->hide(); else menuBar()->show(); } void K3b::MainWindow::slotShowTips() { KTipDialog::showTip( this, QString(), true ); } void K3b::MainWindow::slotViewDocumentHeader() { if( actionViewDocumentHeader->isChecked() && !k3bappcore->projectManager()->isEmpty() ) { m_documentHeader->show(); } else { m_documentHeader->hide(); } } K3b::ExternalBinManager* K3b::MainWindow::externalBinManager() const { return k3bcore->externalBinManager(); } K3b::Device::DeviceManager* K3b::MainWindow::deviceManager() const { return k3bcore->deviceManager(); } void K3b::MainWindow::slotDataImportSession() { if( activeView() ) { if( K3b::DataView* view = dynamic_cast(activeView()) ) { view->importSession(); } } } void K3b::MainWindow::slotDataClearImportedSession() { if( activeView() ) { if( K3b::DataView* view = dynamic_cast(activeView()) ) { view->clearImportedSession(); } } } void K3b::MainWindow::slotEditBootImages() { if( activeView() ) { if( K3b::DataView* view = dynamic_cast(activeView()) ) { view->editBootImages(); } } } void K3b::MainWindow::slotCheckSystemTimed() { // run the system check from the event queue so we do not // mess with the device state resetting throughout the app // when called from K3b::DeviceManager::changed QTimer::singleShot( 0, this, SLOT(slotCheckSystem()) ); } void K3b::MainWindow::slotCheckSystem() { K3b::SystemProblemDialog::checkSystem( this, K3b::SystemProblemDialog::NotifyOnlyErrors ); } void K3b::MainWindow::slotManualCheckSystem() { K3b::SystemProblemDialog::checkSystem( this, K3b::SystemProblemDialog::AlwaysNotify ); } void K3b::MainWindow::addUrls( const KUrl::List& urls ) { if( K3b::View* view = activeView() ) { view->addUrls( urls ); } else { // check if the files are all audio we can handle. If so create an audio project bool audio = true; QList fl = k3bcore->pluginManager()->plugins( "AudioDecoder" ); for( KUrl::List::const_iterator it = urls.begin(); it != urls.end(); ++it ) { const KUrl& url = *it; if( QFileInfo(url.path()).isDir() ) { audio = false; break; } bool a = false; Q_FOREACH( K3b::Plugin* plugin, fl ) { if( static_cast( plugin )->canDecode( url ) ) { a = true; break; } } if( !a ) { audio = a; break; } } if( !audio && urls.count() == 1 ) { // see if it's an audio cue file K3b::CueFileParser parser( urls.first().path() ); if( parser.isValid() && parser.toc().contentType() == K3b::Device::AUDIO ) { audio = true; } } if( audio ) static_cast(slotNewAudioDoc()->view())->addUrls( urls ); else if( urls.count() > 1 || !isCdDvdImageAndIfSoOpenDialog( urls.first() ) ) static_cast(slotNewDataDoc()->view())->addUrls( urls ); } } void K3b::MainWindow::slotClearProject() { K3b::Doc* doc = k3bappcore->projectManager()->activeDoc(); if( doc ) { if( KMessageBox::warningContinueCancel( this, i18n("Clear Project"), i18n("Do you really want to clear the current project?"), KGuiItem(i18n("Clear Project")), KGuiItem(i18n("Clear")), QString("clear_current_project_dontAskAgain") ) == KMessageBox::Continue ) { doc->clear(); } } } bool K3b::MainWindow::isCdDvdImageAndIfSoOpenDialog( const KUrl& url ) { K3b::Iso9660 iso( url.path() ); if( iso.open() ) { iso.close(); slotWriteImage( url ); return true; } else return false; } void K3b::MainWindow::slotCddaRip() { cddaRip( 0 ); } void K3b::MainWindow::cddaRip( K3b::Device::Device* dev ) { if( !dev || !(k3bappcore->mediaCache()->medium( dev ).content() & K3b::Medium::CONTENT_AUDIO ) ) dev = K3b::MediaSelectionDialog::selectMedium( K3b::Device::MEDIA_CD_ALL, K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE, K3b::Medium::CONTENT_AUDIO, this, i18n("Audio CD Rip") ); if( dev ) m_dirView->showDevice( dev ); } void K3b::MainWindow::videoDvdRip( K3b::Device::Device* dev ) { if( !dev || !(k3bappcore->mediaCache()->medium( dev ).content() & K3b::Medium::CONTENT_VIDEO_DVD ) ) dev = K3b::MediaSelectionDialog::selectMedium( K3b::Device::MEDIA_DVD_ALL, K3b::Device::STATE_COMPLETE, K3b::Medium::CONTENT_VIDEO_DVD, this, i18n("Video DVD Rip") ); if( dev ) m_dirView->showDevice( dev ); } void K3b::MainWindow::slotVideoDvdRip() { videoDvdRip( 0 ); } void K3b::MainWindow::videoCdRip( K3b::Device::Device* dev ) { if( !dev || !(k3bappcore->mediaCache()->medium( dev ).content() & K3b::Medium::CONTENT_VIDEO_CD ) ) dev = K3b::MediaSelectionDialog::selectMedium( K3b::Device::MEDIA_CD_ALL, K3b::Device::STATE_COMPLETE, K3b::Medium::CONTENT_VIDEO_CD, this, i18n("Video CD Rip") ); if( dev ) m_dirView->showDevice( dev ); } void K3b::MainWindow::slotVideoCdRip() { videoCdRip( 0 ); } void K3b::MainWindow::showDiskInfo( K3b::Device::Device* dev ) { m_dirView->showDiskInfo( dev ); } #include "k3b.moc" diff --git a/src/k3b.h b/src/k3b.h index c1a993270..5e618498f 100644 --- a/src/k3b.h +++ b/src/k3b.h @@ -1,304 +1,304 @@ /* * * Copyright (C) 1998-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3B_H #define K3B_H #include // include files for Qt #include #include // include files for KDE #include #include #include #include #include #include #include #include "option/k3boptiondialog.h" class KSystemTray; class KToggleAction; class KAction; class KRecentFilesAction; class KActionMenu; namespace K3b { class Doc; class View; class DirView; class ExternalBinManager; class OptionDialog; class ProjectTabWidget; class StatusBarManager; class ThemedHeader; namespace Device { class DeviceManager; class Device; } class MainWindow : public KXmlGuiWindow { Q_OBJECT public: /** construtor of MainWindow, calls all init functions to create the application. * @see initMenuBar initToolBar */ MainWindow(); ~MainWindow(); /** opens a file specified by commandline option */ Doc* openDocument( const KUrl& url = KUrl() ); Device::DeviceManager* deviceManager() const; ExternalBinManager* externalBinManager() const; KSharedConfig::Ptr config() const; // return main window with browser/cd/dvd view, used for DND DirView* mainWindow() const { return m_dirView; } /** * @returns a pointer to the currently visible view or 0 if no project was created */ View* activeView() const; /** * @returns a pointer to the doc associated with the currently visible view or 0 if no project was created */ Doc* activeDoc() const; QList projects() const; bool eject(); void showOptionDialog( OptionDialog::ConfigPage page = OptionDialog::Misc ); /** Creates the main view of the KDockMainWindow instance and initializes the MDI view area including any needed * connections. * must be called after construction */ void initView(); KSystemTray* systemTray() const { return m_systemTray; } public Q_SLOTS: K3b::Doc* slotNewAudioDoc(); K3b::Doc* slotNewDataDoc(); K3b::Doc* slotNewMixedDoc(); K3b::Doc* slotNewVcdDoc(); K3b::Doc* slotNewMovixDoc(); K3b::Doc* slotNewVideoDvdDoc(); K3b::Doc* slotContinueMultisession(); void slotClearProject(); void slotWriteImage(); void slotWriteImage( const KUrl& url ); void formatMedium( K3b::Device::Device* ); void slotFormatMedium(); void mediaCopy( K3b::Device::Device* ); void slotMediaCopy(); void cddaRip( K3b::Device::Device* ); void slotCddaRip(); void videoDvdRip( K3b::Device::Device* ); void slotVideoDvdRip(); void videoCdRip( K3b::Device::Device* ); void slotVideoCdRip(); void slotK3bSetup(); void showDiskInfo( K3b::Device::Device* ); void slotErrorMessage(const QString&); void slotWarningMessage(const QString&); void slotConfigureKeys(); void slotShowTips(); void slotCheckSystem(); void slotManualCheckSystem(); void addUrls( const KUrl::List& urls ); Q_SIGNALS: void initializationInfo( const QString& ); void configChanged( KSharedConfig::Ptr c ); protected: /** queryClose is called by KTMainWindow on each closeEvent of a window. Against the * default implementation (only returns true), this overridden function retrieves all modified documents * from the open document list and asks the user to select which files to save before exiting the application. * @see KTMainWindow#queryClose * @see KTMainWindow#closeEvent */ virtual bool queryClose(); /** queryExit is called by KTMainWindow when the last window of the application is going to be closed during the closeEvent(). * Against the default implementation that just returns true, this calls saveOptions() to save the settings of the last window's * properties. * @see KTMainWindow#queryExit * @see KTMainWindow#closeEvent */ virtual bool queryExit(); /** saves the window properties for each open window during session end to the session config file, including saving the currently * opened file by a temporary filename provided by KApplication. * @see KTMainWindow#saveProperties */ virtual void saveProperties(KConfigGroup &_cfg); /** reads the session config file and restores the application's state including the last opened files and documents by reading the * temporary files saved by saveProperties() * @see KTMainWindow#readProperties */ virtual void readProperties(const KConfigGroup &_cfg); /** * checks if doc is modified and asks the user for saving if so. * returns false if the user chose cancel. */ bool canCloseDocument( Doc* ); private Q_SLOTS: /** open a file and load it into the document*/ void slotFileOpen(); /** opens a file from the recent files menu */ void slotFileOpenRecent(const KUrl& url); /** save a document */ void slotFileSave(); /** save a document by a new filename*/ void slotFileSaveAs(); void slotFileSaveAll(); /** asks for saving if the file is modified, then closes the actual file and window*/ void slotFileClose(); void slotFileCloseAll(); void slotSettingsConfigure(); /** checks if the currently visible tab is a k3bview or not and dis- or enables some actions */ void slotCurrentDocChanged(); void slotFileQuit(); /** toggles the statusbar */ void slotViewStatusBar(); void slotViewDocumentHeader(); /** changes the statusbar contents for the standard label permanently, used to indicate current actions. * @param text the text that is displayed in the statusbar */ void slotStatusMsg(const QString &text); void slotShowMenuBar(); void slotProjectAddFiles(); void slotEditToolbars(); void slotNewToolBarConfig(); void slotDataImportSession(); void slotDataClearImportedSession(); void slotEditBootImages(); void createClient(K3b::Doc* doc); /** * Run slotCheckSystem with a timer */ void slotCheckSystemTimed(); private: - void fileSave( Doc* doc = 0 ); - void fileSaveAs( Doc* doc = 0 ); + bool fileSave( Doc* doc = 0 ); + bool fileSaveAs( Doc* doc = 0 ); void closeProject( Doc* ); /** save general Options like all bar positions and status as well as the geometry and the recent file list to the configuration * file */ void saveOptions(); /** read general Options again and initialize all variables like the recent file list */ void readOptions(); /** initializes the KActions of the application */ void initActions(); /** sets up the statusbar for the main window by initialzing a statuslabel. */ void initStatusBar(); bool isCdDvdImageAndIfSoOpenDialog( const KUrl& url ); /** The MDI-Interface is managed by this tabbed view */ ProjectTabWidget* m_documentTab; // KAction pointers to enable/disable actions KActionMenu* actionFileNewMenu; KAction* actionFileNewAudio; KAction* actionFileNewData; KAction* actionFileNewMixed; KAction* actionFileNewVcd; KAction* actionFileNewMovix; KAction* actionFileNewVideoDvd; KAction* actionFileContinueMultisession; KAction* actionFileOpen; KRecentFilesAction* actionFileOpenRecent; KAction* actionFileSave; KAction* actionFileSaveAs; KAction* actionFileSaveAll; KAction* actionFileClose; KAction* actionFileCloseAll; KAction* actionFileQuit; KAction* actionSettingsConfigure; KAction* actionSettingsSetup; KAction* actionToolsWriteImage; KAction* actionToolsCddaRip; KAction* actionToolsVideoDvdRip; KAction* actionToolsVideoCdRip; KAction* actionProjectAddFiles; KToggleAction* actionViewStatusBar; KToggleAction* actionViewDocumentHeader; // project actions QList m_dataProjectActions; QDockWidget* m_contentsDock; QDockWidget* m_dirTreeDock; // The K3b-specific widgets DirView* m_dirView; OptionDialog* m_optionDialog; StatusBarManager* m_statusBarManager; KSystemTray* m_systemTray; bool m_initialized; // the funny header ThemedHeader* m_documentHeader; class Private; Private* d; }; } #endif // K3B_H diff --git a/src/k3bappdevicemanager.cpp b/src/k3bappdevicemanager.cpp index a400d4059..34c03bd50 100644 --- a/src/k3bappdevicemanager.cpp +++ b/src/k3bappdevicemanager.cpp @@ -1,360 +1,359 @@ /* * * Copyright (C) 2005-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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 "k3bappdevicemanager.h" #include "k3baction.h" #include "k3bdevice.h" #include "k3bdevicehandler.h" #include "k3bglobals.h" #include "k3bapplication.h" #include "k3bmediacache.h" #include #include #include #include #include #include #include #include #include class K3b::AppDeviceManager::Private { public: KAction* actionDiskInfo; KAction* actionUnmount; KAction* actionMount; KAction* actionEject; KAction* actionLoad; KAction* actionSetReadSpeed; K3b::Device::Device* currentDevice; }; K3b::AppDeviceManager::AppDeviceManager( QObject* parent ) : K3b::Device::DeviceManager( parent ), d( new Private ) { // setup actions d->actionDiskInfo = K3b::createAction(this, i18n("Media &Info"), "document-properties", 0, this, SLOT(diskInfo()), actionCollection(), "device_diskinfo"); d->actionUnmount = K3b::createAction(this, i18n("&Unmount"), "media-optical", 0, this, SLOT(unmountDisk()), actionCollection(), "device_unmount"); d->actionMount = K3b::createAction(this, i18n("&Mount"), "media-optical", 0, this, SLOT(mountDisk()), actionCollection(), "device_mount"); d->actionEject = K3b::createAction(this, i18n("&Eject"), 0, 0, this, SLOT(ejectDisk()), actionCollection(), "device_eject"); d->actionLoad = K3b::createAction(this, i18n("L&oad"), 0, 0, this, SLOT(loadDisk()), actionCollection(), "device_load"); // KAction* actionUnlock = new KAction( i18n("Un&lock"), "", 0, this, SLOT(unlockDevice()), // actionCollection(), "device_unlock" ); // KAction* actionlock = new KAction( i18n("Loc&k"), "", 0, this, SLOT(lockDevice()), // actionCollection(), "device_lock" ); d->actionSetReadSpeed = K3b::createAction(this, i18n("Set Read Speed..."), 0, 0, this, SLOT(setReadSpeed()), actionCollection(), "device_set_read_speed" ); d->actionDiskInfo->setToolTip( i18n("Display generic medium information") ); d->actionUnmount->setToolTip( i18n("Unmount the medium") ); d->actionMount->setToolTip( i18n("Mount the medium") ); d->actionEject->setToolTip( i18n("Eject the medium") ); d->actionLoad->setToolTip( i18n("(Re)Load the medium") ); d->actionSetReadSpeed->setToolTip( i18n("Force the drive's read speed") ); setXMLFile( "k3bdeviceui.rc", true ); d->currentDevice = 0; slotMediumChanged( 0 ); } K3b::AppDeviceManager::~AppDeviceManager() { delete d; } void K3b::AppDeviceManager::setMediaCache( K3b::MediaCache* c ) { connect( c, SIGNAL(mediumChanged(K3b::Device::Device*)), this, SLOT(slotMediumChanged(K3b::Device::Device*)) ); } K3b::Device::Device* K3b::AppDeviceManager::currentDevice() const { return d->currentDevice; } void K3b::AppDeviceManager::clear() { // make sure we do not use a deleted device setCurrentDevice( 0 ); K3b::Device::DeviceManager::clear(); } K3b::Device::Device* K3b::AppDeviceManager::addDevice( const Solid::Device& solidDev ) { K3b::Device::Device* dev = K3b::Device::DeviceManager::addDevice( solidDev ); if( dev && currentDevice() == 0 ) { setCurrentDevice( dev ); } return dev; } void K3b::AppDeviceManager::removeDevice( const Solid::Device& solidDev ) { if( findDevice( solidDev.as()->device() ) == currentDevice() ) setCurrentDevice( 0 ); - + K3b::Device::DeviceManager::removeDevice( solidDev ); if( currentDevice() == 0 && !allDevices().isEmpty() ) setCurrentDevice( allDevices().first() ); } void K3b::AppDeviceManager::slotMediumChanged( K3b::Device::Device* dev ) { if( dev == currentDevice() ) { - + d->actionDiskInfo->setEnabled( dev != 0 ); d->actionEject->setEnabled( dev != 0 ); d->actionLoad->setEnabled( dev != 0 ); d->actionSetReadSpeed->setEnabled( dev != 0 ); - + if( dev ) { bool mediumMountable = k3bappcore->mediaCache()->medium( dev ).content() & K3b::Medium::CONTENT_DATA; d->actionMount->setEnabled( mediumMountable ); d->actionUnmount->setEnabled( mediumMountable ); - + disconnect( this, SLOT(slotMountChanged(bool,const QString&)) ); disconnect( this, SLOT(slotMountFinished(Solid::ErrorType,QVariant,const QString&)) ); disconnect( this, SLOT(slotUnmountFinished(Solid::ErrorType,QVariant,const QString&)) ); Solid::StorageAccess* storage = dev->solidStorage(); if( storage != 0 ) { connect( storage, SIGNAL(accessibilityChanged(bool,const QString&)), this, SLOT(slotMountChanged(bool,const QString&)) ); connect( storage, SIGNAL(setupDone(Solid::ErrorType,QVariant,const QString&)), this, SLOT(slotMountFinished(Solid::ErrorType,QVariant,const QString&)) ); connect( storage, SIGNAL(teardownDone(Solid::ErrorType,QVariant,const QString&)), this, SLOT(slotUnmountFinished(Solid::ErrorType,QVariant,const QString&)) ); d->actionMount->setVisible( !storage->isAccessible() ); d->actionUnmount->setVisible( storage->isAccessible() ); } else { d->actionMount->setVisible( true ); d->actionUnmount->setVisible( false ); } } else { d->actionMount->setVisible( true ); d->actionUnmount->setVisible( false ); d->actionMount->setEnabled( false ); d->actionUnmount->setEnabled( false ); } } } void K3b::AppDeviceManager::slotMountChanged( bool accessible, const QString& ) { d->actionMount->setVisible( !accessible ); d->actionUnmount->setVisible( accessible ); } void K3b::AppDeviceManager::slotMountFinished( Solid::ErrorType error, QVariant, const QString& ) { if( currentDevice() != 0 ) { Solid::StorageAccess* storage = currentDevice()->solidStorage(); if( error == Solid::NoError && storage != 0 ) { kDebug() << "Device mounted at " << storage->filePath(); emit mountFinished( storage->filePath() ); } } } void K3b::AppDeviceManager::slotUnmountFinished( Solid::ErrorType error, QVariant, const QString& ) { emit unmountFinished( error == Solid::NoError ); } void K3b::AppDeviceManager::setCurrentDevice( K3b::Device::Device* dev ) { if( dev != currentDevice() ) { d->currentDevice = dev; emit currentDeviceChanged( currentDevice() ); slotMediumChanged( currentDevice() ); } } void K3b::AppDeviceManager::diskInfo() { if( currentDevice() ) { emit detectingDiskInfo( currentDevice() ); } } void K3b::AppDeviceManager::unlockDevice() { if( currentDevice() ) K3b::Device::unblock( currentDevice() ); } void K3b::AppDeviceManager::lockDevice() { if( currentDevice() ) K3b::Device::block( currentDevice() ); } void K3b::AppDeviceManager::mountDisk() { if( currentDevice() ) { Solid::StorageAccess* storage = currentDevice()->solidStorage(); if( storage != 0 ) { if( storage->isAccessible() ) emit mountFinished( storage->filePath() ); else storage->setup(); } } } void K3b::AppDeviceManager::unmountDisk() { if ( currentDevice() ) { Solid::StorageAccess* storage = currentDevice()->solidStorage(); if( storage != 0 ) { if( storage->isAccessible() ) storage->teardown(); else emit unmountFinished( true ); } } } void K3b::AppDeviceManager::ejectDisk() { - // FIXME: make this non-blocking if ( currentDevice() ) - K3b::eject( currentDevice() ); // just ignore errors here + K3b::Device::eject( currentDevice() ); } void K3b::AppDeviceManager::loadDisk() { if( currentDevice() ) K3b::Device::reload( currentDevice() ); } void K3b::AppDeviceManager::setReadSpeed() { if( currentDevice() ) { bool ok = false; int s = KInputDialog::getInteger( i18n("CD Read Speed"), i18n("

Please enter the preferred read speed for %1. " "This speed will be used for the currently mounted " "medium." "

This is especially useful to slow down the drive when " "watching movies which are read directly from the drive " "and the spinning noise is intrusive." "

Be aware that this has no influence on K3b since it will " "change the reading speed again when copying CDs or DVDs." ,currentDevice()->vendor() + " " + currentDevice()->description()), 12, 1, currentDevice()->maxReadSpeed(), 1, 10, &ok, 0 ); if( ok ) { if( !currentDevice()->setSpeed( s*175, 0xFFFF ) ) KMessageBox::error( 0, i18n("Setting the read speed failed.") ); } } } void K3b::AppDeviceManager::diskInfo( K3b::Device::Device* dev ) { setCurrentDevice( dev ); diskInfo(); } void K3b::AppDeviceManager::unlockDevice( K3b::Device::Device* dev ) { setCurrentDevice( dev ); unlockDevice(); } void K3b::AppDeviceManager::lockDevice( K3b::Device::Device* dev ) { setCurrentDevice( dev ); lockDevice(); } void K3b::AppDeviceManager::mountDisk( K3b::Device::Device* dev ) { setCurrentDevice( dev ); mountDisk(); } void K3b::AppDeviceManager::unmountDisk( K3b::Device::Device* dev ) { setCurrentDevice( dev ); unmountDisk(); } void K3b::AppDeviceManager::ejectDisk( K3b::Device::Device* dev ) { setCurrentDevice( dev ); ejectDisk(); } void K3b::AppDeviceManager::loadDisk( K3b::Device::Device* dev ) { setCurrentDevice( dev ); loadDisk(); } void K3b::AppDeviceManager::setReadSpeed( K3b::Device::Device* dev ) { setCurrentDevice( dev ); setReadSpeed(); } #include "k3bappdevicemanager.moc" diff --git a/src/k3bdebuggingoutputfile.cpp b/src/k3bdebuggingoutputfile.cpp index c0a74d2ae..9bca4093d 100644 --- a/src/k3bdebuggingoutputfile.cpp +++ b/src/k3bdebuggingoutputfile.cpp @@ -1,72 +1,72 @@ /* * * Copyright (C) 2005-2007 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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 "k3bdebuggingoutputfile.h" #include #include #include #include #include #include #include #include #include #include K3b::DebuggingOutputFile::DebuggingOutputFile() : QFile( KStandardDirs::locateLocal( "appdata", "lastlog.log", true ) ) { } bool K3b::DebuggingOutputFile::open( OpenMode mode ) { - if( !QFile::open( mode|WriteOnly ) ) + if( !QFile::open( mode|WriteOnly|Unbuffered ) ) return false; addOutput( QLatin1String( "System" ), QLatin1String( "K3b Version: " ) + k3bcore->version() ); addOutput( QLatin1String( "System" ), QLatin1String( "KDE Version: " ) + QString(KDE::versionString()) ); addOutput( QLatin1String( "System" ), QLatin1String( "QT Version: " ) + QString(qVersion()) ); addOutput( QLatin1String( "System" ), QLatin1String( "Kernel: " ) + K3b::kernelVersion() ); // devices in the logfile Q_FOREACH( K3b::Device::Device* dev, k3bcore->deviceManager()->allDevices() ) { addOutput( "Devices", QString( "%1 (%2, %3) [%5] [%6] [%7]" ) .arg( dev->vendor() + " " + dev->description() + " " + dev->version() ) .arg( dev->blockDeviceName() ) .arg( K3b::Device::deviceTypeString( dev->type() ) ) .arg( K3b::Device::mediaTypeString( dev->supportedProfiles() ) ) .arg( K3b::Device::writingModeString( dev->writingModes() ) ) ); } return true; } void K3b::DebuggingOutputFile::addOutput( const QString& app, const QString& msg ) { if( !isOpen() ) open(); QTextStream s( this ); s << "[" << app << "] " << msg << endl; flush(); } #include "k3bdebuggingoutputfile.moc" diff --git a/src/k3binteractiondialog.cpp b/src/k3binteractiondialog.cpp index 1d0d45e08..4eeef7228 100644 --- a/src/k3binteractiondialog.cpp +++ b/src/k3binteractiondialog.cpp @@ -1,617 +1,580 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3binteractiondialog.h" #include "k3btitlelabel.h" #include "k3bstdguiitems.h" #include "k3bpushbutton.h" #include "k3bthemedheader.h" #include "k3bthememanager.h" #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include K3b::InteractionDialog::InteractionDialog( QWidget* parent, - const QString& title, - const QString& subTitle, - int buttonMask, - int defaultButton, - const QString& configGroup ) + const QString& title, + const QString& subTitle, + int buttonMask, + int defaultButton, + const QString& configGroup ) : KDialog( parent ), m_mainWidget(0), m_defaultButton(defaultButton), m_configGroup(configGroup), m_inToggleMode(false), - m_delayedInit(false), - m_eventLoop( 0 ) + m_delayedInit(false) { installEventFilter( this ); setButtons( KDialog::None ); mainGrid = new QGridLayout( KDialog::mainWidget() ); mainGrid->setSpacing( spacingHint() ); mainGrid->setMargin( 0 ); // header // --------------------------------------------------------------------------------------------------- m_dialogHeader = new K3b::ThemedHeader( KDialog::mainWidget() ); mainGrid->addWidget( m_dialogHeader, 0, 0, 1, 3 ); // settings buttons // --------------------------------------------------------------------------------------------------- if( !m_configGroup.isEmpty() ) { QHBoxLayout* layout2 = new QHBoxLayout; layout2->setSpacing( spacingHint() ); m_buttonLoadSettings = new QToolButton( KDialog::mainWidget() ); m_buttonLoadSettings->setIcon( KIcon( "document-revert" ) ); m_buttonLoadSettings->setPopupMode( QToolButton::InstantPopup ); QMenu* userDefaultsPopup = new QMenu( m_buttonLoadSettings ); userDefaultsPopup->addAction( i18n("Load default settings"), this, SLOT(slotLoadK3bDefaults()) ); userDefaultsPopup->addAction( i18n("Load saved settings"), this, SLOT(slotLoadUserDefaults()) ); userDefaultsPopup->addAction( i18n("Load last used settings"), this, SLOT(slotLoadLastSettings()) ); m_buttonLoadSettings->setMenu( userDefaultsPopup ); layout2->addWidget( m_buttonLoadSettings ); m_buttonSaveSettings = new QToolButton( KDialog::mainWidget() ); m_buttonSaveSettings->setIcon( KIcon( "document-save" ) ); layout2->addWidget( m_buttonSaveSettings ); mainGrid->addLayout( layout2, 2, 0 ); } QSpacerItem* spacer = new QSpacerItem( 10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum ); mainGrid->addItem( spacer, 2, 1 ); // action buttons // --------------------------------------------------------------------------------------------------- QDialogButtonBox *buttonBox = new QDialogButtonBox( KDialog::mainWidget() ); if( buttonMask & START_BUTTON ) { KGuiItem startItem = KStandardGuiItem::ok(); m_buttonStart = new KPushButton( startItem, buttonBox ); // refine the button text setButtonText( START_BUTTON, i18n("Start"), i18n("Start the task") ); QFont fnt( m_buttonStart->font() ); fnt.setBold(true); m_buttonStart->setFont( fnt ); buttonBox->addButton( m_buttonStart, QDialogButtonBox::AcceptRole ); } if( buttonMask & SAVE_BUTTON ) { m_buttonSave = new KPushButton( KStandardGuiItem::save(), buttonBox ); buttonBox->addButton( m_buttonSave, QDialogButtonBox::ApplyRole ); } else { m_buttonSave = 0; } if( buttonMask & CANCEL_BUTTON ) { m_buttonCancel = new KPushButton( KConfigGroup( KGlobal::config(), "General Options" ) .readEntry( "keep action dialogs open", false ) ? KStandardGuiItem::close() : KStandardGuiItem::cancel(), buttonBox ); buttonBox->addButton( m_buttonCancel, QDialogButtonBox::RejectRole ); } else { m_buttonCancel = 0; } mainGrid->addWidget( buttonBox, 2, 2 ); mainGrid->setRowStretch( 1, 1 ); setTitle( title, subTitle ); initConnections(); initToolTipsAndWhatsThis(); setDefaultButton( START_BUTTON ); } K3b::InteractionDialog::~InteractionDialog() { kDebug() << this; } void K3b::InteractionDialog::show() { KDialog::show(); if( KPushButton* b = getButton( m_defaultButton ) ) b->setFocus(); } QSize K3b::InteractionDialog::sizeHint() const { QSize s = KDialog::sizeHint(); // I want the dialogs to look good. // That means their height should never outgrow their width if( s.height() > s.width() ) s.setWidth( s.height() ); return s; } void K3b::InteractionDialog::initConnections() { if( m_buttonStart ) { connect( m_buttonStart, SIGNAL(clicked()), this, SLOT(slotStartClickedInternal()) ); } if( m_buttonSave ) { // connect( m_buttonSave, SIGNAL(clicked()), // this, SLOT(slotSaveLastSettings()) ); connect( m_buttonSave, SIGNAL(clicked()), this, SLOT(slotSaveClicked()) ); } if( m_buttonCancel ) connect( m_buttonCancel, SIGNAL(clicked()), this, SLOT(slotCancelClicked()) ); if( !m_configGroup.isEmpty() ) { connect( m_buttonSaveSettings, SIGNAL(clicked()), this, SLOT(slotSaveUserDefaults()) ); } } void K3b::InteractionDialog::initToolTipsAndWhatsThis() { if( !m_configGroup.isEmpty() ) { // ToolTips // ------------------------------------------------------------------------- m_buttonLoadSettings->setToolTip( i18n("Load default or saved settings") ); m_buttonSaveSettings->setToolTip( i18n("Save current settings to reuse them later") ); // What's This info // ------------------------------------------------------------------------- m_buttonLoadSettings->setWhatsThis( i18n("

Load a set of settings either from the default K3b settings, " "settings saved before, or the last used ones.") ); m_buttonSaveSettings->setWhatsThis( i18n("

Saves the current settings of the action dialog." "

These settings can be loaded with the Load saved settings " "button." "

The K3b defaults are not overwritten by this.") ); } } void K3b::InteractionDialog::setTitle( const QString& title, const QString& subTitle ) { m_dialogHeader->setTitle( title, subTitle ); setCaption( title ); } void K3b::InteractionDialog::setMainWidget( QWidget* w ) { w->setParent( KDialog::mainWidget() ); mainGrid->addWidget( w, 1, 0, 1, 3 ); m_mainWidget = w; } QWidget* K3b::InteractionDialog::mainWidget() { if( !m_mainWidget ) { QWidget *widget = new QWidget(this); setMainWidget( widget ); } return m_mainWidget; } void K3b::InteractionDialog::slotLoadK3bDefaults() { KSharedConfig::Ptr c = KGlobal::config(); c->setReadDefaults( true ); loadSettings( c->group( m_configGroup ) ); c->setReadDefaults( false ); } void K3b::InteractionDialog::slotLoadUserDefaults() { KConfigGroup c( KGlobal::config(), m_configGroup ); loadSettings( c ); } void K3b::InteractionDialog::slotSaveUserDefaults() { KConfigGroup c( KGlobal::config(), m_configGroup ); saveSettings( c ); } void K3b::InteractionDialog::slotLoadLastSettings() { KConfigGroup c( KGlobal::config(), "last used " + m_configGroup ); loadSettings( c ); } void K3b::InteractionDialog::saveLastSettings() { KConfigGroup c( KGlobal::config(), "last used " + m_configGroup ); saveSettings( c ); } void K3b::InteractionDialog::slotStartClickedInternal() { saveLastSettings(); KConfigGroup c( KGlobal::config(), "General Options" ); if( !c.readEntry( "action dialog startup settings", 0 ) ) { // first time saving last used settings switch( K3b::MultiChoiceDialog::choose( i18n("Action Dialog Settings"), i18n("

K3b handles three sets of settings in action dialogs: " "the defaults, the saved settings, and the last used settings. " "Please choose which of these sets should be loaded if an action " "dialog is opened again." "

Be aware that this choice can always be changed from the K3b " "configuration dialog."), QMessageBox::Question, this, 3, KGuiItem(i18n("Default Settings")), KGuiItem(i18n("Saved Settings")), KGuiItem(i18n("Last Used Settings")) ) ) { case 1: c.writeEntry( "action dialog startup settings", int(LOAD_K3B_DEFAULTS) ); break; case 2: c.writeEntry( "action dialog startup settings", int(LOAD_SAVED_SETTINGS) ); break; case 3: c.writeEntry( "action dialog startup settings", int(LOAD_LAST_SETTINGS) ); break; } } slotStartClicked(); } void K3b::InteractionDialog::slotStartClicked() { emit started(); } void K3b::InteractionDialog::slotCancelClicked() { emit canceled(); close(); } void K3b::InteractionDialog::slotSaveClicked() { emit saved(); } void K3b::InteractionDialog::setDefaultButton( int button ) { m_defaultButton = button; // reset all other default buttons if( KPushButton* b = getButton( START_BUTTON ) ) b->setDefault( true ); if( KPushButton* b = getButton( SAVE_BUTTON ) ) b->setDefault( true ); if( KPushButton* b = getButton( CANCEL_BUTTON ) ) b->setDefault( true ); // set the selected default if( KPushButton* b = getButton( button ) ) b->setDefault( true ); } bool K3b::InteractionDialog::eventFilter( QObject* o, QEvent* ev ) { if( dynamic_cast(o) == this && ev->type() == QEvent::KeyPress ) { QKeyEvent* kev = dynamic_cast(ev); switch ( kev->key() ) { case Qt::Key_Enter: case Qt::Key_Return: // if the process finished this closes the dialog if( m_defaultButton == START_BUTTON ) { if( m_buttonStart->isEnabled() ) slotStartClickedInternal(); } else if( m_defaultButton == CANCEL_BUTTON ) { if( m_buttonCancel->isEnabled() ) slotCancelClicked(); } else if( m_defaultButton == SAVE_BUTTON ) { if( m_buttonSave->isEnabled() ) slotSaveClicked(); } return true; case Qt::Key_Escape: // simulate button clicks if( m_buttonCancel ) { if( m_buttonCancel->isEnabled() ) slotCancelClicked(); } return true; } } return KDialog::eventFilter( o, ev ); } KPushButton* K3b::InteractionDialog::getButton( int button ) { switch( button ) { case START_BUTTON: return m_buttonStart; case SAVE_BUTTON: return m_buttonSave; case CANCEL_BUTTON: return m_buttonCancel; default: return 0; } } void K3b::InteractionDialog::setButtonGui( int button, const KGuiItem& item ) { if( KPushButton* b = getButton( button ) ) b->setGuiItem( item ); } void K3b::InteractionDialog::setButtonText( int button, const QString& text, const QString& tooltip, const QString& whatsthis ) { if( KPushButton* b = getButton( button ) ) { b->setText( text ); b->setToolTip( tooltip ); b->setWhatsThis( whatsthis ); } } void K3b::InteractionDialog::setButtonEnabled( int button, bool enabled ) { if( KPushButton* b = getButton( button ) ) { b->setEnabled( enabled ); // make sure the correct button is selected as default again setDefaultButton( m_defaultButton ); } } void K3b::InteractionDialog::setButtonShown( int button, bool shown ) { if( KPushButton* b = getButton( button ) ) { b->setVisible( shown ); // make sure the correct button is selected as default again setDefaultButton( m_defaultButton ); } } void K3b::InteractionDialog::setStartButtonText( const QString& text, const QString& tooltip, const QString& whatsthis ) { if( m_buttonStart ) { m_buttonStart->setText( text ); m_buttonStart->setToolTip( tooltip ); m_buttonStart->setWhatsThis( whatsthis ); } } void K3b::InteractionDialog::setCancelButtonText( const QString& text, const QString& tooltip, const QString& whatsthis ) { if( m_buttonCancel ) { m_buttonCancel->setText( text ); m_buttonCancel->setToolTip( tooltip ); m_buttonCancel->setWhatsThis( whatsthis ); } } void K3b::InteractionDialog::setSaveButtonText( const QString& text, const QString& tooltip, const QString& whatsthis ) { if( m_buttonSave ) { m_buttonSave->setText( text ); m_buttonSave->setToolTip( tooltip ); m_buttonSave->setWhatsThis( whatsthis ); } } void K3b::InteractionDialog::saveSettings( KConfigGroup ) { } void K3b::InteractionDialog::loadSettings( const KConfigGroup& ) { } void K3b::InteractionDialog::loadStartupSettings() { KConfigGroup c( KGlobal::config(), "General Options" ); // earlier K3b versions loaded the saved settings // so that is what we do as a default int i = c.readEntry( "action dialog startup settings", int(LOAD_SAVED_SETTINGS) ); switch( i ) { case LOAD_K3B_DEFAULTS: slotLoadK3bDefaults(); break; case LOAD_SAVED_SETTINGS: slotLoadUserDefaults(); break; case LOAD_LAST_SETTINGS: slotLoadLastSettings(); break; } } int K3b::InteractionDialog::exec() { kDebug() << this; - // the following code is mainly taken from QDialog::exec - if( m_eventLoop ) { - kError() << "(K3b::InteractionDialog::exec) Recursive call detected." << endl; - return -1; - } - - bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose); - setAttribute(Qt::WA_DeleteOnClose, false); - - bool wasShowModal = testAttribute(Qt::WA_ShowModal); - setAttribute(Qt::WA_ShowModal, true); - setResult(0); - loadStartupSettings(); - show(); - if( m_delayedInit ) QMetaObject::invokeMethod( this, "slotInternalInit", Qt::QueuedConnection ); else slotInternalInit(); - QEventLoop eventLoop; - m_eventLoop = &eventLoop; - QPointer guard = this; - (void) eventLoop.exec(); - if (guard.isNull()) - return QDialog::Rejected; - m_eventLoop = 0; - - setAttribute(Qt::WA_ShowModal, wasShowModal); - - int res = result(); - if (deleteOnClose) - delete this; - return res; + return KDialog::exec(); } void K3b::InteractionDialog::hideTemporarily() { hide(); } void K3b::InteractionDialog::close() { - if( m_eventLoop ) { - m_eventLoop->exit(); - } KDialog::close(); } void K3b::InteractionDialog::done( int r ) { - if( m_eventLoop ) { - m_eventLoop->exit(); - } KDialog::done( r ); } void K3b::InteractionDialog::hideEvent( QHideEvent* e ) { kDebug() << this; KDialog::hideEvent( e ); } void K3b::InteractionDialog::slotToggleAll() { if( !m_inToggleMode ) { m_inToggleMode = true; toggleAll(); m_inToggleMode = false; } } void K3b::InteractionDialog::slotInternalInit() { init(); slotToggleAll(); } void K3b::InteractionDialog::toggleAll() { } #include "k3binteractiondialog.moc" diff --git a/src/k3binteractiondialog.h b/src/k3binteractiondialog.h index 3623b5339..a4bbdb975 100644 --- a/src/k3binteractiondialog.h +++ b/src/k3binteractiondialog.h @@ -1,257 +1,254 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * 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. */ #ifndef _K3B_INTERACTION_DIALOG_H_ #define _K3B_INTERACTION_DIALOG_H_ #include #include #include #include class QGridLayout; class QLabel; class KPushButton; class KGuiItem; class QToolButton; -class QEventLoop; namespace K3b { class ThemedHeader; /** * This is the base dialog for all the dialogs in K3b that start * some job. Use setMainWidget to set the contents or let mainWidget() * create an empty plain page. * The default implementations of the slots just emit the * corresponding signals. */ class InteractionDialog : public KDialog { Q_OBJECT public: /** * The constructor. * loadSettings will be called automatically when the dialog is showing. * * @param title the text to be displayed in the K3b header (not the widget frame) * @param subTitle additional text that will be displayed after the title in smaller size * @param buttonMask combination of Buttons * @param defaultButton may also be null to deactivate the feature * @param configgroup The config group used for the loadSettings and saveSettings methods */ InteractionDialog( QWidget* parent = 0, const QString& title = QString(), const QString& subTitle = QString(), int buttonMask = START_BUTTON|CANCEL_BUTTON, int defaultButton = START_BUTTON, const QString& configgroup = QString() ); virtual ~InteractionDialog(); void setMainWidget( QWidget* w ); void setTitle( const QString& title, const QString& subTitle = QString() ); void setDefaultButton( int b ); /** * In contract to "normal" dialogs InteractionDialog will not return from exec * until close() has been called. This allows to hide the dialog while a progress * dialog is shown. */ int exec(); /** * reimplemented to allow initialization after the dialog has been opened. */ void show(); /** * If no mainWidget has been set a plain page will be created. */ QWidget* mainWidget(); enum Buttons { START_BUTTON = 1, SAVE_BUTTON = 2, CANCEL_BUTTON = 4 }; QSize sizeHint() const; QString configGroup() const { return m_configGroup; } enum StartUpSettings { LOAD_K3B_DEFAULTS = 1, LOAD_SAVED_SETTINGS = 2, LOAD_LAST_SETTINGS = 3 }; Q_SIGNALS: void started(); void canceled(); void saved(); public Q_SLOTS: /** * \deprecated use setButtonText */ void setStartButtonText( const QString& text, const QString& tooltip = QString(), const QString& whatsthis = QString() ); /** * \deprecated use setButtonText */ void setCancelButtonText( const QString& text, const QString& tooltip = QString(), const QString& whatsthis = QString() ); /** * \deprecated use setButtonText */ void setSaveButtonText( const QString& text, const QString& tooltip = QString(), const QString& whatsthis = QString() ); void setButtonGui( int button, const KGuiItem& ); void setButtonText( int button, const QString& text, const QString& tooltip = QString(), const QString& whatsthis = QString() ); void setButtonEnabled( int button, bool enabled ); void setButtonShown( int button, bool enabled ); /** * If set true the init() method will be called via a QTimer to ensure event * handling be done before (default: false). */ void setDelayedInitialization( bool b ) { m_delayedInit = b; } /** * Hide the dialog but do not return from the exec call. */ void hideTemporarily(); /** * Close the dialog and return from any exec call. */ void close(); /** * Close the dialog and return from any exec call. */ void done( int r ); protected Q_SLOTS: // FIXME: replace these with protected methods which are called from private slots. virtual void slotStartClicked(); /** * The default implementation emits the canceled() signal * and calls close() */ virtual void slotCancelClicked(); virtual void slotSaveClicked(); /** * This slot will call the toggleAll() method protecting from infinite loops * caused by one element influencing another element which in turn influences * the first. * * Connect this slot to GUI elements (like Checkboxes) that change * the state of the whole dialog. */ void slotToggleAll(); protected: /** * Reimplement this method in case you are using slotToggleAll() */ virtual void toggleAll(); /** * Reimplement this to support the save/load user default buttons. * @p config is already set to the correct group. * * The save/load buttons are only activated if the config group is * set in the constructor. */ virtual void saveSettings( KConfigGroup config ); /** * Reimplement this to support the save/load user default buttons. * @p config is already set to the correct group. * * The save/load buttons are only activated if the config group is * set in the constructor. * * This method will also be called to load defaults. In that case * \m config will ignore local settings. */ virtual void loadSettings( const KConfigGroup& config ); /** * This is called after the dialog has been shown. * Use this for initialization that should happen * when the user already sees the dialog. */ virtual void init() {} /** * reimplemented from QDialog */ virtual bool eventFilter( QObject*, QEvent* ); void hideEvent( QHideEvent* ); private Q_SLOTS: void slotLoadK3bDefaults(); void slotLoadUserDefaults(); void slotSaveUserDefaults(); void slotLoadLastSettings(); void slotStartClickedInternal(); void slotInternalInit(); private: void initConnections(); void initToolTipsAndWhatsThis(); void saveLastSettings(); void loadStartupSettings(); KPushButton* getButton( int ); ThemedHeader* m_dialogHeader; KPushButton* m_buttonStart; KPushButton* m_buttonSave; KPushButton* m_buttonCancel; QWidget* m_mainWidget; QToolButton* m_buttonLoadSettings; QToolButton* m_buttonSaveSettings; QGridLayout* mainGrid; int m_defaultButton; QString m_configGroup; bool m_inToggleMode; bool m_delayedInit; - - QEventLoop* m_eventLoop; }; } #endif diff --git a/src/k3bjobprogressosd.cpp b/src/k3bjobprogressosd.cpp index 42b6d7150..d3cd0ed9c 100644 --- a/src/k3bjobprogressosd.cpp +++ b/src/k3bjobprogressosd.cpp @@ -1,288 +1,280 @@ /* * * Copyright (C) 2005-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bjobprogressosd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include -//Added by qt3to4: #include #include #include -#include K3b::JobProgressOSD::JobProgressOSD( QWidget* parent ) : QWidget( parent, Qt::WType_TopLevel | Qt::WNoAutoErase | Qt::WStyle_Customize | Qt::X11BypassWindowManagerHint | Qt::WStyle_StaysOnTop ), m_progress(0), m_dragging(false), m_screen(0), m_position(s_outerMargin, s_outerMargin) { setFocusPolicy( Qt::NoFocus ); // dummy size resize( 20, 20 ); // make sure we are always visible KWindowSystem::setOnAllDesktops( winId(), true ); connect( k3bappcore->themeManager(), SIGNAL(themeChanged()), - this, SLOT(repaint()) ); + this, SLOT(update()) ); connect( KGlobalSettings::self(), SIGNAL(appearanceChanged()), - this, SLOT(repaint()) ); + this, SLOT(update()) ); } K3b::JobProgressOSD::~JobProgressOSD() { } void K3b::JobProgressOSD::show() { // start with 0 progress setProgress(0); // reposition the osd reposition(); QWidget::show(); } void K3b::JobProgressOSD::setText( const QString& text ) { if( m_text != text ) { m_text = text; reposition(); } } void K3b::JobProgressOSD::setProgress( int p ) { if( m_progress != p ) { m_progress = p; update(); } } void K3b::JobProgressOSD::setPosition( const QPoint& p ) { m_position = p; reposition(); } void K3b::JobProgressOSD::setScreen( int screen ) { const int n = QApplication::desktop()->numScreens(); m_screen = (screen >= n) ? n-1 : screen; reposition(); } void K3b::JobProgressOSD::reposition() { QPixmap icon = KIconLoader::global()->loadIcon( "k3b", KIconLoader::NoGroup, 32 ); int margin = 10; int textWidth = fontMetrics().width( m_text ); // do not change the size every time the text changes, just in case we are too small QSize newSize( qMax( qMax( 2*margin + icon.width() + margin + textWidth, 100 ), width() ), qMax( 2*margin + icon.height(), 2*margin + fontMetrics().height()*2 ) ); QPoint newPos = m_position; const QRect screen = QApplication::desktop()->screenGeometry( m_screen ); // now to properly resize if put into one of the corners we interpret the position // depending on the quadrant int midH = screen.width()/2; int midV = screen.height()/2; if( newPos.x() > midH ) newPos.rx() -= newSize.width(); if( newPos.y() > midV ) newPos.ry() -= newSize.height(); newPos = fixupPosition( newPos ); // correct for screen position newPos += screen.topLeft(); - // ensure we are painted before we move - if( isVisible() ) - paintEvent( 0 ); - - // fancy X11 move+resize, reduces visual artifacts -// XMoveResizeWindow( QX11Info::display(), winId(), newPos.x(), newPos.y(), newSize.width(), newSize.height() ); setGeometry( QRect( newPos, newSize ) ); update(); } void K3b::JobProgressOSD::paintEvent( QPaintEvent* ) { // ---------------------------------------- // | Copying CD | // | K3B ========== 40% | // | | // ---------------------------------------- // calculate needed size if( K3b::Theme* theme = k3bappcore->themeManager()->currentTheme() ) { QPixmap icon = KIconLoader::global()->loadIcon( "k3b", KIconLoader::NoGroup, 32 ); int margin = 10; int textWidth = fontMetrics().width( m_text ); // do not change the size every time the text changes, just in case we are too small QSize newSize( qMax( qMax( 2*margin + icon.width() + margin + textWidth, 100 ), width() ), qMax( 2*margin + icon.height(), 2*margin + fontMetrics().height()*2 ) ); QPainter p( this ); p.setPen( theme->foregroundColor() ); // draw the background and the frame QRect thisRect( 0, 0, newSize.width(), newSize.height() ); p.fillRect( thisRect, theme->backgroundColor() ); p.drawRect( thisRect ); // draw the k3b icon p.drawPixmap( margin, (newSize.height()-icon.height())/2, icon ); // draw the text QSize textSize = fontMetrics().size( 0, m_text ); int textX = 2*margin + icon.width(); int textY = margin + fontMetrics().ascent(); p.drawText( textX, textY, m_text ); // draw the progress textY += fontMetrics().descent() + 4; QRect progressRect( textX, textY, newSize.width()-textX-margin, newSize.height()-textY-margin ); p.drawRect( progressRect ); progressRect.setWidth( m_progress > 0 ? m_progress*progressRect.width()/100 : 0 ); p.fillRect( progressRect, theme->foregroundColor() ); } } void K3b::JobProgressOSD::mousePressEvent( QMouseEvent* e ) { m_dragOffset = e->pos(); if( e->button() == Qt::LeftButton && !m_dragging ) { grabMouse( Qt::SizeAllCursor ); m_dragging = true; } else if( e->button() == Qt::RightButton ) { KMenu m; QAction *hideOSD = m.addAction(i18n("Hide OSD")); QAction *a = m.exec( e->globalPos() ); if( a == hideOSD) hide(); } } void K3b::JobProgressOSD::mouseReleaseEvent( QMouseEvent* ) { if( m_dragging ) { m_dragging = false; releaseMouse(); } } void K3b::JobProgressOSD::mouseMoveEvent( QMouseEvent* e ) { if( m_dragging && this == mouseGrabber() ) { // check if the osd has been dragged out of the current screen int currentScreen = QApplication::desktop()->screenNumber( e->globalPos() ); if( currentScreen != -1 ) m_screen = currentScreen; const QRect screen = QApplication::desktop()->screenGeometry( m_screen ); // make sure the position is valid m_position = fixupPosition( e->globalPos() - m_dragOffset - screen.topLeft() ); // move us to the new position move( m_position ); // fix the position int midH = screen.width()/2; int midV = screen.height()/2; if( m_position.x() + width() > midH ) m_position.rx() += width(); if( m_position.y() + height() > midV ) m_position.ry() += height(); } } QPoint K3b::JobProgressOSD::fixupPosition( const QPoint& pp ) { QPoint p(pp); const QRect& screen = QApplication::desktop()->screenGeometry( m_screen ); int maxY = screen.height() - height() - s_outerMargin; int maxX = screen.width() - width() - s_outerMargin; if( p.y() < s_outerMargin ) p.ry() = s_outerMargin; else if( p.y() > maxY ) p.ry() = maxY; if( p.x() < s_outerMargin ) p.rx() = s_outerMargin; else if( p.x() > maxX ) p.rx() = screen.width() - s_outerMargin - width(); p += screen.topLeft(); return p; } void K3b::JobProgressOSD::readSettings( const KConfigGroup& c ) { setPosition( c.readEntry( "Position", QPoint() ) ); setScreen( c.readEntry( "Screen", 0 ) ); } void K3b::JobProgressOSD::saveSettings( KConfigGroup c ) { c.writeEntry( "Position", m_position ); c.writeEntry( "Screen", m_screen ); } #include "k3bjobprogressosd.moc" diff --git a/src/k3bmediaselectioncombobox.cpp b/src/k3bmediaselectioncombobox.cpp index 2bd38eb98..3e07ff741 100644 --- a/src/k3bmediaselectioncombobox.cpp +++ b/src/k3bmediaselectioncombobox.cpp @@ -1,486 +1,486 @@ /* * - * Copyright (C) 2005-2008 Sebastian Trueg + * Copyright (C) 2005-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bmediaselectioncombobox.h" #include "k3bapplication.h" #include "k3bmediacache.h" #include #include #include #include #include #include #include #include #include #include #include #include class K3b::MediaSelectionComboBox::Private { public: Private() : ignoreDevice( 0 ) { } QMap deviceIndexMap; QVector devices; K3b::Device::Device* ignoreDevice; // medium strings for every entry QMap mediaStringMap; - int wantedMediumType; - int wantedMediumState; - int wantedMediumContent; + Device::MediaTypes wantedMediumType; + Device::MediaStates wantedMediumState; + Medium::MediumContents wantedMediumContent; K3b::Msf wantedMediumSize; }; K3b::MediaSelectionComboBox::MediaSelectionComboBox( QWidget* parent ) : KComboBox( false, parent ) { d = new Private(); // set defaults d->wantedMediumType = K3b::Device::MEDIA_WRITABLE_CD; d->wantedMediumState = K3b::Device::STATE_EMPTY; d->wantedMediumContent = K3b::Medium::CONTENT_ALL; connect( this, SIGNAL(activated(int)), this, SLOT(slotActivated(int)) ); connect( k3bcore->deviceManager(), SIGNAL(changed(K3b::Device::DeviceManager*)), this, SLOT(slotDeviceManagerChanged(K3b::Device::DeviceManager*)) ); connect( k3bappcore->mediaCache(), SIGNAL(mediumChanged(K3b::Device::Device*)), this, SLOT(slotMediumChanged(K3b::Device::Device*)) ); connect( this, SIGNAL(selectionChanged(K3b::Device::Device*)), this, SLOT(slotUpdateToolTip(K3b::Device::Device*)) ); updateMedia(); } K3b::MediaSelectionComboBox::~MediaSelectionComboBox() { delete d; } void K3b::MediaSelectionComboBox::setIgnoreDevice( K3b::Device::Device* dev ) { d->ignoreDevice = dev; updateMedia(); } K3b::Device::Device* K3b::MediaSelectionComboBox::selectedDevice() const { if( d->devices.count() > currentIndex() && currentIndex() >= 0 ) return d->devices[currentIndex()]; else return 0; } QList K3b::MediaSelectionComboBox::allDevices() const { QList l; for( int i = 0; i < d->devices.count(); ++i ) l.append( d->devices[i] ); return l; } void K3b::MediaSelectionComboBox::setSelectedDevice( K3b::Device::Device* dev ) { if( dev && d->deviceIndexMap.contains( dev ) ) { setCurrentIndex( d->deviceIndexMap[dev] ); emit selectionChanged( dev ); } } -void K3b::MediaSelectionComboBox::setWantedMediumType( int type ) +void K3b::MediaSelectionComboBox::setWantedMediumType( K3b::Device::MediaTypes type ) { if( type != 0 && type != d->wantedMediumType) { d->wantedMediumType = type; updateMedia(); } } -void K3b::MediaSelectionComboBox::setWantedMediumState( int state ) +void K3b::MediaSelectionComboBox::setWantedMediumState( K3b::Device::MediaStates state ) { if( state != 0 && state != d->wantedMediumState ) { d->wantedMediumState = state; updateMedia(); } } -void K3b::MediaSelectionComboBox::setWantedMediumContent( int content ) +void K3b::MediaSelectionComboBox::setWantedMediumContent( K3b::Medium::MediumContents content ) { if( content != d->wantedMediumContent ) { d->wantedMediumContent = content; updateMedia(); } } void K3b::MediaSelectionComboBox::setWantedMediumSize( const K3b::Msf& minSize ) { if ( d->wantedMediumSize != minSize ) { d->wantedMediumSize = minSize; updateMedia(); } } -int K3b::MediaSelectionComboBox::wantedMediumType() const +K3b::Device::MediaTypes K3b::MediaSelectionComboBox::wantedMediumType() const { return d->wantedMediumType; } -int K3b::MediaSelectionComboBox::wantedMediumState() const +K3b::Device::MediaStates K3b::MediaSelectionComboBox::wantedMediumState() const { return d->wantedMediumState; } -int K3b::MediaSelectionComboBox::wantedMediumContent() const +K3b::Medium::MediumContents K3b::MediaSelectionComboBox::wantedMediumContent() const { return d->wantedMediumContent; } K3b::Msf K3b::MediaSelectionComboBox::wantedMediumSize() const { return d->wantedMediumSize; } void K3b::MediaSelectionComboBox::slotActivated( int i ) { if( d->devices.count() > 0 ) emit selectionChanged( d->devices[i] ); } void K3b::MediaSelectionComboBox::slotMediumChanged( K3b::Device::Device* dev ) { updateMedium( dev ); } void K3b::MediaSelectionComboBox::clear() { d->deviceIndexMap.clear(); d->mediaStringMap.clear(); d->devices.clear(); KComboBox::clear(); } void K3b::MediaSelectionComboBox::showNoMediumMessage() { // make it italic QFont f( font() ); f.setItalic( true ); setFont( f ); addItem( noMediumMessage() ); setItemData( 0, f, Qt::FontRole ); } void K3b::MediaSelectionComboBox::updateMedia() { // remember set of devices QVector oldDevices = d->devices; // remember last selected medium K3b::Device::Device* selected = selectedDevice(); clear(); // // We need to only check a selection of the available devices based on the // wanted media type. // // no ROM media -> we most likely want only CD/DVD writers bool rwOnly = !( wantedMediumType() & (K3b::Device::MEDIA_CD_ROM|K3b::Device::MEDIA_DVD_ROM) ); bool dvdOnly = !( wantedMediumType() & (K3b::Device::MEDIA_CD_ROM|K3b::Device::MEDIA_WRITABLE_CD) ); QList devices(k3bcore->deviceManager()->allDevices()); if( dvdOnly ) { if( rwOnly ) devices = k3bcore->deviceManager()->dvdWriter(); else devices = k3bcore->deviceManager()->dvdReader(); } else if( rwOnly ) devices = k3bcore->deviceManager()->cdWriter(); else devices = k3bcore->deviceManager()->cdReader(); for( QList::const_iterator it = devices.constBegin(); it != devices.constEnd(); ++it ) { if ( d->ignoreDevice == *it ) { continue; } K3b::Medium medium = k3bappcore->mediaCache()->medium( *it ); if( showMedium( medium ) ) addMedium( *it ); } // // Now in case no usable medium was found show the user a little message // if( d->devices.isEmpty() ) { showNoMediumMessage(); if( selected != 0 ) { // inform that we have no medium at all emit selectionChanged( 0 ); } } else if( selected && d->deviceIndexMap.contains( selected ) ) { setCurrentIndex( d->deviceIndexMap[selected] ); } else { emit selectionChanged( selectedDevice() ); } // did the selection of devices change if( !( d->devices == oldDevices ) ) { emit newMedia(); for( int i = 0; i < d->devices.count(); ++i ) { int j = 0; for( j = 0; j < oldDevices.count(); ++j ) { if( oldDevices[j] == d->devices[i] ) break; } if( j == oldDevices.count() ) { // prefere a newly inserted medium over the previously selected setSelectedDevice( d->devices[i] ); emit newMedium( d->devices[i] ); } } } } void K3b::MediaSelectionComboBox::updateMedium( K3b::Device::Device* ) { // for now we simply rebuild the whole list updateMedia(); } void K3b::MediaSelectionComboBox::addMedium( K3b::Device::Device* dev ) { // // In case we only want an empty medium (this might happen in case the // the medium is rewritable) we do not care about the contents but tell // the user that the medium is rewritable. // Otherwise we show the contents type since this might also be used // for source selection. // QString s = mediumString( k3bappcore->mediaCache()->medium( dev ) ); // // Now let's see if this string is already contained in the list // and if so add the device name to both // if( d->mediaStringMap.contains( s ) ) { // // insert the modified string // addItem( s + QString(" (%1 - %2)").arg(dev->vendor()).arg(dev->description()) ); // // change the already existing string if we did not already do so // (which happens if more than 2 entries have the same medium string) // int prevIndex = d->mediaStringMap[s]; if( prevIndex >= 0 ) setItemText( prevIndex,itemText(prevIndex) + QString(" (%1 - %2)").arg(d->devices[prevIndex]->vendor()).arg(d->devices[prevIndex]->description() )); // // mark the string as already changed // d->mediaStringMap[s] = -1; } else { // // insert the plain medium string // addItem( s ); d->mediaStringMap[s] = count()-1; } // // update the helper structures // d->deviceIndexMap[dev] = count()-1; d->devices.append( dev ); setItemData( count()-1, mediumToolTip( k3bappcore->mediaCache()->medium( dev ) ), Qt::ToolTipRole ); } void K3b::MediaSelectionComboBox::slotDeviceManagerChanged( K3b::Device::DeviceManager* ) { updateMedia(); } bool K3b::MediaSelectionComboBox::showMedium( const K3b::Medium& m ) const { // // also use if wantedMediumState empty and medium rewritable // because we can always format/erase/overwrite it // // DVD+RW and DVD-RW restr. ovwr. are never reported as appendable // return( m.diskInfo().mediaType() & d->wantedMediumType && m.content() & d->wantedMediumContent && ( m.diskInfo().diskState() & d->wantedMediumState || ( d->wantedMediumState & K3b::Device::STATE_EMPTY && m.diskInfo().rewritable() ) || ( d->wantedMediumState & K3b::Device::STATE_INCOMPLETE && !m.diskInfo().empty() && m.diskInfo().mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) ) ) && ( d->wantedMediumSize == 0 || d->wantedMediumSize <= m.diskInfo().capacity() ) ); } QString K3b::MediaSelectionComboBox::mediumString( const K3b::Medium& medium ) const { return medium.shortString( d->wantedMediumState != K3b::Device::STATE_EMPTY ); } QString K3b::MediaSelectionComboBox::mediumToolTip( const K3b::Medium& m ) const { return m.longString(); } QString K3b::MediaSelectionComboBox::noMediumMessage() const { KLocalizedString stateString; if( d->wantedMediumContent == K3b::Medium::CONTENT_ALL ) { if( d->wantedMediumState == K3b::Device::STATE_EMPTY ) stateString = ki18n("an empty %1 medium"); else if( d->wantedMediumState == K3b::Device::STATE_INCOMPLETE ) stateString = ki18n("an appendable %1 medium"); else if( d->wantedMediumState == K3b::Device::STATE_COMPLETE ) stateString = ki18n("a complete %1 medium"); else if( d->wantedMediumState & (K3b::Device::STATE_EMPTY|K3b::Device::STATE_INCOMPLETE) && !(d->wantedMediumState & K3b::Device::STATE_COMPLETE) ) stateString = ki18n("an empty or appendable %1 medium"); else if( d->wantedMediumState & (K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE) ) stateString = ki18n("a complete or appendable %1 medium"); else stateString = ki18n("a %1 medium"); } else { // // we only handle the combinations that make sense here // and if content is requested in does not make sense to // also request a specific type of medium (like DVD+RW or DVD-R) // if( d->wantedMediumContent & K3b::Medium::CONTENT_VIDEO_CD ) stateString = ki18n("a Video CD medium"); else if ( d->wantedMediumContent & K3b::Medium::CONTENT_VIDEO_DVD ) stateString = ki18n("a Video DVD medium"); else if( d->wantedMediumContent & K3b::Medium::CONTENT_AUDIO && d->wantedMediumContent & K3b::Medium::CONTENT_DATA ) stateString = ki18n("a Mixed Mode CD medium"); else if( d->wantedMediumContent & K3b::Medium::CONTENT_AUDIO ) stateString = ki18n("an Audio CD medium"); else if( d->wantedMediumContent & K3b::Medium::CONTENT_DATA ) { if ( d->wantedMediumType == K3b::Device::MEDIA_ALL ) stateString = ki18n("a Data medium"); else if ( d->wantedMediumType == (K3b::Device::MEDIA_CD_ALL|K3b::Device::MEDIA_DVD_ALL) ) stateString = ki18n("a Data CD or DVD medium"); else if ( d->wantedMediumType == K3b::Device::MEDIA_CD_ALL ) stateString = ki18n("a Data CD medium"); else if( d->wantedMediumType == K3b::Device::MEDIA_DVD_ALL ) stateString = ki18n("a Data DVD medium"); else if ( d->wantedMediumType == K3b::Device::MEDIA_BD_ALL ) stateString = ki18n("a Data Blu-ray medium"); } else { stateString = ki18n("an empty medium"); } } // this is basically the same as in K3b::EmptyDiskWaiter // FIXME: include things like only rewritable dvd or cd since we will probably need that QString mediumString; if ( d->wantedMediumType == K3b::Device::MEDIA_WRITABLE ) mediumString = i18n( "writable" ); else if( d->wantedMediumType == (K3b::Device::MEDIA_CD_ALL|K3b::Device::MEDIA_DVD_ALL) ) mediumString = i18n("CD or DVD"); else if( d->wantedMediumType == K3b::Device::MEDIA_CD_ALL ) mediumString = i18n("CD"); else if( d->wantedMediumType == K3b::Device::MEDIA_DVD_ALL ) mediumString = i18n("DVD"); else if ( d->wantedMediumType == K3b::Device::MEDIA_BD_ALL ) mediumString = i18n( "Blu-ray" ); else if( d->wantedMediumType == ( K3b::Device::MEDIA_WRITABLE_DVD|K3b::Device::MEDIA_WRITABLE_CD) ) mediumString = i18n("CD-R(W) or DVD%1R(W)",QString("±")); else if( d->wantedMediumType == ( K3b::Device::MEDIA_WRITABLE_DVD|K3b::Device::MEDIA_WRITABLE_BD) ) mediumString = i18n("DVD%1R(W) or BD-R(E)",QString("±")); else if( d->wantedMediumType == K3b::Device::MEDIA_WRITABLE_DVD_SL ) mediumString = i18n("DVD%1R(W)",QString("±")); else if( d->wantedMediumType == K3b::Device::MEDIA_WRITABLE_DVD_DL ) mediumString = i18n("Double Layer DVD%1R",QString("±")); else if ( d->wantedMediumType == K3b::Device::MEDIA_WRITABLE_BD ) mediumString = i18n( "Blu-ray BD-R(E)" ); else if( d->wantedMediumType == K3b::Device::MEDIA_WRITABLE_CD ) mediumString = i18n("CD-R(W)"); else if( d->wantedMediumType == K3b::Device::MEDIA_DVD_ROM ) mediumString = i18n("DVD-ROM"); else if( d->wantedMediumType == K3b::Device::MEDIA_CD_ROM ) mediumString = i18n("CD-ROM"); if ( stateString.toString().contains( "%1" ) ) return ki18n("Please insert %1...").subs(stateString.subs( mediumString ).toString() ).toString(); else return ki18n("Please insert %1...").subs(stateString.toString() ).toString(); } void K3b::MediaSelectionComboBox::slotUpdateToolTip( K3b::Device::Device* dev ) { // update the tooltip for the combobox (the tooltip for the dropdown box is created in addMedium) setToolTip( dev ? mediumToolTip( k3bappcore->mediaCache()->medium( dev ) ) : QString() ); } #include "k3bmediaselectioncombobox.moc" diff --git a/src/k3bmediaselectioncombobox.h b/src/k3bmediaselectioncombobox.h index aae952f3c..f55cf9eb0 100644 --- a/src/k3bmediaselectioncombobox.h +++ b/src/k3bmediaselectioncombobox.h @@ -1,140 +1,140 @@ /* * - * Copyright (C) 2005-2008 Sebastian Trueg + * Copyright (C) 2005-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2008 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_MEDIA_SELECTION_COMBOBOX_H_ #define _K3B_MEDIA_SELECTION_COMBOBOX_H_ #include #include "k3bmedium.h" #include namespace K3b { namespace Device { class Device; class DeviceManager; } class Msf; /** * Combo box which allows to select a media (in comparison * to the DeviceComboBox which allows to select a device. * * This class uses the MediaCache to update it's status. */ class MediaSelectionComboBox : public KComboBox { Q_OBJECT public: MediaSelectionComboBox( QWidget* parent ); virtual ~MediaSelectionComboBox(); /** * Although the widget allows selection of media this * results in a device being selected. */ Device::Device* selectedDevice() const; QList allDevices() const; - int wantedMediumType() const; - int wantedMediumState() const; - int wantedMediumContent() const; + Device::MediaTypes wantedMediumType() const; + Device::MediaStates wantedMediumState() const; + Medium::MediumContents wantedMediumContent() const; K3b::Msf wantedMediumSize() const; Q_SIGNALS: /** * Be aware that his signal will also be emitted in case * no medium is available with a null pointer. */ void selectionChanged( K3b::Device::Device* ); /** * This signal is emitted if the selection of media changed. * This includes a change due to changing the wanted medium state. */ void newMedia(); void newMedium( K3b::Device::Device* dev ); public Q_SLOTS: /** * Only works in case the device actually contains a usable medium. * Otherwise the currently selected medium stays selected. */ void setSelectedDevice( K3b::Device::Device* ); /** * Set the wanted medium type. Defaults to writable CD. * * \param type a bitwise combination of the Device::MediaType enum */ - void setWantedMediumType( int type ); + void setWantedMediumType( K3b::Device::MediaTypes type ); /** * Set the wanted medium state. Defaults to empty media. * * \param state a bitwise combination of the Device::State enum */ - void setWantedMediumState( int state ); + void setWantedMediumState( K3b::Device::MediaStates state ); /** * Set the wanted medium content type. The default is Medium::CONTENT_ALL (i.e. ignore media * content) * Be aware that 0 maps to Medium::CONTENT_NONE, i.e. empty media. * * \param content A bitwise or of Medium::MediumContent */ - void setWantedMediumContent( int content ); + void setWantedMediumContent( K3b::Medium::MediumContents content ); /** * Set the wanted medium size. Defaults to 0 which means * that the size should be irgnored. */ void setWantedMediumSize( const K3b::Msf& minSize ); /** * Set the device to ignore. This device will not be checked for * wanted media. This is many useful for media copy. * * \param dev The device to ignore or 0 to not ignore any device. */ void setIgnoreDevice( K3b::Device::Device* dev ); private Q_SLOTS: void slotMediumChanged( K3b::Device::Device* ); void slotDeviceManagerChanged( K3b::Device::DeviceManager* ); void slotActivated( int i ); void slotUpdateToolTip( K3b::Device::Device* ); protected: void updateMedia(); virtual bool showMedium( const Medium& ) const; virtual QString mediumString( const Medium& ) const; virtual QString mediumToolTip( const Medium& ) const; virtual QString noMediumMessage() const; private: void updateMedium( Device::Device* ); void addMedium( Device::Device* ); void showNoMediumMessage(); void clear(); class Private; Private* d; }; } #endif diff --git a/src/k3bmediaselectiondialog.cpp b/src/k3bmediaselectiondialog.cpp index a28a82e73..8762199e2 100644 --- a/src/k3bmediaselectiondialog.cpp +++ b/src/k3bmediaselectiondialog.cpp @@ -1,128 +1,119 @@ -/* +/* * - * Copyright (C) 2005 Sebastian Trueg + * Copyright (C) 2005-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2007 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bmediaselectiondialog.h" #include "k3bmediaselectioncombobox.h" #include "k3bmediacache.h" #include "k3bapplication.h" #include -#include #include -//Added by qt3to4: #include -K3b::MediaSelectionDialog::MediaSelectionDialog( QWidget* parent, - const QString& title, - const QString& text, - bool modal ) - : KDialog( parent) +K3b::MediaSelectionDialog::MediaSelectionDialog( QWidget* parent, + const QString& title, + const QString& text, + bool modal ) + : KDialog( parent) { - QWidget *widget = new QWidget(); - setMainWidget(widget); - setCaption(title.isEmpty() ? i18n("Medium Selection") : title); - setButtons (Ok|Cancel); - setModal(modal); - setDefaultButton(Ok); - QGridLayout* lay = new QGridLayout( widget ); - - QLabel* label = new QLabel( text.isEmpty() ? i18n("Please select a medium:") : text, widget ); - m_combo = new K3b::MediaSelectionComboBox( widget ); - - // lay->setMargin( marginHint() ); - lay->setSpacing( spacingHint() ); - lay->addWidget( label, 0, 0 ); - lay->addWidget( m_combo, 1, 0 ); - lay->setRowStretch( 2, 1 ); - - connect( m_combo, SIGNAL(selectionChanged(K3b::Device::Device*)), - this, SLOT(slotSelectionChanged(K3b::Device::Device*)) ); - - slotSelectionChanged( m_combo->selectedDevice() ); + QWidget *widget = new QWidget(); + setMainWidget(widget); + setCaption(title.isEmpty() ? i18n("Medium Selection") : title); + setButtons (Ok|Cancel); + setModal(modal); + setDefaultButton(Ok); + QGridLayout* lay = new QGridLayout( widget ); + + QLabel* label = new QLabel( text.isEmpty() ? i18n("Please select a medium:") : text, widget ); + m_combo = new K3b::MediaSelectionComboBox( widget ); + + // lay->setMargin( marginHint() ); + lay->setSpacing( spacingHint() ); + lay->addWidget( label, 0, 0 ); + lay->addWidget( m_combo, 1, 0 ); + lay->setRowStretch( 2, 1 ); + + connect( m_combo, SIGNAL(selectionChanged(K3b::Device::Device*)), + this, SLOT(slotSelectionChanged(K3b::Device::Device*)) ); + + slotSelectionChanged( m_combo->selectedDevice() ); } K3b::MediaSelectionDialog::~MediaSelectionDialog() { } -void K3b::MediaSelectionDialog::setWantedMediumType( int type ) +void K3b::MediaSelectionDialog::setWantedMediumType( Device::MediaTypes type ) { - m_combo->setWantedMediumType( type ); + m_combo->setWantedMediumType( type ); } -void K3b::MediaSelectionDialog::setWantedMediumState( int state ) +void K3b::MediaSelectionDialog::setWantedMediumState( Device::MediaStates state ) { - m_combo->setWantedMediumState( state ); + m_combo->setWantedMediumState( state ); } -void K3b::MediaSelectionDialog::setWantedMediumContent( int content ) +void K3b::MediaSelectionDialog::setWantedMediumContent( Medium::MediumContents content ) { - m_combo->setWantedMediumContent( content ); + m_combo->setWantedMediumContent( content ); } K3b::Device::Device* K3b::MediaSelectionDialog::selectedDevice() const { - return m_combo->selectedDevice(); + return m_combo->selectedDevice(); } void K3b::MediaSelectionDialog::slotSelectionChanged( K3b::Device::Device* dev ) { - enableButtonOk( dev != 0 ); + enableButtonOk( dev != 0 ); } -K3b::Device::Device* K3b::MediaSelectionDialog::selectMedium( int type, int state, int content, - QWidget* parent, - const QString& title, const QString& text, - bool* canceled ) +K3b::Device::Device* K3b::MediaSelectionDialog::selectMedium( Device::MediaTypes type, + Device::MediaStates state, + Medium::MediumContents content, + QWidget* parent, + const QString& title, const QString& text, + bool* canceled ) { - K3b::MediaSelectionDialog dlg( parent, title, text ); - dlg.setWantedMediumType( type ); - dlg.setWantedMediumState( state ); - dlg.setWantedMediumContent( content ); - - // even if no usable medium is inserted the combobox shows the "insert one" message - // so it's not sufficient to check for just one entry to check if there only is a - // single useable medium - if( ( dlg.selectedDevice() && dlg.m_combo->count() == 1 ) - || dlg.exec() == Accepted ) { - if( canceled ) - *canceled = false; - return dlg.selectedDevice(); - } - else { - if( canceled ) - *canceled = true; - return 0; - } -} - - -K3b::Device::Device* K3b::MediaSelectionDialog::selectMedium( int type, int state, - QWidget* parent, - const QString& title, const QString& text, - bool* canceled ) -{ - return selectMedium( type, state, K3b::Medium::CONTENT_ALL, parent, title, text, canceled ); + K3b::MediaSelectionDialog dlg( parent, title, text ); + dlg.setWantedMediumType( type ); + dlg.setWantedMediumState( state ); + dlg.setWantedMediumContent( content ); + + // even if no usable medium is inserted the combobox shows the "insert one" message + // so it's not sufficient to check for just one entry to check if there only is a + // single useable medium + if( ( dlg.selectedDevice() && dlg.m_combo->count() == 1 ) + || dlg.exec() == Accepted ) { + if( canceled ) + *canceled = false; + return dlg.selectedDevice(); + } + else { + if( canceled ) + *canceled = true; + return 0; + } } #include "k3bmediaselectiondialog.moc" diff --git a/src/k3bmediaselectiondialog.h b/src/k3bmediaselectiondialog.h index 56395b4b4..8fb98a2e5 100644 --- a/src/k3bmediaselectiondialog.h +++ b/src/k3bmediaselectiondialog.h @@ -1,90 +1,79 @@ /* * - * Copyright (C) 2005 Sebastian Trueg + * Copyright (C) 2005-2009 Sebastian Trueg * * This file is part of the K3b project. - * Copyright (C) 1998-2007 Sebastian Trueg + * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef _K3B_MEDIA_SELECTION_DIALOG_H_ #define _K3B_MEDIA_SELECTION_DIALOG_H_ #include #include namespace K3b { class MediaSelectionComboBox; -} -namespace Device { - class Device; -} -namespace K3b { + namespace Device { + class Device; + } + class MediaSelectionDialog : public KDialog { Q_OBJECT public: /** * Do not use the constructor. Use the static method instead. */ MediaSelectionDialog( QWidget* parent = 0, const QString& title = QString(), const QString& text = QString(), bool modal = false ); ~MediaSelectionDialog(); /** * \see MediaSelectionComboBox::setWantedMediumType() */ - void setWantedMediumType( int type ); + void setWantedMediumType( Device::MediaTypes type ); /** * \see MediaSelectionComboBox::setWantedMediumState() */ - void setWantedMediumState( int state ); + void setWantedMediumState( Device::MediaStates state ); /** * \see MediaSelectionComboBox::setWantedMediumContent() */ - void setWantedMediumContent( int state ); + void setWantedMediumContent( Medium::MediumContents state ); /** * Although the dialog is used to select a medium the result is the * device containing that medium. */ Device::Device* selectedDevice() const; - /** - * \deprecated - * - * Select a medium. - * If only one medium of the wanted type is found the method returns immideately - * without showing the dialog. - */ - static Device::Device* selectMedium( int type, int state, QWidget* parent = 0, - const QString& title = QString(), - const QString& text = QString(), - bool* canceled = 0 ); - - static Device::Device* selectMedium( int type, int state, int content = Medium::CONTENT_ALL, + static Device::Device* selectMedium( Device::MediaTypes type, + Device::MediaStates state, + Medium::MediumContents content = Medium::CONTENT_ALL, QWidget* parent = 0, const QString& title = QString(), const QString& text = QString(), bool* canceled = 0 ); private Q_SLOTS: void slotSelectionChanged( K3b::Device::Device* ); private: MediaSelectionComboBox* m_combo; }; } #endif diff --git a/src/k3bwriterselectionwidget.cpp b/src/k3bwriterselectionwidget.cpp index 8e3c60616..4053642a6 100644 --- a/src/k3bwriterselectionwidget.cpp +++ b/src/k3bwriterselectionwidget.cpp @@ -1,660 +1,660 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bwriterselectionwidget.h" #include "k3bapplication.h" #include "k3bmediacache.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { int s_autoSpeedValue = 0; int s_ignoreSpeedValue = -1; int s_moreSpeedValue = -2; } class K3b::WriterSelectionWidget::MediaSelectionComboBox : public K3b::MediaSelectionComboBox { public: MediaSelectionComboBox( QWidget* parent ) : K3b::MediaSelectionComboBox( parent ), m_overrideDevice( 0 ) { } void setOverrideDevice( K3b::Device::Device* dev, const QString& s, const QString& t ) { m_overrideDevice = dev; m_overrideString = s; m_overrideToolTip = t; updateMedia(); } K3b::Device::Device* overrideDevice() const { return m_overrideDevice; } protected: bool showMedium( const K3b::Medium& m ) const { return ( m.device() == m_overrideDevice || K3b::MediaSelectionComboBox::showMedium( m ) ); } QString mediumString( const K3b::Medium& m ) const { if( m.device() == m_overrideDevice ) return m_overrideString; else return K3b::MediaSelectionComboBox::mediumString( m ); } QString mediumToolTip( const K3b::Medium& m ) const { if( m.device() == m_overrideDevice ) return m_overrideToolTip; else { QString s = K3b::MediaSelectionComboBox::mediumToolTip( m ); if( !m.diskInfo().empty() && !(wantedMediumState() & m.diskInfo().diskState()) ) s.append( "

" + i18n("Medium will be overwritten.") + "" ); return s; } } private: K3b::Device::Device* m_overrideDevice; QString m_overrideString; QString m_overrideToolTip; }; class K3b::WriterSelectionWidget::Private { public: bool forceAutoSpeed; bool haveIgnoreSpeed; bool haveManualSpeed; K3b::WritingApps supportedWritingApps; int lastSetSpeed; }; K3b::WriterSelectionWidget::WriterSelectionWidget( QWidget *parent ) : QWidget( parent ) { d = new Private; d->forceAutoSpeed = false; d->supportedWritingApps = K3b::WRITING_APP_CDRECORD|K3b::WRITING_APP_CDRDAO|K3b::WRITING_APP_GROWISOFS; d->lastSetSpeed = -1; QGroupBox* groupWriter = new QGroupBox( this ); groupWriter->setTitle( i18n( "Burn Medium" ) ); QGridLayout* groupWriterLayout = new QGridLayout( groupWriter ); groupWriterLayout->setAlignment( Qt::AlignTop ); groupWriterLayout->setSpacing( KDialog::spacingHint() ); groupWriterLayout->setMargin( KDialog::marginHint() ); QLabel* labelSpeed = new QLabel( groupWriter ); labelSpeed->setText( i18n( "Speed:" ) ); m_comboSpeed = new K3b::IntMapComboBox( groupWriter ); m_comboMedium = new MediaSelectionComboBox( groupWriter ); m_writingAppLabel = new QLabel( i18n("Writing app:"), groupWriter ); m_comboWritingApp = new K3b::IntMapComboBox( groupWriter ); groupWriterLayout->addWidget( m_comboMedium, 0, 0 ); groupWriterLayout->addWidget( labelSpeed, 0, 1 ); groupWriterLayout->addWidget( m_comboSpeed, 0, 2 ); groupWriterLayout->addWidget( m_writingAppLabel, 0, 3 ); groupWriterLayout->addWidget( m_comboWritingApp, 0, 4 ); groupWriterLayout->setColumnStretch( 0, 1 ); QGridLayout* mainLayout = new QGridLayout( this ); mainLayout->setAlignment( Qt::AlignTop ); mainLayout->setSpacing( KDialog::spacingHint() ); mainLayout->setMargin( 0 ); mainLayout->addWidget( groupWriter, 0, 0 ); // tab order setTabOrder( m_comboMedium, m_comboSpeed ); setTabOrder( m_comboSpeed, m_comboWritingApp ); connect( m_comboMedium, SIGNAL(selectionChanged(K3b::Device::Device*)), this, SIGNAL(writerChanged()) ); connect( m_comboMedium, SIGNAL(selectionChanged(K3b::Device::Device*)), this, SIGNAL(writerChanged(K3b::Device::Device*)) ); connect( m_comboMedium, SIGNAL(newMedia()), this, SIGNAL(newMedia()) ); connect( m_comboMedium, SIGNAL(newMedium(K3b::Device::Device*)), this, SIGNAL(newMedium(K3b::Device::Device*)) ); connect( m_comboMedium, SIGNAL(newMedium(K3b::Device::Device*)), this, SLOT(slotNewBurnMedium(K3b::Device::Device*)) ); connect( m_comboWritingApp, SIGNAL(valueChanged(int)), this, SLOT(slotWritingAppSelected(int)) ); connect( this, SIGNAL(writerChanged()), SLOT(slotWriterChanged()) ); connect( m_comboSpeed, SIGNAL(valueChanged(int)), this, SLOT(slotSpeedChanged(int)) ); m_comboMedium->setToolTip( i18n("The medium that will be used for burning") ); m_comboSpeed->setToolTip( i18n("The speed at which to burn the medium") ); m_comboWritingApp->setToolTip( i18n("The external application to actually burn the medium") ); m_comboMedium->setWhatsThis( i18n("

Select the medium that you want to use for burning." "

In most cases there will only be one medium available which " "does not leave much choice.") ); m_comboSpeed->setWhatsThis( i18n("

Select the speed with which you want to burn." "

Auto
" "This will choose the maximum writing speed possible with the used " "medium. " "This is the recommended selection for most media.

" "

Ignore (DVD only)
" "This will leave the speed selection to the writer device. " "Use this if K3b is unable to set the writing speed." "

1x refers to 175 KB/s for CD, 1385 KB/s for DVD, and 4496 KB/s for Blu-ray.

" "

Caution: Make sure your system is able to send the data " "fast enough to prevent buffer underruns.") ); m_comboWritingApp->setWhatsThis( i18n("

K3b uses the command line tools cdrecord, growisofs, and cdrdao " "to actually write a CD or DVD." "

Normally K3b chooses the best " "suited application for every task automatically but in some cases it " "may be possible that one of the applications does not work as intended " "with a certain writer. In this case one may select the " "application manually.") ); clearSpeedCombo(); slotConfigChanged( KGlobal::config() ); slotWriterChanged(); } K3b::WriterSelectionWidget::~WriterSelectionWidget() { delete d; } -void K3b::WriterSelectionWidget::setWantedMediumType( int type ) +void K3b::WriterSelectionWidget::setWantedMediumType( Device::MediaTypes type ) { m_comboMedium->setWantedMediumType( type ); } -void K3b::WriterSelectionWidget::setWantedMediumState( int state ) +void K3b::WriterSelectionWidget::setWantedMediumState( Device::MediaStates state ) { m_comboMedium->setWantedMediumState( state ); } void K3b::WriterSelectionWidget::setWantedMediumSize( const K3b::Msf& minSize ) { #warning The wanted medium size may not be enough if we need to handle multisession! m_comboMedium->setWantedMediumSize( minSize ); } -int K3b::WriterSelectionWidget::wantedMediumType() const +K3b::Device::MediaTypes K3b::WriterSelectionWidget::wantedMediumType() const { return m_comboMedium->wantedMediumType(); } -int K3b::WriterSelectionWidget::wantedMediumState() const +K3b::Device::MediaStates K3b::WriterSelectionWidget::wantedMediumState() const { return m_comboMedium->wantedMediumState(); } K3b::Msf K3b::WriterSelectionWidget::wantedMediumSize() const { return m_comboMedium->wantedMediumSize(); } void K3b::WriterSelectionWidget::slotConfigChanged( KSharedConfig::Ptr c ) { KConfigGroup g( c, "General Options" ); if( g.readEntry( "Manual writing app selection", false ) ) { m_comboWritingApp->show(); m_writingAppLabel->show(); } else { m_comboWritingApp->hide(); m_writingAppLabel->hide(); } } void K3b::WriterSelectionWidget::slotRefreshWriterSpeeds() { if( writerDevice() ) { QList speeds = k3bappcore->mediaCache()->writingSpeeds( writerDevice() ); int lastSpeed = writerSpeed(); clearSpeedCombo(); m_comboSpeed->insertItem( s_autoSpeedValue, i18n("Auto") ); if( k3bappcore->mediaCache()->diskInfo( writerDevice() ).isDvdMedia() ) { m_comboSpeed->insertItem( s_ignoreSpeedValue, i18n("Ignore") ); d->haveIgnoreSpeed = true; } else d->haveIgnoreSpeed = false; if( !d->forceAutoSpeed ) { if( speeds.isEmpty() || writerDevice() == m_comboMedium->overrideDevice() ) { // // In case of the override device we do not know which medium will actually be used // So this is the only case in which we need to use the device's max writing speed // // But we need to know if it will be a CD or DVD medium. Since the override device // is only used for CD/DVD copy anyway we simply reply on the inserted medium's type. // int i = 1; int x1Speed = K3b::Device::SPEED_FACTOR_CD; if ( k3bappcore->mediaCache()->diskInfo( writerDevice() ).isDvdMedia() ) { x1Speed = K3b::Device::SPEED_FACTOR_DVD; } else if ( K3b::Device::isBdMedia( k3bappcore->mediaCache()->diskInfo( writerDevice() ).mediaType() ) ) { x1Speed = K3b::Device::SPEED_FACTOR_BD; } int max = writerDevice()->maxWriteSpeed(); while( i*x1Speed <= max ) { insertSpeedItem( i*x1Speed ); // a little hack to handle the stupid 2.4x DVD speed if( i == 2 && x1Speed == K3b::Device::SPEED_FACTOR_DVD ) insertSpeedItem( (int)(2.4*( double )K3b::Device::SPEED_FACTOR_DVD) ); i = ( i == 1 ? 2 : i+2 ); } } else { for( QList::iterator it = speeds.begin(); it != speeds.end(); ++it ) insertSpeedItem( *it ); } } // // Although most devices return all speeds properly there are still some dumb ones around // that don't. Users of those will need the possibility to set the speed manually even if // a medium is inserted. // if ( !d->forceAutoSpeed ) { m_comboSpeed->insertItem( s_moreSpeedValue, i18n("More...") ); d->haveManualSpeed = true; } else { d->haveManualSpeed = false; } // try to reload last set speed if( d->lastSetSpeed == -1 ) setSpeed( lastSpeed ); else setSpeed( d->lastSetSpeed ); } m_comboSpeed->setEnabled( writerDevice() != 0 ); } void K3b::WriterSelectionWidget::clearSpeedCombo() { m_comboSpeed->clear(); d->haveManualSpeed = false; d->haveIgnoreSpeed = false; } void K3b::WriterSelectionWidget::insertSpeedItem( int speed ) { Device::MediaType mediaType = k3bappcore->mediaCache()->diskInfo( writerDevice() ).mediaType(); int insertIndex = -1; if ( m_comboSpeed->hasValue( s_moreSpeedValue ) ) { insertIndex = m_comboSpeed->count()-1; } // // polish the speed // if( K3b::Device::isDvdMedia( mediaType ) ) { // // AFAIK there is only one strange DVD burning speed like 2.4 // int xs = ( int )( 1385.0*2.4 ); if ( abs( speed - xs ) < 1385/2 ) speed = xs; else speed = ( ( speed+692 )/1385 )*1385; } else if ( K3b::Device::isBdMedia( mediaType ) ) { speed = ( ( speed+2250 )/4496 )*4496; } else { speed = ( ( speed + ( K3b::Device::SPEED_FACTOR_CD/2 ) )/K3b::Device::SPEED_FACTOR_CD )*K3b::Device::SPEED_FACTOR_CD; } if( !m_comboSpeed->hasValue( speed ) ) { if( K3b::Device::isDvdMedia( mediaType ) ) { m_comboSpeed->insertItem( speed, ( speed%1385 > 0 ? QString::number( (float)speed/1385.0, 'f', 1 ) // example: DVD+R(W): 2.4x : QString::number( speed/K3b::Device::SPEED_FACTOR_DVD ) ) + "x", QString(), insertIndex ); } else if ( K3b::Device::isBdMedia( mediaType ) ) { m_comboSpeed->insertItem( speed, QString("%1x").arg(speed/K3b::Device::SPEED_FACTOR_BD), QString(), insertIndex ); } else { m_comboSpeed->insertItem( speed, QString("%1x").arg(speed/K3b::Device::SPEED_FACTOR_CD), QString(), insertIndex ); } } } void K3b::WriterSelectionWidget::slotWritingAppSelected( int app ) { emit writingAppChanged( K3b::WritingApp( app ) ); } K3b::Device::Device* K3b::WriterSelectionWidget::writerDevice() const { return m_comboMedium->selectedDevice(); } QList K3b::WriterSelectionWidget::allDevices() const { return m_comboMedium->allDevices(); } void K3b::WriterSelectionWidget::setWriterDevice( K3b::Device::Device* dev ) { m_comboMedium->setSelectedDevice( dev ); } void K3b::WriterSelectionWidget::setSpeed( int s ) { d->lastSetSpeed = -1; if( d->haveIgnoreSpeed && s < 0 ) m_comboSpeed->setSelectedValue( s_ignoreSpeedValue ); // Ignore else if( m_comboSpeed->hasValue( s ) ) m_comboSpeed->setSelectedValue( s ); else { m_comboSpeed->setSelectedValue( s_autoSpeedValue ); // Auto d->lastSetSpeed = s; // remember last set speed } } void K3b::WriterSelectionWidget::setWritingApp( K3b::WritingApp app ) { m_comboWritingApp->setSelectedValue( ( int )app ); } int K3b::WriterSelectionWidget::writerSpeed() const { if( m_comboSpeed->selectedValue() == s_autoSpeedValue ) return 0; // Auto else if( d->haveIgnoreSpeed && m_comboSpeed->selectedValue() == s_ignoreSpeedValue ) return -1; // Ignore else return m_comboSpeed->selectedValue(); } K3b::WritingApp K3b::WriterSelectionWidget::writingApp() const { KConfigGroup g( KGlobal::config(), "General Options" ); if( g.readEntry( "Manual writing app selection", false ) ) { return selectedWritingApp(); } else return K3b::WRITING_APP_DEFAULT; } K3b::WritingApp K3b::WriterSelectionWidget::selectedWritingApp() const { return K3b::WritingApp( m_comboWritingApp->selectedValue() ); } void K3b::WriterSelectionWidget::slotSpeedChanged( int s ) { // the last item is the manual speed selection item if( d->haveManualSpeed && s == s_moreSpeedValue ) { slotManualSpeed(); } else { d->lastSetSpeed = s; if( K3b::Device::Device* dev = writerDevice() ) dev->setCurrentWriteSpeed( writerSpeed() ); } } void K3b::WriterSelectionWidget::slotWriterChanged() { slotRefreshWriterSpeeds(); slotRefreshWritingApps(); // save last selected writer if( K3b::Device::Device* dev = writerDevice() ) { KConfigGroup g( KGlobal::config(), "General Options" ); g.writeEntry( "current_writer", dev->blockDeviceName() ); } } void K3b::WriterSelectionWidget::setSupportedWritingApps( K3b::WritingApps i ) { K3b::WritingApp oldApp = writingApp(); d->supportedWritingApps = i; slotRefreshWritingApps(); setWritingApp( oldApp ); } void K3b::WriterSelectionWidget::slotRefreshWritingApps() { K3b::WritingApps i = 0; // select the ones that make sense if( k3bappcore->mediaCache()->diskInfo( writerDevice() ).isDvdMedia() ) i = K3b::WRITING_APP_GROWISOFS|K3b::WRITING_APP_DVD_RW_FORMAT|K3b::WRITING_APP_CDRECORD; else if ( K3b::Device::isBdMedia( k3bappcore->mediaCache()->diskInfo( writerDevice() ).mediaType() ) ) i = K3b::WRITING_APP_GROWISOFS|K3b::WRITING_APP_CDRECORD; else i = K3b::WRITING_APP_CDRDAO|K3b::WRITING_APP_CDRECORD; // now strip it down to the ones we support i &= d->supportedWritingApps; m_comboWritingApp->clear(); m_comboWritingApp->insertItem( K3b::WRITING_APP_DEFAULT, i18n("Auto") ); if( i & K3b::WRITING_APP_CDRDAO ) m_comboWritingApp->insertItem( K3b::WRITING_APP_CDRDAO, "cdrdao" ); if( i & K3b::WRITING_APP_CDRECORD ) m_comboWritingApp->insertItem( K3b::WRITING_APP_CDRECORD, "cdrecord" ); if( i & K3b::WRITING_APP_GROWISOFS ) m_comboWritingApp->insertItem( K3b::WRITING_APP_GROWISOFS, "growisofs" ); if( i & K3b::WRITING_APP_DVD_RW_FORMAT ) m_comboWritingApp->insertItem( K3b::WRITING_APP_DVD_RW_FORMAT, "dvd+rw-format" ); m_comboWritingApp->setEnabled( writerDevice() != 0 ); } void K3b::WriterSelectionWidget::loadConfig( const KConfigGroup& c ) { setWriterDevice( k3bcore->deviceManager()->findDevice( c.readEntry( "writer_device" ) ) ); setSpeed( c.readEntry( "writing_speed", s_autoSpeedValue ) ); setWritingApp( K3b::writingAppFromString( c.readEntry( "writing_app" ) ) ); } void K3b::WriterSelectionWidget::saveConfig( KConfigGroup c ) { c.writeEntry( "writing_speed", writerSpeed() ); c.writeEntry( "writer_device", writerDevice() ? writerDevice()->blockDeviceName() : QString() ); c.writeEntry( "writing_app", m_comboWritingApp->currentText() ); } void K3b::WriterSelectionWidget::setForceAutoSpeed( bool b ) { d->forceAutoSpeed = b; slotRefreshWriterSpeeds(); } void K3b::WriterSelectionWidget::setOverrideDevice( K3b::Device::Device* dev, const QString& overrideString, const QString& tooltip ) { m_comboMedium->setOverrideDevice( dev, overrideString, tooltip ); } void K3b::WriterSelectionWidget::slotNewBurnMedium( K3b::Device::Device* dev ) { // // Try to select a medium that is better suited than the current one // if( dev && dev != writerDevice() ) { K3b::Medium medium = k3bappcore->mediaCache()->medium( dev ); // // Always prefer newly inserted media over the override device // if( writerDevice() == m_comboMedium->overrideDevice() ) { setWriterDevice( dev ); } // // Prefer an empty medium over one that has to be erased // else if( wantedMediumState() & K3b::Device::STATE_EMPTY && !k3bappcore->mediaCache()->diskInfo( writerDevice() ).empty() && medium.diskInfo().empty() ) { setWriterDevice( dev ); } } } void K3b::WriterSelectionWidget::slotManualSpeed() { // // In case we think we have all the available speeds (i.e. if the device reported a non-empty list) // we just treat it as a manual selection. Otherwise we admit that we cannot do better // bool haveSpeeds = ( writerDevice() && !k3bappcore->mediaCache()->writingSpeeds( writerDevice() ).isEmpty() ); QString s; if ( haveSpeeds ) { s = i18n( "Please enter the speed that K3b should use for burning (Example: 16x)." ); } else { s = i18n("

K3b is not able to perfectly determine the maximum " "writing speed of an optical writer. Writing speed is always " "reported subject to the inserted medium." "

Please enter the writing speed here and K3b will remember it " "for future sessions (Example: 16x)."); } // // We need to know the type of medium. Since the override device // is only used for copy anyway we simply reply on the inserted medium's type. // int speedFactor = K3b::Device::SPEED_FACTOR_CD; if( k3bappcore->mediaCache()->diskInfo( writerDevice() ).isDvdMedia() ) { speedFactor = K3b::Device::SPEED_FACTOR_DVD; } else if ( k3bappcore->mediaCache()->diskInfo( writerDevice() ).mediaType() & K3b::Device::MEDIA_BD_ALL ) { speedFactor = K3b::Device::SPEED_FACTOR_BD; } bool ok = true; int newSpeed = KInputDialog::getInteger( i18n("Set writing speed manually"), s, writerDevice()->maxWriteSpeed()/speedFactor, 1, 10000, 1, 10, &ok, this ) * speedFactor; if( ok ) { writerDevice()->setMaxWriteSpeed( qMax( newSpeed, writerDevice()->maxWriteSpeed() ) ); if ( haveSpeeds ) { insertSpeedItem( newSpeed ); } else { slotRefreshWriterSpeeds(); } setSpeed( newSpeed ); } else { if( d->lastSetSpeed == -1 ) m_comboSpeed->setSelectedValue( s_autoSpeedValue ); // Auto else setSpeed( d->lastSetSpeed ); } } void K3b::WriterSelectionWidget::setIgnoreDevice( K3b::Device::Device* dev ) { m_comboMedium->setIgnoreDevice( dev ); } #include "k3bwriterselectionwidget.moc" diff --git a/src/k3bwriterselectionwidget.h b/src/k3bwriterselectionwidget.h index 1bc5213d6..f128b50b2 100644 --- a/src/k3bwriterselectionwidget.h +++ b/src/k3bwriterselectionwidget.h @@ -1,156 +1,156 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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. */ #ifndef K3BWRITERSELECTIONWIDGET_H #define K3BWRITERSELECTIONWIDGET_H #include #include #include #include class QLabel; namespace K3b { class IntMapComboBox; class MediaSelectionComboBox; class Msf; namespace Device { class Device; } class WriterSelectionWidget : public QWidget { Q_OBJECT public: /** * Creates a writerselectionwidget */ WriterSelectionWidget( QWidget* parent = 0 ); ~WriterSelectionWidget(); int writerSpeed() const; Device::Device* writerDevice() const; QList allDevices() const; /** * returns K3b::WritingApp */ K3b::WritingApp writingApp() const; - int wantedMediumType() const; - int wantedMediumState() const; + Device::MediaTypes wantedMediumType() const; + Device::MediaStates wantedMediumState() const; K3b::Msf wantedMediumSize() const; void loadConfig( const KConfigGroup& ); void saveConfig( KConfigGroup ); public Q_SLOTS: void setWriterDevice( K3b::Device::Device* ); void setSpeed( int ); void setWritingApp( K3b::WritingApp app ); /** * K3b::WritingApp or'ed together * * Defaults to cdrecord and cdrdao for CD and growisofs for DVD */ void setSupportedWritingApps( K3b::WritingApps apps ); /** * A simple hack to disable the speed selection for DVD formatting */ void setForceAutoSpeed( bool ); /** * Set the wanted medium type. Defaults to writable CD. * * \param type a bitwise combination of the Device::MediaType enum */ - void setWantedMediumType( int type ); + void setWantedMediumType( K3b::Device::MediaTypes type ); /** * Set the wanted medium state. Defaults to empty media. * * \param state a bitwise combination of the Device::State enum */ - void setWantedMediumState( int state ); + void setWantedMediumState( K3b::Device::MediaStates state ); /** * Set the wanted medium size. Defaults to 0 which means * that the size should be irgnored. */ void setWantedMediumSize( const K3b::Msf& minSize ); /** * This is a hack to allow the copy dialogs to use the same device for reading * and writing without having the user to choose the same medium. * * \param overrideString A string which will be shown in place of the medium string. * For example: "Burn to the same device". Set it to 0 in order * to disable the feature. */ void setOverrideDevice( K3b::Device::Device* dev, const QString& overrideString = QString(), const QString& tooltip = QString() ); /** * Compare MediaSelectionComboBox::setIgnoreDevice */ void setIgnoreDevice( K3b::Device::Device* dev ); Q_SIGNALS: void writerChanged(); void writerChanged( K3b::Device::Device* ); void writingAppChanged( K3b::WritingApp app ); /** * \see MediaSelectionComboBox */ void newMedia(); void newMedium( K3b::Device::Device* dev ); private Q_SLOTS: void slotRefreshWriterSpeeds(); void slotRefreshWritingApps(); void slotWritingAppSelected( int id ); void slotConfigChanged( KSharedConfig::Ptr c ); void slotSpeedChanged( int index ); void slotWriterChanged(); void slotNewBurnMedium( K3b::Device::Device* dev ); void slotManualSpeed(); private: void clearSpeedCombo(); void insertSpeedItem( int ); K3b::WritingApp selectedWritingApp() const; class MediaSelectionComboBox; IntMapComboBox* m_comboSpeed; MediaSelectionComboBox* m_comboMedium; IntMapComboBox* m_comboWritingApp; QLabel* m_writingAppLabel; class Private; Private* d; }; } #endif diff --git a/src/projects/k3bfillstatusdisplay.cpp b/src/projects/k3bfillstatusdisplay.cpp index 9c88578fb..c58e1cb65 100644 --- a/src/projects/k3bfillstatusdisplay.cpp +++ b/src/projects/k3bfillstatusdisplay.cpp @@ -1,813 +1,814 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bfillstatusdisplay.h" #include "k3bdoc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const int DEFAULT_CD_SIZE_74 = 74*60*75; static const int DEFAULT_CD_SIZE_80 = 80*60*75; static const int DEFAULT_CD_SIZE_100 = 100*60*75; static const int DEFAULT_DVD_SIZE_4_4 = 2295104; static const int DEFAULT_DVD_SIZE_8_0 = 4173824; // FIXME: get the proper BD sizes static const int DEFAULT_BD_SIZE_25 = 13107200; static const int DEFAULT_BD_SIZE_50 = 26214400; class K3b::FillStatusDisplayWidget::Private { public: K3b::Msf cdSize; bool showTime; K3b::Doc* doc; }; K3b::FillStatusDisplayWidget::FillStatusDisplayWidget( K3b::Doc* doc, QWidget* parent ) : QWidget( parent ) { d = new Private(); d->doc = doc; setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred ) ); } K3b::FillStatusDisplayWidget::~FillStatusDisplayWidget() { delete d; } const K3b::Msf& K3b::FillStatusDisplayWidget::cdSize() const { return d->cdSize; } void K3b::FillStatusDisplayWidget::setShowTime( bool b ) { d->showTime = b; update(); } void K3b::FillStatusDisplayWidget::setCdSize( const K3b::Msf& size ) { d->cdSize = size; update(); } QSize K3b::FillStatusDisplayWidget::sizeHint() const { return minimumSizeHint(); } QSize K3b::FillStatusDisplayWidget::minimumSizeHint() const { int margin = 2; QFontMetrics fm( font() ); return QSize( -1, fm.height() + 2 * margin ); } void K3b::FillStatusDisplayWidget::mousePressEvent( QMouseEvent* e ) { if( e->button() == Qt::RightButton ) emit contextMenu( e->globalPos() ); } void K3b::FillStatusDisplayWidget::paintEvent( QPaintEvent* ) { QPainter p( this ); p.setPen( Qt::black ); // we use a fixed bar color (which is not very nice btw, so we also fix the text color) K3b::Msf docSize; K3b::Msf cdSize; K3b::Msf maxValue; K3b::Msf tolerance; docSize = d->doc->length(); cdSize = d->cdSize; maxValue = (cdSize > docSize ? cdSize : docSize) + ( 10*60*75 ); tolerance = 60*75; // so split width() in maxValue pieces double one = (double)rect().width() / (double)maxValue.totalFrames(); QRect crect( rect() ); crect.setWidth( (int)(one*(double)docSize.totalFrames()) ); p.setClipping(true); p.setClipRect(crect); p.fillRect( crect, Qt::green ); QRect oversizeRect(crect); // draw red if docSize > cdSize + tolerance if( docSize > cdSize + tolerance ) { oversizeRect.setLeft( oversizeRect.left() + (int)(one * (cdSize - tolerance).totalFrames()) ); p.fillRect( oversizeRect, Qt::red ); QLinearGradient gradient( QPoint( oversizeRect.left() - rect().height(), 0 ), QPoint( oversizeRect.left() + rect().height(), 0 ) ); gradient.setColorAt( 0.1, Qt::green ); gradient.setColorAt( 0.5, Qt::yellow ); gradient.setColorAt( 0.9, Qt::red ); p.fillRect( oversizeRect.left() - rect().height(), 0, rect().height()*2, rect().height(), gradient ); } // draw yellow if cdSize - tolerance < docSize else if( docSize > cdSize - tolerance ) { oversizeRect.setLeft( oversizeRect.left() + (int)(one * (cdSize - tolerance).lba()) ); p.fillRect( oversizeRect, Qt::yellow ); QLinearGradient gradient( QPoint( oversizeRect.left() - rect().height(), 0 ), QPoint( oversizeRect.left() + rect().height(), 0 ) ); gradient.setColorAt( 0.1, Qt::green ); gradient.setColorAt( 0.9, Qt::yellow ); p.fillRect( oversizeRect.left() - rect().height(), 0, rect().height()*2, rect().height(), gradient ); } p.setClipping(false); // ==================================================================================== // Now the colored bar is painted // Continue with the texts // ==================================================================================== // first we determine the text to display // ==================================================================================== QString docSizeText; if( d->showTime ) docSizeText = d->doc->length().toString(false) + " " + i18n("min"); else docSizeText = KIO::convertSize( d->doc->size() ); QString overSizeText; if( d->cdSize.mode1Bytes() >= d->doc->size() ) overSizeText = i18n("Available: %1 of %2", d->showTime ? i18n("%1 min", (cdSize - d->doc->length()).toString(false) ) : KIO::convertSize( (cdSize - d->doc->length()).mode1Bytes() ), d->showTime ? i18n("%1 min", cdSize.toString(false)) : KIO::convertSize( cdSize.mode1Bytes() ) ); else overSizeText = i18n("Capacity exceeded by %1", d->showTime ? i18n("%1 min", (d->doc->length() - cdSize ).toString(false)) : KIO::convertSize( (long long)d->doc->size() - cdSize.mode1Bytes() ) ); // ==================================================================================== // draw the medium size marker // ==================================================================================== int mediumSizeMarkerPos = rect().left() + (int)(one*cdSize.lba()); p.drawLine( mediumSizeMarkerPos, rect().bottom(), mediumSizeMarkerPos, rect().top() + ((rect().bottom()-rect().top())/2) ); // ==================================================================================== // we want to draw the docSizeText centered in the filled area // if there is not enough space we just align it left // ==================================================================================== int docSizeTextPos = 0; int docSizeTextLength = fontMetrics().width(docSizeText); if( docSizeTextLength + 5 > crect.width() ) { docSizeTextPos = crect.left() + 5; // a little margin } else { docSizeTextPos = ( crect.width() - docSizeTextLength ) / 2; // make sure the text does not cross the medium size marker if( docSizeTextPos <= mediumSizeMarkerPos && mediumSizeMarkerPos <= docSizeTextPos + docSizeTextLength ) docSizeTextPos = qMax( crect.left() + 5, mediumSizeMarkerPos - docSizeTextLength - 5 ); } // ==================================================================================== // draw the over size text // ==================================================================================== QFont fnt(font()); fnt.setPointSize( qMax( 8, fnt.pointSize()-4 ) ); fnt.setBold(false); QRect overSizeTextRect( rect() ); int overSizeTextLength = QFontMetrics(fnt).width(overSizeText); if( overSizeTextLength + 5 > overSizeTextRect.width() - (int)(one*cdSize.totalFrames()) ) { // we don't have enough space on the right, so we paint to the left of the line overSizeTextRect.setLeft( (int)(one*cdSize.totalFrames()) - overSizeTextLength - 5 ); } else { overSizeTextRect.setLeft( mediumSizeMarkerPos + 5 ); } // make sure the two text do not overlap (this does not cover all cases though) if( overSizeTextRect.left() < docSizeTextPos + docSizeTextLength ) docSizeTextPos = qMax( crect.left() + 5, qMin( overSizeTextRect.left() - docSizeTextLength - 5, mediumSizeMarkerPos - docSizeTextLength - 5 ) ); QRect docTextRect( rect() ); docTextRect.setLeft( docSizeTextPos ); p.drawText( docTextRect, Qt::AlignLeft | Qt::AlignVCenter, docSizeText ); p.setFont(fnt); p.drawText( overSizeTextRect, Qt::AlignLeft | Qt::AlignVCenter, overSizeText ); // ==================================================================================== } // ---------------------------------------------------------------------------------------------------- class K3b::FillStatusDisplay::Private { public: KActionCollection* actionCollection; KAction* actionShowMinutes; KAction* actionShowMegs; KAction* actionAuto; KAction* action74Min; KAction* action80Min; KAction* action100Min; KAction* actionDvd4_7GB; KAction* actionDvdDoubleLayer; KAction* actionBD25; KAction* actionBD50; KAction* actionCustomSize; KAction* actionDetermineSize; KAction* actionSaveUserDefaults; KAction* actionLoadUserDefaults; KMenu* popup; QToolButton* buttonMenu; K3b::FillStatusDisplayWidget* displayWidget; bool showTime; K3b::Doc* doc; QTimer updateTimer; }; K3b::FillStatusDisplay::FillStatusDisplay( K3b::Doc* doc, QWidget *parent ) : QFrame(parent) { d = new Private; d->doc = doc; setFrameStyle( Panel | Sunken ); d->displayWidget = new K3b::FillStatusDisplayWidget( doc, this ); // d->buttonMenu = new QToolButton( this ); // d->buttonMenu->setIconSet( SmallIconSet("media-optical") ); // d->buttonMenu->setAutoRaise(true); // d->buttonMenu->setToolTip( i18n("Fill display properties") ); // connect( d->buttonMenu, SIGNAL(clicked()), this, SLOT(slotMenuButtonClicked()) ); QGridLayout* layout = new QGridLayout( this ); layout->setSpacing(5); layout->setMargin(frameWidth()); layout->addWidget( d->displayWidget, 0, 0 ); // layout->addWidget( d->buttonMenu, 0, 1 ); layout->setColumnStretch( 0, 1 ); setupPopupMenu(); connect( d->doc, SIGNAL(changed()), this, SLOT(slotDocChanged()) ); connect( &d->updateTimer, SIGNAL(timeout()), this, SLOT(slotUpdateDisplay()) ); connect( k3bappcore->mediaCache(), SIGNAL(mediumChanged(K3b::Device::Device*)), this, SLOT(slotMediumChanged(K3b::Device::Device*)) ); slotLoadUserDefaults(); } K3b::FillStatusDisplay::~FillStatusDisplay() { delete d; } void K3b::FillStatusDisplay::setupPopupMenu() { d->actionCollection = new KActionCollection( this ); // we use a nother popup for the dvd sizes d->popup = new KMenu( this ); d->actionShowMinutes = K3b::createToggleAction( this, i18n("Minutes"), 0, 0, this, SLOT(showTime()), d->actionCollection, "fillstatus_show_minutes" ); d->actionShowMegs = K3b::createToggleAction( this, i18n("Megabytes"), 0, 0, this, SLOT(showSize()), d->actionCollection, "fillstatus_show_megabytes" ); d->actionAuto = K3b::createToggleAction( this, i18n("Automatic Size"), 0, 0, this, SLOT(slotAutoSize()), d->actionCollection, "fillstatus_auto" ); d->action74Min = K3b::createToggleAction( this, i18n("%1 MB",650), 0, 0, this, SLOT(slot74Minutes()), d->actionCollection, "fillstatus_74minutes" ); d->action80Min = K3b::createToggleAction( this, i18n("%1 MB",700), 0, 0, this, SLOT(slot80Minutes()), d->actionCollection, "fillstatus_80minutes" ); d->action100Min = K3b::createToggleAction( this, i18n("%1 MB",880), 0, 0, this, SLOT(slot100Minutes()), d->actionCollection, "fillstatus_100minutes" ); d->actionDvd4_7GB = K3b::createToggleAction( this, KIO::convertSizeFromKiB((int)(4.4*1024.0*1024.0)), 0, 0, this, SLOT(slotDvd4_7GB()), d->actionCollection, "fillstatus_dvd_4_7gb" ); d->actionDvdDoubleLayer = K3b::createToggleAction( this, KIO::convertSizeFromKiB((int)(8.0*1024.0*1024.0)), 0, 0, this, SLOT(slotDvdDoubleLayer()), d->actionCollection, "fillstatus_dvd_double_layer" ); d->actionBD25 = K3b::createToggleAction( this, KIO::convertSizeFromKiB( 25*1024*1024 ), 0, 0, this, SLOT( slotBD25() ), d->actionCollection, "fillstatus_bd_25" ); d->actionBD50 = K3b::createToggleAction( this, KIO::convertSizeFromKiB( 50*1024*1024 ), 0, 0, this, SLOT( slotBD50() ), d->actionCollection, "fillstatus_bd_50" ); d->actionCustomSize = K3b::createToggleAction( this, i18n("Custom..."), 0, 0, this, SLOT(slotCustomSize()), d->actionCollection, "fillstatus_custom_size" ); #ifdef __GNUC__ #warning setAlwaysEmitActivated #endif // d->actionCustomSize->setAlwaysEmitActivated(true); d->actionDetermineSize = K3b::createToggleAction( this, i18n("From Medium..."), "media-optical", 0, this, SLOT(slotDetermineSize()), d->actionCollection, "fillstatus_size_from_disk" ); // d->actionDetermineSize->setAlwaysEmitActivated(true); QActionGroup* showSizeInGroup = new QActionGroup( this ); showSizeInGroup->addAction( d->actionShowMegs ); showSizeInGroup->addAction( d->actionShowMinutes ); QActionGroup* cdSizeGroup = new QActionGroup( this ); cdSizeGroup->addAction( d->actionAuto ); cdSizeGroup->addAction( d->action74Min ); cdSizeGroup->addAction( d->action80Min ); cdSizeGroup->addAction( d->action100Min ); cdSizeGroup->addAction( d->actionDvd4_7GB ); cdSizeGroup->addAction( d->actionDvdDoubleLayer ); cdSizeGroup->addAction( d->actionBD25 ); cdSizeGroup->addAction( d->actionBD50 ); cdSizeGroup->addAction( d->actionCustomSize ); cdSizeGroup->addAction( d->actionDetermineSize ); d->actionLoadUserDefaults = K3b::createAction( this, i18n("User Defaults"), "", 0, this, SLOT(slotLoadUserDefaults()), d->actionCollection, "load_user_defaults" ); d->actionSaveUserDefaults = K3b::createAction( this, i18n("Save User Defaults"), "", 0, this, SLOT(slotSaveUserDefaults()), d->actionCollection, "save_user_defaults" ); KAction* dvdSizeInfoAction = K3b::createAction( this, i18n("Why 4.4 instead of 4.7?"), "", 0, this, SLOT(slotWhy44()), d->actionCollection, "why_44_gb" ); d->popup->addTitle( i18n("Show Size In") ); d->popup->addAction( d->actionShowMinutes ); d->popup->addAction( d->actionShowMegs ); d->popup->addSeparator(); d->popup->addAction( d->actionAuto ); if ( d->doc->supportedMediaTypes() & K3b::Device::MEDIA_CD_ALL ) { d->popup->addTitle( i18n("CD Size") ); d->popup->addAction( d->action74Min ); d->popup->addAction( d->action80Min ); d->popup->addAction( d->action100Min ); } if ( d->doc->supportedMediaTypes() & K3b::Device::MEDIA_DVD_ALL ) { d->popup->addTitle( i18n("DVD Size") ); d->popup->addAction( dvdSizeInfoAction ); d->popup->addAction( d->actionDvd4_7GB ); d->popup->addAction( d->actionDvdDoubleLayer ); } if ( d->doc->supportedMediaTypes() & K3b::Device::MEDIA_BD_ALL ) { d->popup->addTitle( i18n("Blu-Ray Size") ); d->popup->addAction( d->actionBD25 ); d->popup->addAction( d->actionBD50 ); } d->popup->addSeparator(); d->popup->addAction( d->actionCustomSize ); d->popup->addAction( d->actionDetermineSize ); d->popup->addSeparator(); d->popup->addAction( d->actionLoadUserDefaults ); d->popup->addAction( d->actionSaveUserDefaults ); connect( d->displayWidget, SIGNAL(contextMenu(const QPoint&)), this, SLOT(slotPopupMenu(const QPoint&)) ); } void K3b::FillStatusDisplay::showSize() { d->actionShowMegs->setChecked( true ); d->action74Min->setText( i18n("%1 MB",650) ); d->action80Min->setText( i18n("%1 MB",700) ); d->action100Min->setText( i18n("%1 MB",880) ); d->showTime = false; d->displayWidget->setShowTime(false); } void K3b::FillStatusDisplay::showTime() { d->actionShowMinutes->setChecked( true ); d->action74Min->setText( i18np("unused", "%1 minutes", 74) ); d->action80Min->setText( i18np("unused", "%1 minutes", 80) ); d->action100Min->setText( i18np("unused", "%1 minutes", 100) ); d->showTime = true; d->displayWidget->setShowTime(true); } void K3b::FillStatusDisplay::slotAutoSize() { slotMediumChanged( 0 ); } void K3b::FillStatusDisplay::slot74Minutes() { d->displayWidget->setCdSize( DEFAULT_CD_SIZE_74 ); } void K3b::FillStatusDisplay::slot80Minutes() { d->displayWidget->setCdSize( DEFAULT_CD_SIZE_80 ); } void K3b::FillStatusDisplay::slot100Minutes() { d->displayWidget->setCdSize( DEFAULT_CD_SIZE_100 ); } void K3b::FillStatusDisplay::slotDvd4_7GB() { d->displayWidget->setCdSize( DEFAULT_DVD_SIZE_4_4 ); } void K3b::FillStatusDisplay::slotDvdDoubleLayer() { d->displayWidget->setCdSize( DEFAULT_DVD_SIZE_8_0 ); } void K3b::FillStatusDisplay::slotBD25() { d->displayWidget->setCdSize( DEFAULT_BD_SIZE_25 ); } void K3b::FillStatusDisplay::slotBD50() { d->displayWidget->setCdSize( DEFAULT_BD_SIZE_50 ); } void K3b::FillStatusDisplay::slotWhy44() { QWhatsThis::showText( QCursor::pos(), i18n("

Why does K3b offer 4.4 GB and 8.0 GB instead of 4.7 and 8.5 like " "it says on the media?" "

A single layer DVD media has a capacity of approximately " "4.4 GB which equals 4.4*10243 bytes. Media producers just " "calculate with 1000 instead of 1024 for advertising reasons.
" "This results in 4.4*10243/10003 = 4.7 GB."), this ); } void K3b::FillStatusDisplay::slotCustomSize() { // allow the units to be translated QString gbS = i18n("gb"); QString mbS = i18n("mb"); QString minS = i18n("min"); // we certainly do not have BD- or HD-DVD-only projects QString defaultCustom; if( d->doc->supportedMediaTypes() & K3b::Device::MEDIA_CD_ALL ) { defaultCustom = d->showTime ? QString("74") + minS : QString("650") + mbS; } else { defaultCustom = QString("4%14%2").arg( KGlobal::locale()->decimalSymbol() ).arg( gbS ); } QRegExp rx( "(\\d+\\" + KGlobal::locale()->decimalSymbol() + "?\\d*)(" + gbS + "|" + mbS + "|" + minS + ")?" ); bool ok; QString size = KInputDialog::getText( i18n("Custom Size"), i18n("

Please specify the size of the media. Use suffixes gb,mb, " "and min for gigabytes, megabytes, and minutes" " respectively."), defaultCustom, &ok, this, new QRegExpValidator( rx, this ) ); if( ok ) { // determine size if( rx.exactMatch( size ) ) { QString valStr = rx.cap(1); if( valStr.endsWith( KGlobal::locale()->decimalSymbol() ) ) valStr += "0"; double val = KGlobal::locale()->readNumber( valStr, &ok ); if( ok ) { QString s = rx.cap(2); if( s == gbS ) val *= 1024*512; else if( s == mbS || (s.isEmpty() && !d->showTime) ) val *= 512; else val *= 60*75; d->displayWidget->setCdSize( (int)val ); update(); } } } } void K3b::FillStatusDisplay::slotMenuButtonClicked() { QSize size = d->popup->sizeHint(); slotPopupMenu( d->buttonMenu->mapToGlobal(QPoint(d->buttonMenu->width(), 0)) + QPoint(-1*size.width(), -1*size.height()) ); } void K3b::FillStatusDisplay::slotPopupMenu( const QPoint& p ) { d->popup->popup(p); } void K3b::FillStatusDisplay::slotDetermineSize() { bool canceled = false; K3b::Device::Device* dev = K3b::MediaSelectionDialog::selectMedium( d->doc->supportedMediaTypes(), - K3b::Device::STATE_EMPTY|K3b::Device::STATE_INCOMPLETE, - parentWidget(), - QString(), QString(), &canceled ); + K3b::Device::STATE_EMPTY|K3b::Device::STATE_INCOMPLETE, + K3b::Medium::CONTENT_ALL, + parentWidget(), + QString(), QString(), &canceled ); if( dev ) { K3b::Msf size = k3bappcore->mediaCache()->diskInfo( dev ).capacity(); if( size > 0 ) { d->displayWidget->setCdSize( size ); d->actionCustomSize->setChecked(true); update(); } else KMessageBox::error( parentWidget(), i18n("Medium is not empty.") ); } else if( !canceled ) KMessageBox::error( parentWidget(), i18n("No usable medium found.") ); } void K3b::FillStatusDisplay::slotLoadUserDefaults() { // load project specific values KConfigGroup c( KGlobal::config(), "default " + d->doc->typeString() + " settings" ); // defaults to megabytes d->showTime = c.readEntry( "show minutes", false ); d->displayWidget->setShowTime(d->showTime); d->actionShowMegs->setChecked( !d->showTime ); d->actionShowMinutes->setChecked( d->showTime ); long size = c.readEntry( "default media size", 0 ); switch( size ) { case 0: // automatic mode d->actionAuto->setChecked( true ); break; case DEFAULT_CD_SIZE_74: d->action74Min->setChecked( true ); break; case DEFAULT_CD_SIZE_80: d->action80Min->setChecked( true ); break; case DEFAULT_CD_SIZE_100: d->action100Min->setChecked( true ); break; case DEFAULT_DVD_SIZE_4_4: d->actionDvd4_7GB->setChecked( true ); break; case DEFAULT_DVD_SIZE_8_0: d->actionDvdDoubleLayer->setChecked( true ); break; case DEFAULT_BD_SIZE_25: d->actionBD25->setChecked( true ); break; case DEFAULT_BD_SIZE_50: d->actionBD50->setChecked( true ); break; default: d->actionCustomSize->setChecked( true ); break; } if( size == 0 ) { slotMediumChanged( 0 ); } else { d->displayWidget->setCdSize( size*60*75 ); } } void K3b::FillStatusDisplay::slotMediumChanged( K3b::Device::Device* ) { if( d->actionAuto->isChecked() ) { // // now search for a usable medium // if we find exactly one usable or multiple with the same size // we use that size // K3b::Medium autoSelectedMedium; QList devs = k3bcore->deviceManager()->burningDevices(); Q_FOREACH( K3b::Device::Device* dev, devs ) { const K3b::Medium medium = k3bappcore->mediaCache()->medium( dev ); if( ( medium.diskInfo().empty() || medium.diskInfo().appendable() || medium.diskInfo().rewritable() ) && ( medium.diskInfo().mediaType() & d->doc->supportedMediaTypes() ) ) { // We use a 10% margin to allow the user to fine-tune project sizes // However, if we have a bigger medium we always use that if ( ( double )d->doc->size() <= ( double )( medium.diskInfo().capacity().mode1Bytes() ) * 1.1 ) { // first usable medium if( !autoSelectedMedium.isValid() ) { autoSelectedMedium = medium; } else { // prefer the medium which can fit the whole doc if ( d->doc->length() <= medium.diskInfo().capacity() && d->doc->length() > autoSelectedMedium.diskInfo().capacity() ) { autoSelectedMedium = medium; } // roughly compare the sizes of the two usable media. If they match, carry on. else if( medium.diskInfo().capacity().lba()/75/60 != autoSelectedMedium.diskInfo().capacity().lba()/75/60 ) { // different usable media -> fallback autoSelectedMedium = K3b::Medium(); break; } } } } } if( autoSelectedMedium.isValid() ) { d->displayWidget->setCdSize( autoSelectedMedium.diskInfo().capacity().lba() ); } else { bool haveDVD = !k3bcore->deviceManager()->dvdWriter().isEmpty(); bool haveBD = !k3bcore->deviceManager()->blueRayWriters().isEmpty(); // default fallback // we do not have BD- or HD-DVD only projects if( ( d->doc->supportedMediaTypes() & K3b::Device::MEDIA_CD_ALL && d->doc->length().lba() <= DEFAULT_CD_SIZE_80 ) || !( d->doc->supportedMediaTypes() & ( K3b::Device::MEDIA_DVD_ALL|K3b::Device::MEDIA_BD_ALL ) ) || ( !haveDVD && !haveBD ) ) { d->displayWidget->setCdSize( DEFAULT_CD_SIZE_80 ); } else if ( haveDVD && ( ( d->doc->supportedMediaTypes() & K3b::Device::MEDIA_DVD_ALL && d->doc->length().lba() <= DEFAULT_DVD_SIZE_8_0 ) || !( d->doc->supportedMediaTypes() & K3b::Device::MEDIA_BD_ALL ) || !haveBD ) ) { if( d->doc->length().lba() > DEFAULT_DVD_SIZE_4_4 ) d->displayWidget->setCdSize( DEFAULT_DVD_SIZE_8_0 ); else d->displayWidget->setCdSize( DEFAULT_DVD_SIZE_4_4 ); } else if ( d->doc->length().lba() <= DEFAULT_BD_SIZE_25 ) { d->displayWidget->setCdSize( DEFAULT_BD_SIZE_25 ); } else { d->displayWidget->setCdSize( DEFAULT_BD_SIZE_50 ); } } } } void K3b::FillStatusDisplay::slotSaveUserDefaults() { // save project specific values KConfigGroup c( KGlobal::config(), "default " + d->doc->typeString() + " settings" ); c.writeEntry( "show minutes", d->showTime ); c.writeEntry( "default media size", d->actionAuto->isChecked() ? 0 : d->displayWidget->cdSize().lba() ); } void K3b::FillStatusDisplay::slotUpdateDisplay() { if( d->actionAuto->isChecked() ) { // // also update the medium list in case the docs size exceeds the capacity // slotMediumChanged( 0 ); } else { d->displayWidget->update(); } } void K3b::FillStatusDisplay::slotDocChanged() { // cache updates if( !d->updateTimer.isActive() ) { slotUpdateDisplay(); d->updateTimer.setSingleShot( false ); d->updateTimer.start( 500 ); } } bool K3b::FillStatusDisplay::event( QEvent* event ) { if ( event->type() == QEvent::ToolTip ) { QHelpEvent* he = ( QHelpEvent* )event; QPoint pos = he->pos(); QToolTip::showText( he->globalPos(), KIO::convertSize( d->doc->size() ) + " (" + KGlobal::locale()->formatNumber( d->doc->size(), 0 ) + "), " + d->doc->length().toString(false) + " " + i18n("min") + " (" + i18n("Right click for media sizes") + ")"); event->accept(); return true; } return QFrame::event( event ); } #include "k3bfillstatusdisplay.moc" diff --git a/src/projects/k3bstandardview.cpp b/src/projects/k3bstandardview.cpp index a75036131..f59cb0233 100644 --- a/src/projects/k3bstandardview.cpp +++ b/src/projects/k3bstandardview.cpp @@ -1,216 +1,218 @@ /* * * Copyright (C) 2009 Gustavo Pichorim Boiko * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bstandardview.h" #include "k3bdirproxymodel.h" #include #include #include #include #include #include K3b::StandardView::StandardView(K3b::Doc* doc, QWidget *parent ) : K3b::View(doc, parent) { m_dirProxy = new K3b::DirProxyModel(this); // --- setup GUI --------------------------------------------------- m_splitter = new QSplitter( this ); m_dirView = new QTreeView(m_splitter); m_fileView = new QTreeView(m_splitter); m_splitter->setStretchFactor( 0, 1 ); m_splitter->setStretchFactor( 1, 3 ); setMainWidget( m_splitter ); // --- setup Views --------------------------------------------------- // Dir panel m_dirView->setHeaderHidden(true); m_dirView->setAcceptDrops(true); m_dirView->setDragEnabled(true); m_dirView->setDragDropMode(QTreeView::DragDrop); m_dirView->setSelectionMode(QTreeView::SingleSelection); m_dirView->setModel(m_dirProxy); m_dirView->setContextMenuPolicy(Qt::CustomContextMenu); m_dirView->setAnimated(true); // File panel m_fileView->setAcceptDrops(true); m_fileView->setDragEnabled(true); m_fileView->setDragDropMode(QTreeView::DragDrop); m_fileView->setRootIsDecorated(false); m_fileView->setSelectionMode(QTreeView::ExtendedSelection); m_fileView->setContextMenuPolicy(Qt::CustomContextMenu); m_fileView->setAnimated(true); + // FIXME: make QHeaderView::Interactive the default but connect to model changes and call header()->resizeSections( QHeaderView::ResizeToContents ); + m_fileView->header()->setResizeMode( QHeaderView::ResizeToContents ); m_expanded = false; // connect signals/slots connect(m_dirView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(slotCustomContextMenu(const QPoint&))); connect(m_fileView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(slotCustomContextMenu(const QPoint&))); } K3b::StandardView::~StandardView() { } void K3b::StandardView::setModel(QAbstractItemModel *model) { m_dirProxy->setSourceModel(model); // hide the columns in the dir view for (int i = 1; i < model->columnCount(); ++i) m_dirView->setColumnHidden(i, true); m_fileView->setModel(model); // connect signals/slots // this signal is better to get connected before the setCurrentIndex is called, // so that it updates the file view connect(m_dirView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection&)), this, SLOT(slotCurrentDirChanged())); connect(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(slotItemsAdded())); // select the first item from the model if (m_dirProxy->rowCount() > 0) m_dirView->setCurrentIndex(m_dirProxy->index(0,0)); } void K3b::StandardView::setShowDirPanel(bool show) { m_dirView->setVisible(show); if (!show) m_fileView->setRootIndex(QModelIndex()); } void K3b::StandardView::contextMenuForSelection(const QModelIndexList &selectedIndexes, const QPoint &pos) { // do nothing in the default implementation (at least for now) qDebug() << "Gotta show a menu for " << selectedIndexes.count() << " items at " << pos; } QModelIndexList K3b::StandardView::currentSelection() const { return m_currentSelection; } QModelIndex K3b::StandardView::currentRoot() const { return m_fileView->rootIndex(); } void K3b::StandardView::setViewExpanded(bool expand) { m_expanded = expand; if (expand) m_fileView->expandAll(); } void K3b::StandardView::setAutoExpandDelay(int delay) { m_dirView->setAutoExpandDelay(delay); m_fileView->setAutoExpandDelay(delay); } void K3b::StandardView::slotCurrentDirChanged() { QModelIndexList indexes = m_dirView->selectionModel()->selectedRows(); QModelIndex currentDir; if (indexes.count()) currentDir = m_dirProxy->mapToSource(indexes.first()); // make the file view show only the child nodes of the currently selected // directory from dir view m_fileView->setRootIndex(currentDir); m_fileView->header()->resizeSections( QHeaderView::Stretch ); emit currentRootChanged( currentDir ); } void K3b::StandardView::slotCustomContextMenu(const QPoint &pos) { QModelIndexList selection; // detect which view emitted the signal QTreeView *view = dynamic_cast(sender()); // this should not happen, but just in case... if (!view) return; // if the signal was emitted by the dirview, we need to map the indexes to the // source model if (view == m_dirView) { foreach(QModelIndex index, view->selectionModel()->selectedRows()) selection.append( m_dirProxy->mapToSource(index) ); } else { selection = view->selectionModel()->selectedRows(); } m_currentSelection = selection; // call the context menu method so that derived classes can place customized // context menus contextMenuForSelection(selection, view->viewport()->mapToGlobal(pos)); } void K3b::StandardView::slotParentDir() { m_dirView->setCurrentIndex(m_dirProxy->mapFromSource(m_fileView->rootIndex().parent())); } void K3b::StandardView::slotRemoveSelectedIndexes() { QAbstractItemModel *model = m_fileView->model(); if (!model) return; // create a list of persistent model indexes to be able to remove all of them QList indexes; foreach(QModelIndex index, m_currentSelection) indexes.append( QPersistentModelIndex(index) ); // and now ask the indexes to be removed foreach(QPersistentModelIndex index, indexes) model->removeRow(index.row(), index.parent()); // clear the selection, just to make sure m_currentSelection.clear(); } void K3b::StandardView::slotRenameItem() { if (m_currentSelection.isEmpty()) return; if (m_dirView->hasFocus()) m_dirView->edit( m_dirProxy->mapFromSource(m_currentSelection.first()) ); else m_fileView->edit( m_currentSelection.first() ); } void K3b::StandardView::slotItemsAdded() { if (m_expanded) m_fileView->expandAll(); } #include "k3bstandardview.moc" diff --git a/src/rip/k3bvideocdinfo.cpp b/src/rip/k3bvideocdinfo.cpp index f8b2aae0d..afc8a5384 100644 --- a/src/rip/k3bvideocdinfo.cpp +++ b/src/rip/k3bvideocdinfo.cpp @@ -1,237 +1,238 @@ /* * * Copyright (C) 2003 Christian Kvasny +* Copyright (C) 2009 Sebastian Trueg * * This file is part of the K3b project. -* Copyright (C) 1998-2007 Sebastian Trueg +* Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3bvideocdinfo.h" #include #include #include #include #include #include #include #include #include #include K3b::VideoCdInfo::VideoCdInfo( QObject* parent ) : QObject( parent ) { m_process = 0L; m_isXml = false; } K3b::VideoCdInfo::~VideoCdInfo() { delete m_process; } void K3b::VideoCdInfo::cancelAll() { if ( m_process->isRunning() ) { m_process->disconnect( this ); m_process->kill(); } } void K3b::VideoCdInfo::info( const QString& device ) { if ( !k3bcore ->externalBinManager() ->foundBin( "vcdxrip" ) ) { kDebug() << "(K3b::VideoCdInfo::info) could not find vcdxrip executable"; emit infoFinished( false ); return ; } delete m_process; m_process = new K3b::Process(); *m_process << k3bcore ->externalBinManager() ->binPath( "vcdxrip" ); *m_process << "-q" << "--norip" << "-i" << device << "-o" << "-"; m_process->setSuppressEmptyLines( false ); - connect( m_process, SIGNAL( stderrLine( const QString& ) ), - this, SLOT( slotParseOutput( const QString& ) ) ); - connect( m_process, SIGNAL( receivedStdout( const QString& ) ), - this, SLOT( slotParseOutput( const QString& ) ) ); + connect( m_process, SIGNAL(stderrLine(QString)), + this, SLOT(slotParseOutput(QString)) ); + connect( m_process, SIGNAL(receivedStdout(QString)), + this, SLOT(slotParseOutput(QString)) ); connect( m_process, SIGNAL( finished( int, QProcess::ExitStatus ) ), this, SLOT( slotInfoFinished( int, QProcess::ExitStatus ) ) ); - if ( !m_process->start( K3Process::AllOutput ) ) { + if ( !m_process->start( KProcess::SeparateChannels ) ) { kDebug() << "(K3b::VideoCdInfo::info) could not start vcdxrip"; cancelAll(); emit infoFinished( false ); } } void K3b::VideoCdInfo::slotParseOutput( const QString& inp ) { if ( inp.contains( "" ) ) m_isXml = false; } void K3b::VideoCdInfo::slotInfoFinished( int exitCode, QProcess::ExitStatus exitStatus ) { if ( exitStatus == QProcess::NormalExit ) { // TODO: check the process' exitStatus() switch ( exitCode ) { case 0: break; default: cancelAll(); emit infoFinished( false ); return ; } } else { cancelAll(); emit infoFinished( false ); return ; } if ( m_xmlData.isEmpty() ) { emit infoFinished( false ); return ; } parseXmlData(); emit infoFinished( true ); } void K3b::VideoCdInfo::parseXmlData() { QDomDocument xml_doc; QDomElement xml_root; m_Result.xmlData = m_xmlData; xml_doc.setContent( m_xmlData ); xml_root = xml_doc.documentElement(); m_Result.type = xml_root.attribute( "class" ); m_Result.version = xml_root.attribute( "version" ); for ( QDomNode node = xml_root.firstChild(); !node.isNull(); node = node.nextSibling() ) { QDomElement el = node.toElement(); QString tagName = el.tagName().toLower(); if ( tagName == "pvd" ) { for ( QDomNode snode = node.firstChild(); !snode.isNull(); snode = snode.nextSibling() ) { QDomElement sel = snode.toElement(); QString pvdElement = sel.tagName().toLower(); QString pvdElementText = sel.text(); if ( pvdElement == "volume-id" ) m_Result.volumeId = pvdElementText; } } else if ( tagName == "sequence-items" ) { for ( QDomNode snode = node.firstChild(); !snode.isNull(); snode = snode.nextSibling() ) { QDomElement sel = snode.toElement(); QString seqElement = sel.tagName().toLower(); m_Result.addEntry( K3b::VideoCdInfoResultEntry( sel.attribute( "src" ), sel.attribute( "id" ) ), K3b::VideoCdInfoResult::SEQUENCE ); } } else if ( tagName == "segment-items" ) { for ( QDomNode snode = node.firstChild(); !snode.isNull(); snode = snode.nextSibling() ) { QDomElement sel = snode.toElement(); QString seqElement = sel.tagName().toLower(); m_Result.addEntry( K3b::VideoCdInfoResultEntry( sel.attribute( "src" ), sel.attribute( "id" ) ), K3b::VideoCdInfoResult::SEGMENT ); } } else { kDebug() << QString( "(K3b::VideoCdInfo::parseXmlData) tagName '%1' not used" ).arg( tagName ); } } } const K3b::VideoCdInfoResult& K3b::VideoCdInfo::result() const { return m_Result; } const K3b::VideoCdInfoResultEntry& K3b::VideoCdInfoResult::entry( int number, int type ) const { switch ( type ) { case K3b::VideoCdInfoResult::FILE: if ( number >= m_fileEntry.count() ) return m_emptyEntry; return m_fileEntry[ number ]; case K3b::VideoCdInfoResult::SEGMENT: if ( number >= m_segmentEntry.count() ) return m_emptyEntry; return m_segmentEntry[ number ]; case K3b::VideoCdInfoResult::SEQUENCE: if ( number >= m_sequenceEntry.count() ) return m_emptyEntry; return m_sequenceEntry[ number ]; default: kDebug() << "(K3b::VideoCdInfoResult::entry) not supported entrytype."; } return m_emptyEntry; } void K3b::VideoCdInfoResult::addEntry( const K3b::VideoCdInfoResultEntry& entry, int type ) { switch ( type ) { case K3b::VideoCdInfoResult::FILE: m_fileEntry.append( entry ); break; case K3b::VideoCdInfoResult::SEGMENT: m_segmentEntry.append( entry ); break; case K3b::VideoCdInfoResult::SEQUENCE: m_sequenceEntry.append( entry ); break; default: kDebug() << "(K3b::VideoCdInfoResult::addEntry) not supported entrytype."; } } int K3b::VideoCdInfoResult::foundEntries( int type ) const { switch ( type ) { case K3b::VideoCdInfoResult::FILE: return m_fileEntry.count(); case K3b::VideoCdInfoResult::SEGMENT: return m_segmentEntry.count(); case K3b::VideoCdInfoResult::SEQUENCE: return m_sequenceEntry.count(); default: kDebug() << "(K3b::VideoCdInfoResult::addEntry) not supported entrytype."; } return 0; } #include "k3bvideocdinfo.moc"