Skip to content

Commit

Permalink
Allow to cancel archives extraction
Browse files Browse the repository at this point in the history
There is cancel button in extraction dialog, but there were no support
for cancellation in underlying KArchive library. This commits adds
interrupt support and fixes crash #289
  • Loading branch information
variar committed May 11, 2021
1 parent 385c7dc commit cf1b845
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 102 deletions.
11 changes: 7 additions & 4 deletions 3rdparty/karchive/src/karchive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ static QFileDevice::Permissions withExecutablePerms(
return filePerms;
}

bool KArchiveFile::copyTo(const QString &dest) const
bool KArchiveFile::copyTo(const QString &dest, const QAtomicInt& isCanceled) const
{
QFile f(dest + QLatin1Char('/') + name());
if (f.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
Expand All @@ -851,6 +851,9 @@ bool KArchiveFile::copyTo(const QString &dest) const
array.resize(int(qMin(chunkSize, remainingSize)));

while (remainingSize > 0) {
if (isCanceled.loadAcquire() != 0) {
break;
}
const qint64 currentChunkSize = qMin(chunkSize, remainingSize);
const qint64 n = inputDev->read(array.data(), currentChunkSize);
Q_UNUSED(n) // except in Q_ASSERT
Expand All @@ -862,7 +865,7 @@ bool KArchiveFile::copyTo(const QString &dest) const
f.close();

delete inputDev;
return true;
return isCanceled.loadAcquire() == 0;
}
return false;
}
Expand Down Expand Up @@ -953,7 +956,7 @@ static bool sortByPosition(const KArchiveFile *file1, const KArchiveFile *file2)
return file1->position() < file2->position();
}

bool KArchiveDirectory::copyTo(const QString &dest, bool recursiveCopy) const
bool KArchiveDirectory::copyTo(const QString &dest, const QAtomicInt& isCanceled, bool recursiveCopy) const
{
QDir root;
const QString destDir(QDir(dest).absolutePath()); // get directory path without any "." or ".."
Expand Down Expand Up @@ -1025,7 +1028,7 @@ bool KArchiveDirectory::copyTo(const QString &dest, bool recursiveCopy) const
it != end; ++it) {
const KArchiveFile *f = *it;
qint64 pos = f->position();
if (!f->copyTo(fileToDir[pos])) {
if (!f->copyTo(fileToDir[pos], isCanceled)) {
return false;
}
}
Expand Down
3 changes: 2 additions & 1 deletion 3rdparty/karchive/src/karchivedirectory.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <sys/stat.h>
#include <sys/types.h>

#include <QAtomicInt>
#include <QDate>
#include <QString>
#include <QStringList>
Expand Down Expand Up @@ -122,7 +123,7 @@ class KARCHIVE_EXPORT KArchiveDirectory : public KArchiveEntry
* @param recursive if set to true, subdirectories are extracted as well
* @return true on success, false if the directory (dest + '/' + name()) couldn't be created
*/
bool copyTo(const QString &dest, bool recursive = true) const;
bool copyTo(const QString &dest, const QAtomicInt& isCanceled, bool recursive = true) const;

protected:
void virtual_hook(int id, void *data) override;
Expand Down
4 changes: 3 additions & 1 deletion 3rdparty/karchive/src/karchivefile.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#ifndef KARCHIVEFILE_H
#define KARCHIVEFILE_H

#include <QAtomicInt>

#include <karchiveentry.h>

class KArchiveFilePrivate;
Expand Down Expand Up @@ -103,7 +105,7 @@ class KARCHIVE_EXPORT KArchiveFile : public KArchiveEntry
* @param dest the directory to extract to
* @return true on success, false if the file (dest + '/' + name()) couldn't be created
*/
bool copyTo(const QString &dest) const;
bool copyTo(const QString &dest, const QAtomicInt& isCanceled) const;

protected:
void virtual_hook(int id, void *data) override;
Expand Down
124 changes: 72 additions & 52 deletions src/ui/src/mainwindow.cpp

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/utils/include/atomicflag.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ class AtomicFlag {
return !flag_.loadAcquire();
}

operator const QAtomicInt&() const
{
return flag_;
}

private:
QAtomicInt flag_;
};
Expand Down
11 changes: 7 additions & 4 deletions src/utils/include/decompressor.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,21 @@
#include <QFuture>
#include <QFutureWatcher>

#include "atomicflag.h"

enum class DecompressAction {None, Extract, Decompress};
enum class DecompressAction { None, Extract, Decompress };
class Decompressor : public QObject {
Q_OBJECT
public:
explicit Decompressor( QObject* parent = nullptr );

bool decompress( const QString& path, QFile* outputFile );
bool extract( const QString& archiveFilePath, const QString& destination );
bool decompress( const QString& path, QFile* outputFile, AtomicFlag& interrupt );
bool extract( const QString& archiveFilePath, const QString& destination,
AtomicFlag& interrupt );

bool waitForResult();

static DecompressAction action(const QString& archiveFilePath );
static DecompressAction action( const QString& archiveFilePath );

signals:
void finished( bool );
Expand Down
128 changes: 88 additions & 40 deletions src/utils/src/decompressor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,92 @@ std::shared_ptr<KCompressionDevice> makeDecompressor( Archive archiveType,
}
}

