diff --git a/app/batchextract.cpp b/app/batchextract.cpp index 7571b77b..0600d039 100644 --- a/app/batchextract.cpp +++ b/app/batchextract.cpp @@ -1,251 +1,252 @@ /* * ark -- archiver for the KDE project * * Copyright (C) 2008 Harald Hvaal * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "batchextract.h" #include "kerfuffle/extractiondialog.h" #include #include #include #include #include #include #include #include #include #include BatchExtract::BatchExtract() : autoSubfolders(false), destinationFolder(), m_preservePaths(true) { + setCapabilities(KJob::Killable); } BatchExtract::~BatchExtract() { kDebug(1601) << "Dying"; KIO::getJobTracker()->unregisterJob(this); } void BatchExtract::addExtraction(Kerfuffle::Archive* archive,bool preservePaths, QString destinationFolder) { kDebug( 1601 ); QString autoDestination = destinationFolder; if (autoSubfolders) { if (!archive->isSingleFolderArchive()) { QString subfolder = archive->subfolderName(); kDebug( 1601 ) << "Creating subfolder" << subfolder << "under" << destinationFolder; QDir dest(destinationFolder); dest.mkdir(subfolder); autoDestination = destinationFolder + '/' + subfolder; } } Kerfuffle::ExtractionOptions options; if (preservePaths) options["PreservePaths"] = true; Kerfuffle::ExtractJob *job = archive->copyFiles( QVariantList(), //extract all files autoDestination, //extract to folder options ); connect(job, SIGNAL(userQuery(Query*)), this, SLOT(slotUserQuery(Query*))); kDebug( 1601 ) << QString("Registering job from archive %1, to %2, preservePaths %3").arg(archive->fileName()).arg(autoDestination).arg(preservePaths); addSubjob(job); fileNames[job] = qMakePair(archive->fileName(), destinationFolder); connect(job, SIGNAL(percent(KJob*, unsigned long)), this, SLOT(forwardProgress(KJob *, unsigned long))); } void BatchExtract::slotUserQuery(Query *query) { query->execute(); } void BatchExtract::setAutoSubfolder(bool value) { autoSubfolders = value; } void BatchExtract::start() { kDebug( 1601 ); if (!subfolder.isEmpty()) { kDebug( 1601 ) << "Creating subfolder" << subfolder; QDir dest(destinationFolder); dest.mkpath(subfolder); destinationFolder += '/' + subfolder; } foreach (Kerfuffle::Archive *archive, inputs) { QString finalDestination; if (destinationFolder.isEmpty()) { finalDestination = QDir::currentPath(); } else { finalDestination = destinationFolder; } addExtraction(archive, m_preservePaths, finalDestination); } KIO::getJobTracker()->registerJob(this); emit description(this, "Extracting file...", qMakePair(i18n("Source archive"), fileNames.value(subjobs().at(0)).first), qMakePair(i18n("Destination"), fileNames.value(subjobs().at(0)).second) ); initialJobCount = subjobs().size(); if (!initialJobCount) return; kDebug( 1601 ) << "Starting first job"; subjobs().at(0)->start(); } void BatchExtract::slotResult( KJob *job ) { kDebug( 1601 ); KCompositeJob::slotResult(job); if ( job->error() ) { kDebug(1601) << "There was en error, " << job->errorText(); //TODO: why does this exit immediately KMessageBox::error( NULL, job->errorText()); emitResult(); return; } if (!subjobs().size()) { kDebug(1601) << "Finished, emitting the result"; emitResult(); } else { kDebug(1601) << "Starting the next job"; emit description(this, "Extracting file...", qMakePair(i18n("Source archive"), fileNames.value(subjobs().at(0)).first), qMakePair(i18n("Destination"), fileNames.value(subjobs().at(0)).second) ); subjobs().at(0)->start(); } } void BatchExtract::forwardProgress(KJob *job, unsigned long percent) { Q_UNUSED(job); int jobPart = 100 / initialJobCount; setPercent( jobPart * (initialJobCount - subjobs().size()) + percent / initialJobCount ); } bool BatchExtract::addInput( const KUrl& url ) { kDebug( 1601 ); Kerfuffle::Archive *archive = Kerfuffle::factory(url.path()); if (archive == NULL) return false; inputs << archive; return true; } void BatchExtract::setDestinationFolder(QString folder) { if (!folder.isEmpty()) destinationFolder = folder; } void BatchExtract::setPreservePaths(bool value) { m_preservePaths = value; } void BatchExtract::setSubfolder(QString subfolder) { this->subfolder = subfolder; } bool BatchExtract::showExtractDialog() { kDebug( 1601 ); Kerfuffle::ExtractionDialog dialog(NULL); if (inputs.size() > 1) { dialog.batchModeOption(); } if (destinationFolder.isEmpty()) dialog.setCurrentUrl(QDir::currentPath()); else dialog.setCurrentUrl(destinationFolder); dialog.setAutoSubfolder(autoSubfolders); dialog.setPreservePaths(m_preservePaths); if (subfolder.isEmpty() && inputs.size() == 1) { if (inputs.at(0)->isSingleFolderArchive()) { dialog.setSingleFolderArchive(true); } dialog.setSubfolder(inputs.at(0)->subfolderName()); } else { dialog.setSubfolder(subfolder); } bool ret = dialog.exec(); if (!ret) return false; setDestinationFolder(dialog.destinationDirectory().path()); if (dialog.extractToSubfolder()) { subfolder = dialog.subfolder(); } autoSubfolders = dialog.autoSubfolders(); m_preservePaths = dialog.preservePaths(); return true; } #include diff --git a/kerfuffle/archiveinterface.cpp b/kerfuffle/archiveinterface.cpp index 0472d492..fd97c393 100644 --- a/kerfuffle/archiveinterface.cpp +++ b/kerfuffle/archiveinterface.cpp @@ -1,165 +1,183 @@ /* * Copyright (c) 2007 Henrique Pinto * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "archiveinterface.h" #include "observer.h" #include #include #include #include namespace Kerfuffle { ReadOnlyArchiveInterface::ReadOnlyArchiveInterface( const QString & filename, QObject *parent ) : QObject( parent ), m_filename( filename ), m_waitForFinishedSignal(false) { } ReadOnlyArchiveInterface::~ReadOnlyArchiveInterface() { } const QString& ReadOnlyArchiveInterface::filename() const { return m_filename; } bool ReadOnlyArchiveInterface::isReadOnly() const { return true; } bool ReadOnlyArchiveInterface::open() { return true; } void ReadOnlyArchiveInterface::setPassword(QString password) { m_password = password; } const QString& ReadOnlyArchiveInterface::password() const { return m_password; } void ReadOnlyArchiveInterface::error( const QString & message, const QString & details ) { foreach( ArchiveObserver *observer, m_observers ) { observer->onError( message, details ); } } void ReadOnlyArchiveInterface::entry( const ArchiveEntry & archiveEntry ) { foreach( ArchiveObserver *observer, m_observers ) { observer->onEntry( archiveEntry ); } } void ReadOnlyArchiveInterface::entryRemoved( const QString & path ) { foreach( ArchiveObserver *observer, m_observers ) { observer->onEntryRemoved( path ); } } void ReadOnlyArchiveInterface::progress( double p ) { foreach( ArchiveObserver *observer, m_observers ) { observer->onProgress( p ); } } void ReadOnlyArchiveInterface::info( const QString& info) { foreach( ArchiveObserver *observer, m_observers ) { observer->onInfo( info); } } void ReadOnlyArchiveInterface::finished(bool result) { foreach( ArchiveObserver *observer, m_observers ) { observer->onFinished( result ); } } + bool ReadOnlyArchiveInterface::doKill() + { + //default implementation + return false; + } + + bool ReadOnlyArchiveInterface::doSuspend() + { + //default implementation + return false; + } + + bool ReadOnlyArchiveInterface::doResume() + { + //default implementation + return false; + } + void ReadOnlyArchiveInterface::userQuery(Query *query) { foreach( ArchiveObserver *observer, m_observers ) { observer->onUserQuery( query ); } } void ReadOnlyArchiveInterface::registerObserver( ArchiveObserver *observer ) { m_observers.append( observer ); } void ReadOnlyArchiveInterface::removeObserver( ArchiveObserver *observer ) { m_observers.removeAll( observer ); } ReadWriteArchiveInterface::ReadWriteArchiveInterface( const QString & filename, QObject *parent ) : ReadOnlyArchiveInterface( filename, parent ) { } ReadWriteArchiveInterface::~ReadWriteArchiveInterface() { } void ReadOnlyArchiveInterface::setWaitForFinishedSignal(bool value) { m_waitForFinishedSignal = value; } bool ReadWriteArchiveInterface::isReadOnly() const { QFileInfo fileInfo( filename() ); if ( fileInfo.exists() ) { return ! fileInfo.isWritable(); } else { return !fileInfo.dir().exists(); // TODO: Should also check if we can create a file in that directory } } } // namespace Kerfuffle #include "archiveinterface.moc" diff --git a/kerfuffle/archiveinterface.h b/kerfuffle/archiveinterface.h index fd8a1e65..0e622dc7 100644 --- a/kerfuffle/archiveinterface.h +++ b/kerfuffle/archiveinterface.h @@ -1,142 +1,146 @@ /* * Copyright (c) 2007 Henrique Pinto * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef ARCHIVEINTERFACE_H #define ARCHIVEINTERFACE_H #include #include #include #include "archive.h" #include "queries.h" #include "kerfuffle_export.h" #include #include #include namespace Kerfuffle { class ArchiveObserver; class KERFUFFLE_EXPORT ReadOnlyArchiveInterface: public QObject { Q_OBJECT public: explicit ReadOnlyArchiveInterface( const QString & filename, QObject *parent = 0 ); virtual ~ReadOnlyArchiveInterface(); /** Return the filename of currently handled archive. */ const QString& filename() const; virtual bool isReadOnly() const; void KDE_NO_EXPORT registerObserver( ArchiveObserver *observer ); void KDE_NO_EXPORT removeObserver( ArchiveObserver *observer ); virtual bool open(); /** * List archive contents. * This runs the process of reading archive contents. * When subclassing, you can block as long as you need, the function runs * in its own thread. * @returns whether the listing succeeded. * @note If returning false, make sure to call error() beforewards to notify * the user of the error condition. */ virtual bool list() = 0; void setPassword(QString password); /** * Extract files from archive. * Globally recognized extraction options: * @li PreservePaths - preserve file paths (extract flat if false) * @li RootNode - node in the archive which will correspond to the @arg destinationDirectory * When subclassing, you can block as long as you need, the function runs * in its own thread. * @returns whether the listing succeeded. * @note If returning false, make sure to call error() beforewards to notify * the user of the error condition. */ virtual bool copyFiles( const QList & files, const QString & destinationDirectory, ExtractionOptions options ) = 0; bool waitForFinishedSignal() { return m_waitForFinishedSignal; } void finished(bool result); + virtual bool doKill(); + virtual bool doSuspend(); + virtual bool doResume(); + protected: /** * Communicate an error. * Sets message of error for user to read and understand. It will be * displayed once the job has returned false. */ void error( const QString & message, const QString & details = QString() ); /** * Notify observers of a new archive entry. * The interface should call this function whenever a new archive entry * is read. @note Remember that directories should have filename ending with /. */ void entry( const ArchiveEntry & archiveEntry ); void progress( double ); void info( const QString& info); void entryRemoved( const QString& path ); const QString& password() const; void userQuery( Query* ); /** * Setting this option to true will not exit the tread with the * exit of the various functions, but rather when finished(bool) is * called. Doing this one can use the event loop easily while doing * the operation. */ void setWaitForFinishedSignal(bool value); private: QList m_observers; QString m_filename; QString m_password; bool m_waitForFinishedSignal; }; class KERFUFFLE_EXPORT ReadWriteArchiveInterface: public ReadOnlyArchiveInterface { Q_OBJECT public: explicit ReadWriteArchiveInterface( const QString & filename, QObject *parent = 0 ); virtual ~ReadWriteArchiveInterface(); virtual bool isReadOnly() const; //see archive.h for a list of what the compressionoptions might //contain virtual bool addFiles( const QStringList & files, const CompressionOptions& options ) = 0; virtual bool deleteFiles( const QList & files ) = 0; }; } // namespace Kerfuffle #endif // ARCHIVEINTERFACE_H diff --git a/kerfuffle/cliinterface.cpp b/kerfuffle/cliinterface.cpp index 71d673ae..cc3b0fb8 100644 --- a/kerfuffle/cliinterface.cpp +++ b/kerfuffle/cliinterface.cpp @@ -1,487 +1,509 @@ /* * ark -- archiver for the KDE project * * Copyright (C) 2009 Harald Hvaal * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "cliinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Kerfuffle { CliInterface::CliInterface( const QString& filename, QObject *parent) : ReadWriteArchiveInterface(filename, parent), m_process(NULL), m_loop(NULL) { //because this interface uses the event loop setWaitForFinishedSignal(true); } void CliInterface::cacheParameterList() { m_param = parameterList(); Q_ASSERT(m_param.contains(ExtractProgram)); Q_ASSERT(m_param.contains(ListProgram)); Q_ASSERT(m_param.contains(PreservePathSwitch)); Q_ASSERT(m_param.contains(RootNodeSwitch)); Q_ASSERT(m_param.contains(FileExistsExpression)); Q_ASSERT(m_param.contains(FileExistsInput)); } CliInterface::~CliInterface() { } bool CliInterface::list() { cacheParameterList(); m_mode = List; bool ret = findProgramAndCreateProcess(m_param.value(ListProgram).toString()); if (!ret) { finished(false); return false; } QStringList args = m_param.value(ListArgs).toStringList(); substituteListVariables(args); executeProcess(m_program, args); return true; } bool CliInterface::copyFiles( const QList & files, const QString & destinationDirectory, ExtractionOptions options ) { kDebug( 1601) ; cacheParameterList(); m_mode = Copy; bool ret = findProgramAndCreateProcess(m_param.value(ExtractProgram).toString()); if (!ret) { finished(false); return false; } //start preparing the argument list QStringList args = m_param.value(ExtractArgs).toStringList(); //now replace the various elements in the list for (int i = 0; i < args.size(); ++i) { QString argument = args.at(i); kDebug(1601) << "Processing argument " << argument; if (argument == "$Archive") { args[i] = filename(); } if (argument == "$PreservePathSwitch") { QStringList replacementFlags = m_param.value(PreservePathSwitch).toStringList(); Q_ASSERT(replacementFlags.size() == 2); bool preservePaths = options.value("PreservePaths").toBool(); QString theReplacement; if (preservePaths) theReplacement = replacementFlags.at(0); else theReplacement = replacementFlags.at(1); if (theReplacement.isEmpty()) { args.removeAt(i); --i; //decrement to compensate for the variable we removed } else //but in this case we don't have to decrement, we just //replace it args[i] = theReplacement; } if (argument == "$RootNodeSwitch") { //if the RootNodeSwitch argument has been added, we at least //assume that the format of the switch has been added as well Q_ASSERT(m_param.contains(RootNodeSwitch)); //we will decrement i afterwards args.removeAt(i); QString rootNode; if (options.contains("RootNode")) { rootNode = options.value("RootNode").toString(); kDebug(1601) << "Set root node " << rootNode; } if (!rootNode.isEmpty()) { QStringList theSwitch = m_param.value(RootNodeSwitch).toStringList(); for (int j = 0; j < theSwitch.size(); ++j) { //get the argument part QString newArg = theSwitch.at(j); //substitute the $Path newArg.replace("$Path", rootNode); //put it in the arg list args.insert(i+j, newArg); ++i; } } --i; //decrement to compensate for the variable we replaced } if (argument == "$Files") { args.removeAt(i); for (int j = 0; j < files.count(); ++j) { args.insert(i + j, files.at(j).toString()); ++i; } --i; } } QDir::setCurrent(destinationDirectory); executeProcess(m_program, args); return true; } bool CliInterface::addFiles( const QStringList & files, const CompressionOptions& options ) { cacheParameterList(); m_mode = Add; bool ret = findProgramAndCreateProcess(m_param.value(ListProgram).toString()); if (!ret) { finished(false); return false; } QString globalWorkdir = options.value("GlobalWorkDir").toString(); if (!globalWorkdir.isEmpty()) { kDebug( 1601 ) << "GlobalWorkDir is set, changing dir to " << globalWorkdir; QDir::setCurrent(globalWorkdir); } //start preparing the argument list QStringList args = m_param.value(AddArgs).toStringList(); //now replace the various elements in the list for (int i = 0; i < args.size(); ++i) { QString argument = args.at(i); kDebug(1601) << "Processing argument " << argument; if (argument == "$Archive") { args[i] = filename(); } if (argument == "$Files") { args.removeAt(i); for (int j = 0; j < files.count(); ++j) { QString relativeName = QDir::current().relativeFilePath(files.at(j)); args.insert(i + j, relativeName); ++i; } --i; } } executeProcess(m_program, args); return true; } bool CliInterface::deleteFiles( const QList & files ) { cacheParameterList(); m_mode = Delete; return false; } bool CliInterface::createProcess() { kDebug(1601); //if (m_process) //return false; m_process = new KProcess; m_process->setOutputChannelMode( KProcess::MergedChannels ); connect( m_process, SIGNAL( started() ), SLOT( started() ), Qt::DirectConnection ); connect( m_process, SIGNAL( readyReadStandardOutput() ), SLOT( readStdout() ), Qt::DirectConnection ); connect( m_process, SIGNAL( finished( int, QProcess::ExitStatus ) ), SLOT( processFinished( int, QProcess::ExitStatus ) ) , Qt::DirectConnection); if (QMetaType::type("QProcess::ExitStatus") == 0) qRegisterMetaType("QProcess::ExitStatus"); return true; } bool CliInterface::executeProcess(const QString& path, const QStringList & args) { kDebug( 1601 ) << "Executing " << path << args; Q_ASSERT(!path.isEmpty()); m_process->setProgram( path, args ); m_process->setNextOpenMode( QIODevice::ReadWrite | QIODevice::Unbuffered ); m_process->start(); /* if (!m_errorMessages.isEmpty()) { error(m_errorMessages.join("\n")); return false; } else if (ret && !m_userCancelled) { error(i18n("Unknown error when extracting files")); return false; } else { return true; } */ return true; } void CliInterface::started() { //m_state = 0; m_userCancelled = false; } void CliInterface::processFinished( int exitCode, QProcess::ExitStatus exitStatus) { kDebug(1601); progress(1.0); finished(true); m_process = NULL; return; if ( m_loop ) { m_loop->exit( exitStatus == QProcess::CrashExit ? 1 : 0 ); } } void CliInterface::readStdout() { //a quick note for any new hackers: Yes, this function is not //very pretty, but it has become like this for reasons. You are //very welcome to find a better implementation for this, but //please consider the following points: // //- standard output comes in unpredictable chunks, this is why //you can never know if the last part of the output is a complete line or not //- because of eventloop magic and the process running in its //own thread, the readStdOut might be called at any time, even //while it's busy processing the last call (this last case //happens when another eventloop is created somewhere (eg //Query::execute) and the readyReadStandardOutput signal is //picked up there) //- console applications are not really consistent about what //characters they send out (newline, backspace, carriage return, //etc), so keep in mind that this function is supposed to handle //all those special cases and be the lowest common denominator if ( !m_process ) { return; } //if another function is already accessing this function, then we //assume that it will finish processing also the lines we just added. static QMutex thisFuncMutex; bool tryLock = thisFuncMutex.tryLock(); if (!tryLock) { return; } QByteArray dd = m_process->readAllStandardOutput(); //for simplicity, we replace all carriage return characters to newlines dd.replace('\015', '\n'); //same thing with backspaces. //TODO: whether this is a safe assumption or not needs to be //determined dd.replace('\010', '\n'); m_stdOutData += dd; //if there is no newline, we leave the data like this for now. if (!m_stdOutData.contains('\n')) { - kDebug(1601) << "No new line, we leave it like this for now"; + //kDebug(1601) << "No new line, we leave it like this for now"; thisFuncMutex.unlock(); return; } QList list = m_stdOutData.split('\n'); //because the last line might be incomplete we leave it for now QByteArray lastLine = list.takeLast(); m_stdOutData = lastLine; foreach( const QByteArray& line, list) { if (line.isEmpty()) continue; if ((m_mode == Copy || m_mode == Add) && m_param.contains(CaptureProgress) && m_param.value(CaptureProgress).toBool()) { //read the percentage int pos = line.indexOf('%'); if (pos != -1 && pos > 1) { int percentage = line.mid(pos - 2, 2).toInt(); progress(float(percentage) / 100); continue; } } if (m_mode == Copy) { if (checkForFileExistsMessage(line)) continue; } if (m_mode == List) { readListLine(line); continue; } } thisFuncMutex.unlock(); QTimer::singleShot(0, this, SLOT(readStdout())); } bool CliInterface::findProgramAndCreateProcess(const QString& program) { m_program = KStandardDirs::findExe( program ); bool ret = !m_program.isEmpty(); if (!ret) { error(i18n("Failed to locate program '%1' in PATH.", program)); return false; } ret = createProcess(); if (!ret) { error(i18n("Found program '%1', but failed to initalise the process.", program)); return false; } return true; } bool CliInterface::checkForFileExistsMessage(const QString& line) { if (m_existsPattern.isEmpty()) { m_existsPattern.setPattern(m_param.value(FileExistsExpression).toString()); } if (m_existsPattern.indexIn(line) != -1) { kDebug(1601) << "Detected file existing!! Filename " << m_existsPattern.cap(1); Kerfuffle::OverwriteQuery query(QDir::current().path() + "/" + m_existsPattern.cap(1)); query.setNoRenameMode(true); userQuery(&query); kDebug(1601) << "Waiting response"; query.waitForResponse(); kDebug(1601) << "Finished response"; QString responseToProcess; QStringList choices = m_param.value(FileExistsInput).toStringList(); if (query.responseOverwrite()) responseToProcess = choices.at(0); else if (query.responseSkip()) responseToProcess = choices.at(1); else if (query.responseOverwriteAll()) responseToProcess = choices.at(2); else if (query.responseCancelled()) responseToProcess = choices.at(3); else if (query.responseAutoSkip()) responseToProcess = choices.at(4); Q_ASSERT(!responseToProcess.isEmpty()); responseToProcess += '\n'; kDebug(1601) << "Writing " << responseToProcess; m_process->write(responseToProcess.toLocal8Bit()); return true; } return false; } + bool CliInterface::doKill() + { + if (m_process) { + m_process->terminate(); + if (!m_process->waitForFinished()) + m_process->kill(); + m_process->waitForFinished(); + return true; + } + return false; + } + + bool CliInterface::doSuspend() + { + return false; + } + + bool CliInterface::doResume() + { + return false; + } + void CliInterface::substituteListVariables(QStringList& params) { for (int i = 0; i < params.size(); ++i) { QString parameter = params.at(i); if (parameter == "$Archive") { params[i] = filename(); } } } } #include "cliinterface.moc" diff --git a/kerfuffle/cliinterface.h b/kerfuffle/cliinterface.h index f7568a82..a2fc8871 100644 --- a/kerfuffle/cliinterface.h +++ b/kerfuffle/cliinterface.h @@ -1,234 +1,238 @@ /* * ark -- archiver for the KDE project * * Copyright (C) 2009 Harald Hvaal * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef _CLIINTERFACE_H_ #define _CLIINTERFACE_H_ #include "archiveinterface.h" #include "kerfuffle_export.h" #include #include #include namespace Kerfuffle { enum CliInterfaceExtractOptions { }; enum CliInterfaceParameters { ///////////////[ COMMON ]///////////// /** * Bool (default false) * Will look for the %-sign in the stdout while working, in the form of * (2%, 14%, 35%, etc etc), and report progress based upon this */ CaptureProgress = 0, ///////////////[ LIST ]///////////// /** * QString * The name to the program that will handle listing of this * archive (eg "rar"). Will be searched for in PATH */ ListProgram, /** * QStringList * The arguments that are passed to the program above for * listing the archive. Special strings that will be * substituted: * $Archive - the path of the archive */ ListArgs, ///////////////[ EXTRACT ]///////////// /** * QString * The name to the program that will handle extracting of this * archive (eg "rar"). Will be searched for in PATH */ ExtractProgram, /** * QStringList * The arguments that are passed to the program above for * extracting the archive. Special strings that will be * substituted: * $Archive - the path of the archive * $Files - the files selected to be extracted, if any * $PreservePathSwitch - the flag for extracting with full paths * $RootNodeSwitch - the internal work dir in the archive (for example * when the user has dragged a folder from the archive and wants it * extracted relative to it) */ ExtractArgs, /** * Bool (default false) * When passing directories to the extract program, do not * include trailing slashes * e.g. if the user selected "foo/" and "foo/bar" in the gui, the * paths "foo" and "foo/bar" will be sent to the program. */ NoTrailingSlashes, /** * QStringList * This should be a qstringlist with either two elements. The first * string is what PreservePathSwitch in the ExtractArgs will be replaced * with if PreservePath is True/enabled. The second is for the disabled * case. An empty string means that the argument will not be used in * that case. * Example: for rar, "x" means extract with full paths, and "e" means * extract without full paths. in this case we will use the stringlist * ("x", "e"). Or, for another format that might use the switch * "--extractFull" for preservePaths, and nothing otherwise: we use the * stringlist ("--preservePaths", "") */ PreservePathSwitch, /** * QStringList (default empty) * The format of the root node switch. The variable $Path will be * substituted for the path string. * Example: ("--internalPath=$Path) * or ("--path", "$Path") */ RootNodeSwitch, /** * QString * This is a regexp, defining how to recognize a "File already exists" * prompt when extracting. It should have one captured string, which is * the filename of the file/folder that already exists. */ FileExistsExpression, /** * int * This sets on what output channel the FileExistsExpression regex * should be applied on, in other words, on what stream the "file * exists" output will appear in. Values accepted: * 0 - Standard error, stderr (default) * 1 - Standard output, stdout */ FileExistsMode, /** * QStringList * The various responses that can be supplied as a response to the * "file exists" prompt. The various items are to be supplied in the * following order: * index 0 - Yes (overwrite) * index 1 - No (skip/do not overwrite) * index 2 - All (overwrite all) * index 3 - Cancel operation * index 4 - Do not overwrite any files (autoskip) */ FileExistsInput, ///////////////[ DELETE ]///////////// /** * QString * The name to the program that will handle deleting of elements in this * archive format (eg "rar"). Will be searched for in PATH */ DeleteProgram, /** * QStringList * The arguments that are passed to the program above for * deleting from the archive. Special strings that will be * substituted: * $Archive - the path of the archive * $Files - the files selected to be deleted */ DeleteArgs, /** * QString * The name to the program that will handle adding in this * archive format (eg "rar"). Will be searched for in PATH */ ///////////////[ ADD ]///////////// AddProgram, /** * QStringList * The arguments that are passed to the program above for * adding to the archive. Special strings that will be * substituted: * $Archive - the path of the archive * $Files - the files selected to be added */ AddArgs }; typedef QHash ParameterList; class KERFUFFLE_EXPORT CliInterface : public ReadWriteArchiveInterface { Q_OBJECT public: enum OperationMode { List, Copy, Add, Delete }; OperationMode m_mode; explicit CliInterface( const QString& filename, QObject *parent = 0); virtual ~CliInterface(); virtual bool list(); virtual bool copyFiles( const QList & files, const QString & destinationDirectory, ExtractionOptions options ); virtual bool addFiles( const QStringList & files, const CompressionOptions& options ); virtual bool deleteFiles( const QList & files ); virtual ParameterList parameterList() const = 0; virtual bool readListLine(QString line) = 0; private: bool findProgramAndCreateProcess(const QString& program); void substituteCopyVariables(QStringList& params, const QList & files, const QString & destinationDirectory, ExtractionOptions options); void substituteListVariables(QStringList& params); bool createProcess(); bool executeProcess(const QString& path, const QStringList & args); void cacheParameterList(); bool checkForFileExistsMessage(const QString& line); + bool doKill(); + bool doSuspend(); + bool doResume(); + QByteArray m_stdOutData; bool m_userCancelled; QRegExp m_existsPattern; KProcess *m_process; QString m_program; QEventLoop *m_loop; ParameterList m_param; private slots: void started(); void readStdout(); void processFinished( int exitCode, QProcess::ExitStatus exitStatus ); }; } #endif /* _CLIINTERFACE_H_ */ diff --git a/kerfuffle/jobs.cpp b/kerfuffle/jobs.cpp index 4944bb08..ffa1098e 100644 --- a/kerfuffle/jobs.cpp +++ b/kerfuffle/jobs.cpp @@ -1,237 +1,246 @@ /* * Copyright (c) 2007 Henrique Pinto * Copyright (c) 2008 Harald Hvaal * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "jobs.h" #include "threading.h" #include #include #include #include #include //#define KERFUFFLE_NOJOBTHREADING namespace Kerfuffle { Job::Job(ReadOnlyArchiveInterface *interface, QObject *parent) : KJob(NULL), m_interface(interface) { static bool onlyOnce = false; if (!onlyOnce) { qRegisterMetaType >("QPair"); onlyOnce = true; } - setCapabilities(KJob::Killable | KJob::Suspendable); + setCapabilities(KJob::Killable); } void Job::start() { #ifdef KERFUFFLE_NOJOBTHREADING QTimer::singleShot(0, this, SLOT(doWork())); #else ThreadExecution *thread = new ThreadExecution(this); //we want the event handling to happen in the work thread to avoid //unsafe data access due to events while work is being done moveToThread(thread); thread->start(); #endif } void Job::onError( const QString & message, const QString & details ) { setError(1); setErrorText(message); } void Job::onEntry( const ArchiveEntry & archiveEntry ) { emit newEntry( archiveEntry ); } void Job::onProgress( double value ) { setPercent( static_cast( 100.0*value ) ); } void Job::onInfo( const QString& info ) { emit infoMessage(this, info); } void Job::onEntryRemoved( const QString & path ) { emit entryRemoved( path ); } void Job::onFinished(bool result) { kDebug(1601); m_interface->removeObserver( this ); setError(!result); emitResult(); #ifndef KERFUFFLE_NOJOBTHREADING moveToThread(QApplication::instance()->thread()); #endif } void Job::onUserQuery(Query *query) { emit userQuery(query); } + bool Job::doKill() + { + kDebug(1601); + bool ret = m_interface->doKill(); + if (!ret) + kDebug(1601) << "Killing does not seem to be supported here."; + return ret; + } + ListJob::ListJob( ReadOnlyArchiveInterface *interface, QObject *parent ) : Job( interface, parent ), m_isSingleFolderArchive(true), m_isPasswordProtected(false), m_extractedFilesSize(0) { connect(this, SIGNAL(newEntry( const ArchiveEntry&)), this, SLOT(onNewEntry( const ArchiveEntry&))); } void ListJob::doWork() { emit description( this, i18n( "Loading archive..." ) ); m_interface->registerObserver( this ); bool ret = m_interface->list(); if (!m_interface->waitForFinishedSignal()) m_interface->finished(ret); } void ListJob::onNewEntry(const ArchiveEntry& entry) { m_extractedFilesSize += entry[ Size ].toLongLong(); m_isPasswordProtected |= entry [ IsPasswordProtected ].toBool(); if (m_isSingleFolderArchive) { QString filename = entry[ FileName ].toString(); if (m_previousEntry.isEmpty()) { //store the root path of the filename m_previousEntry = filename.split(QDir::separator()).first(); } else { QString newRoot = filename.split(QDir::separator()).first(); if (m_previousEntry != newRoot) { m_isSingleFolderArchive = false; m_subfolderName.clear(); } else { m_previousEntry = newRoot; m_subfolderName = newRoot; } } } } ExtractJob::ExtractJob( const QList& files, const QString& destinationDir, ExtractionOptions options, ReadOnlyArchiveInterface *interface, QObject *parent ) : Job(interface, parent ), m_files( files ), m_destinationDir( destinationDir ), m_options(options) { } void ExtractJob::doWork() { QString desc; if ( m_files.count() == 0 ) { desc = i18n( "Extracting all files" ); } else { desc = i18np( "Extracting one file", "Extracting %1 files", m_files.count() ); } emit description( this, desc ); m_interface->registerObserver( this ); fillInDefaultValues(m_options); kDebug(1601) << "Starting extraction with selected files " << m_files << " Destination dir " << m_destinationDir << " And options " << m_options ; bool ret = m_interface->copyFiles( m_files, m_destinationDir, m_options ); if (!m_interface->waitForFinishedSignal()) m_interface->finished(ret); } void ExtractJob::fillInDefaultValues(ExtractionOptions& options) { if (!options.contains("PreservePaths")) options["PreservePaths"] = false; } AddJob::AddJob( const QStringList& files, const CompressionOptions& options , ReadWriteArchiveInterface *interface, QObject *parent ) : Job( interface, parent ), m_files( files ), m_options(options) { kDebug( 1601 ); } void AddJob::doWork() { emit description( this, i18np( "Adding a file", "Adding %1 files", m_files.count() ) ); ReadWriteArchiveInterface *m_writeInterface = qobject_cast (m_interface); Q_ASSERT(m_writeInterface); m_writeInterface->registerObserver( this ); bool ret = m_writeInterface->addFiles( m_files, m_options ); if (!m_interface->waitForFinishedSignal()) m_interface->finished(ret); } DeleteJob::DeleteJob( const QList& files, ReadWriteArchiveInterface *interface, QObject *parent ) : Job( interface, parent ), m_files( files ) { } void DeleteJob::doWork() { emit description( this, i18np( "Deleting a file from the archive", "Deleting %1 files", m_files.count() ) ); ReadWriteArchiveInterface *m_writeInterface = qobject_cast (m_interface); Q_ASSERT(m_writeInterface); m_writeInterface->registerObserver( this ); bool ret = m_writeInterface->deleteFiles( m_files ); if (!m_interface->waitForFinishedSignal()) m_interface->finished(ret); } } // namespace Kerfuffle diff --git a/kerfuffle/jobs.h b/kerfuffle/jobs.h index 20858cec..02359cbe 100644 --- a/kerfuffle/jobs.h +++ b/kerfuffle/jobs.h @@ -1,147 +1,149 @@ /* * Copyright (c) 2007 Henrique Pinto * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef JOBS_H #define JOBS_H #include "kerfuffle_export.h" #include "archiveinterface.h" #include "archive.h" #include "queries.h" #include "observer.h" #include #include #include #include namespace ThreadWeaver { class Job; } // namespace ThreadWeaver namespace Kerfuffle { class KERFUFFLE_EXPORT Job: public KJob, public ArchiveObserver { Q_OBJECT //we friend the Archive class to let it create jobs friend class Archive; public: void start(); //abstract implemented methods from observer virtual void onError( const QString & message, const QString & details ); virtual void onInfo( const QString & info); virtual void onEntry( const ArchiveEntry & archiveEntry ); virtual void onProgress( double ); virtual void onEntryRemoved( const QString & path ); virtual void onFinished(bool result); virtual void onUserQuery( class Query *query ); public slots: virtual void doWork() = 0; signals: void userQuery( Query* ); void newEntry( const ArchiveEntry & ); void error( const QString& errorMessage, const QString& details ); void entryRemoved( const QString & entry ); protected: Job(ReadOnlyArchiveInterface *interface, QObject *parent = 0); ReadOnlyArchiveInterface* m_interface; + virtual bool doKill(); + }; class KERFUFFLE_EXPORT ListJob: public Job { Q_OBJECT public: explicit ListJob( ReadOnlyArchiveInterface *interface, QObject *parent = 0 ); void doWork(); bool isSingleFolderArchive() { return m_isSingleFolderArchive; } bool isPasswordProtected() { return m_isPasswordProtected; } QString subfolderName() { return m_subfolderName; } qlonglong extractedFilesSize() { return m_extractedFilesSize; } private slots: void onNewEntry(const ArchiveEntry&); private: bool m_isSingleFolderArchive; bool m_isPasswordProtected; QString m_subfolderName; QString m_previousEntry; qlonglong m_extractedFilesSize; }; class KERFUFFLE_EXPORT ExtractJob: public Job { Q_OBJECT public: ExtractJob( const QList & files, const QString& destinationDir, ExtractionOptions options, ReadOnlyArchiveInterface *interface, QObject *parent = 0 ); void doWork(); private: void fillInDefaultValues(ExtractionOptions& options); QList m_files; QString m_destinationDir; ExtractionOptions m_options; }; class KERFUFFLE_EXPORT AddJob: public Job { Q_OBJECT public: AddJob(const QStringList & files, const CompressionOptions& options, ReadWriteArchiveInterface *interface, QObject *parent = 0 ); void doWork(); private: QStringList m_files; CompressionOptions m_options; }; class KERFUFFLE_EXPORT DeleteJob: public Job { Q_OBJECT public: DeleteJob( const QList& files, ReadWriteArchiveInterface *interface, QObject *parent = 0 ); void doWork(); private: QList m_files; }; } // namespace Kerfuffle #endif // JOBS_H