Skip to content

Commit

Permalink
fix(core): replace libarchive extraction with own logic (fixes #747)
Browse files Browse the repository at this point in the history
On Windows this allows to properly handle paths containing non-Latin
characters.
  • Loading branch information
trollixx committed Sep 28, 2018
1 parent 00f2993 commit 0785dd4
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 24 deletions.
78 changes: 57 additions & 21 deletions src/libs/core/extractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,38 +27,37 @@
#include <archive.h>
#include <archive_entry.h>

#include <sys/stat.h>

using namespace Zeal::Core;

Extractor::Extractor(QObject *parent) :
QObject(parent)
{
}

void Extractor::extract(const QString &filePath, const QString &destination, const QString &root)
void Extractor::extract(const QString &sourceFile, const QString &destination, const QString &root)
{
ExtractInfo info = {
this, // extractor
archive_read_new(), // archiveHandle
filePath, // filePath
QFileInfo(filePath).size(), // totalBytes
sourceFile, // filePath
QFileInfo(sourceFile).size(), // totalBytes
0 // extractedBytes
};

archive_read_support_filter_all(info.archiveHandle);
archive_read_support_format_all(info.archiveHandle);

int r = archive_read_open_filename(info.archiveHandle, qPrintable(filePath), 10240);
int r = archive_read_open_filename(info.archiveHandle, qPrintable(sourceFile), 10240);
if (r) {
emit error(filePath, QString::fromLocal8Bit(archive_error_string(info.archiveHandle)));
emit error(sourceFile, QString::fromLocal8Bit(archive_error_string(info.archiveHandle)));
return;
}

archive_read_extract_set_progress_callback(info.archiveHandle, &Extractor::progressCallback,
&info);

QDir destinationDir(destination);
if (!root.isEmpty())
if (!root.isEmpty()) {
destinationDir = destinationDir.filePath(root);
}

// TODO: Do not strip root directory in archive if it equals to 'root'
archive_entry *entry;
Expand All @@ -69,25 +68,62 @@ void Extractor::extract(const QString &filePath, const QString &destination, con
// TODO: Remove once https://github.com/libarchive/libarchive/issues/587 is resolved.
QString pathname = QString::fromWCharArray(archive_entry_pathname_w(entry));
#endif
if (!root.isEmpty())

if (!root.isEmpty()) {
pathname.remove(0, pathname.indexOf(QLatin1String("/")) + 1);
archive_entry_set_pathname(entry, qPrintable(destinationDir.filePath(pathname)));
archive_read_extract(info.archiveHandle, entry, 0);
}

const QString filePath = destinationDir.absoluteFilePath(pathname);

const auto filetype = archive_entry_filetype(entry);
if (filetype == S_IFDIR) {
QDir().mkpath(QFileInfo(filePath).absolutePath());
continue;
} else if (filetype != S_IFREG) {
qWarning("Unsupported filetype %d for %s!", filetype, qPrintable(pathname));
continue;
}

QScopedPointer<QFile> file(new QFile(filePath));
if (!file->open(QIODevice::WriteOnly)) {
qWarning("Cannot open file for writing: %s", qPrintable(pathname));
continue;
}

const void *buffer;
size_t size;
std::int64_t offset;
for (;;) {
int rc = archive_read_data_block(info.archiveHandle, &buffer, &size, &offset);
if (rc != ARCHIVE_OK) {
if (rc == ARCHIVE_EOF) {
break;
}

qWarning("Cannot read from archive: %s", archive_error_string(info.archiveHandle));
emit error(sourceFile,
QString::fromLocal8Bit(archive_error_string(info.archiveHandle)));
return;
}

file->write(static_cast<const char *>(buffer), size);
}

emitProgress(info);
}

emit completed(filePath);
emit completed(sourceFile);
archive_read_free(info.archiveHandle);
}

void Extractor::progressCallback(void *ptr)
void Extractor::emitProgress(ExtractInfo &info)
{
ExtractInfo *info = static_cast<ExtractInfo *>(ptr);

const qint64 extractedBytes = archive_filter_bytes(info->archiveHandle, -1);
if (extractedBytes == info->extractedBytes)
const qint64 extractedBytes = archive_filter_bytes(info.archiveHandle, -1);
if (extractedBytes == info.extractedBytes) {
return;
}

info->extractedBytes = extractedBytes;
info.extractedBytes = extractedBytes;

emit info->extractor->progress(info->filePath, extractedBytes, info->totalBytes);
emit progress(info.filePath, extractedBytes, info.totalBytes);
}
7 changes: 4 additions & 3 deletions src/libs/core/extractor.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ class Extractor : public QObject
explicit Extractor(QObject *parent = nullptr);

public slots:
void extract(const QString &filePath, const QString &destination, const QString &root = QString());
void extract(const QString &sourceFile,
const QString &destination,
const QString &root = QString());

signals:
void error(const QString &filePath, const QString &message);
Expand All @@ -46,14 +48,13 @@ public slots:

private:
struct ExtractInfo {
Extractor *extractor;
archive *archiveHandle;
QString filePath;
qint64 totalBytes;
qint64 extractedBytes;
};

static void progressCallback(void *ptr);
void emitProgress(ExtractInfo &info);
};

} // namespace Core
Expand Down

0 comments on commit 0785dd4

Please sign in to comment.