bool doExtract( std::shared_ptr<KArchive> archive, const QString& archiveFilePath,
const QString& destination, AtomicFlag& interrupt )
{
if ( !archive->open( QIODevice::ReadOnly ) ) {
LOG_WARNING << "Cannot open " << archiveFilePath;
return false;
}

const KArchiveDirectory* root = archive->directory();

if ( !root ) {
LOG_WARNING << "Cannot open root directory" << archiveFilePath;
archive->close();
return false;
}

auto result = false;
try {
const auto recursive = true;
result = root->copyTo( destination, interrupt, recursive );
} catch ( const std::exception& e ) {
LOG_ERROR << "Exception during extract: " << e.what();
}

if ( interrupt ) {
result = false;
LOG_INFO << "Interrupted extract of " << archiveFilePath;
}

archive->close();
return result;
}

bool doDecompress( std::shared_ptr<KCompressionDevice> input, const QString& archiveFilePath,
QFile* outputFile, AtomicFlag& interrupt )
{
if ( !input->open( QIODevice::ReadOnly ) ) {
LOG_WARNING << "Cannot open " << archiveFilePath;
return false;
}

bool success = true;
try {
while ( !input->atEnd() ) {
if ( interrupt ) {
success = false;
LOG_INFO << "Interrupted decompress of " << archiveFilePath;
break;
}

QByteArray data = input->read( 4 * 1024 * 1024 );
if ( data.size() > 0 ) {
const auto writtenBytes = outputFile->write( data );
if ( writtenBytes < 0 ) {
LOG_ERROR << "Error decompressing " << archiveFilePath;
success = false;
break;
}
}
}
} catch ( const std::exception& e ) {
LOG_ERROR << "Exception during decompress: " << e.what();
}

input->close();
outputFile->close();

return success;
}

} // namespace

Decompressor::Decompressor( QObject* parent )
: QObject( parent )
{
connect( &watcher_, &QFutureWatcher<bool>::finished, [this]() {
connect( &watcher_, &QFutureWatcher<bool>::finished, [ this ]() {
LOG_INFO << "Decompressor finished " << watcher_.result();
emit finished( watcher_.result() );
} );
}

bool Decompressor::waitForResult()
{
return watcher_.result();
}

DecompressAction Decompressor::action( const QString& archiveFilePath )
{
const auto archive = archiveType( archiveFilePath );
Expand All @@ -190,44 +265,26 @@ DecompressAction Decompressor::action( const QString& archiveFilePath )
}
}

bool Decompressor::decompress( const QString& archiveFilePath, QFile* outputFile )
bool Decompressor::decompress( const QString& archiveFilePath, QFile* outputFile,
AtomicFlag& interrupt )
{
auto decompressor = makeDecompressor( archiveType( archiveFilePath ), archiveFilePath );
if ( !decompressor ) {
LOG_WARNING << "Unsupported archive " << archiveFilePath.constData();
return false;
}

future_ = QtConcurrent::run( [input = std::move( decompressor ), archiveFilePath, outputFile] {
if ( !input->open( QIODevice::ReadOnly ) ) {
LOG_WARNING << "Cannot open " << archiveFilePath;
return false;
}

bool success = true;
while ( !input->atEnd() ) {
QByteArray data = input->read( 1024 * 1024 );
if ( data.size() > 0 ) {
const auto writtenBytes = outputFile->write( data );
if ( writtenBytes < 0 ) {
LOG_ERROR << "Error decompressing " << archiveFilePath;
success = false;
break;
}
}
}
input->close();
outputFile->close();

return success;
} );

future_ = QtConcurrent::run(
[ input = std::move( decompressor ), archiveFilePath, outputFile, &interrupt ] {
return doDecompress( input, archiveFilePath, outputFile, interrupt );
} );
watcher_.setFuture( future_ );

return true;
}

bool Decompressor::extract( const QString& archiveFilePath, const QString& destination )
bool Decompressor::extract( const QString& archiveFilePath, const QString& destination,
AtomicFlag& interrupt )
{
auto archive = makeExtractor( archiveType( archiveFilePath ), archiveFilePath );
if ( !archive ) {
Expand All @@ -237,19 +294,10 @@ bool Decompressor::extract( const QString& archiveFilePath, const QString& desti

// Open the archive

future_ = QtConcurrent::run( [ar = std::move( archive ), archiveFilePath, destination] {
if ( !ar->open( QIODevice::ReadOnly ) ) {
LOG_WARNING << "Cannot open " << archiveFilePath;
return false;
}

const KArchiveDirectory* root = ar->directory();
const auto recursive = true;
const auto result = root->copyTo( destination, recursive );
ar->close();
return result;
} );

future_ = QtConcurrent::run(
[ ar = std::move( archive ), archiveFilePath, destination, &interrupt ] {
return doExtract( ar, archiveFilePath, destination, interrupt );
} );
watcher_.setFuture( future_ );

return true;
Expand Down

0 comments on commit cf1b845

Please sign in to comment.