2020-05-27 14:08:49 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2020 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "archive.h"
|
|
|
|
|
|
|
|
|
|
#include "algorithm.h"
|
|
|
|
|
#include "checkablemessagebox.h"
|
|
|
|
|
#include "environment.h"
|
|
|
|
|
#include "mimetypes/mimedatabase.h"
|
|
|
|
|
#include "qtcassert.h"
|
|
|
|
|
#include "synchronousprocess.h"
|
|
|
|
|
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QPushButton>
|
2020-06-12 09:07:19 +02:00
|
|
|
#include <QSettings>
|
2020-06-15 10:32:22 +02:00
|
|
|
#include <QTimer>
|
2020-05-27 14:08:49 +02:00
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
struct Tool
|
|
|
|
|
{
|
2020-05-27 16:32:31 +02:00
|
|
|
QString executable;
|
2020-05-27 14:08:49 +02:00
|
|
|
QStringList arguments;
|
2020-05-27 16:32:31 +02:00
|
|
|
QStringList supportedMimeTypes;
|
2020-06-12 09:07:19 +02:00
|
|
|
QStringList additionalSearchDirs;
|
2020-05-27 14:08:49 +02:00
|
|
|
};
|
|
|
|
|
|
2020-06-16 09:59:51 +02:00
|
|
|
static QStringList additionalInstallDirs(const QString ®istryKey, const QString &valueName)
|
|
|
|
|
{
|
|
|
|
|
#if defined(Q_OS_WIN)
|
|
|
|
|
const QSettings settings64(registryKey, QSettings::Registry64Format);
|
|
|
|
|
const QSettings settings32(registryKey, QSettings::Registry32Format);
|
|
|
|
|
return {settings64.value(valueName).toString(), settings32.value(valueName).toString()};
|
|
|
|
|
#else
|
|
|
|
|
Q_UNUSED(registryKey)
|
|
|
|
|
Q_UNUSED(valueName)
|
|
|
|
|
return {};
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-12 09:07:19 +02:00
|
|
|
static const QVector<Tool> &sTools()
|
|
|
|
|
{
|
|
|
|
|
static QVector<Tool> tools;
|
|
|
|
|
if (tools.isEmpty()) {
|
|
|
|
|
tools << Tool{{"unzip"}, {"-o", "%{src}", "-d", "%{dest}"}, {"application/zip"}, {}};
|
|
|
|
|
tools << Tool{{"7z"},
|
|
|
|
|
{"x", "-o%{dest}", "-y", "%{src}"},
|
|
|
|
|
{"application/zip", "application/x-7z-compressed"},
|
2020-06-16 09:59:51 +02:00
|
|
|
additionalInstallDirs("HKEY_CURRENT_USER\\Software\\7-Zip", "Path")};
|
2020-06-12 09:07:19 +02:00
|
|
|
tools << Tool{{"tar"},
|
|
|
|
|
{"xvf", "%{src}"},
|
|
|
|
|
{"application/zip", "application/x-tar", "application/x-7z-compressed"},
|
|
|
|
|
{}};
|
|
|
|
|
tools << Tool{{"tar"}, {"xvzf", "%{src}"}, {"application/x-compressed-tar"}, {}};
|
|
|
|
|
tools << Tool{{"tar"}, {"xvJf", "%{src}"}, {"application/x-xz-compressed-tar"}, {}};
|
|
|
|
|
tools << Tool{{"tar"}, {"xvjf", "%{src}"}, {"application/x-bzip-compressed-tar"}, {}};
|
2020-06-16 09:59:51 +02:00
|
|
|
const QStringList additionalCMakeDirs = additionalInstallDirs(
|
|
|
|
|
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Kitware\\CMake", "InstallDir");
|
2020-06-12 09:07:19 +02:00
|
|
|
tools << Tool{{"cmake"},
|
|
|
|
|
{"-E", "tar", "xvf", "%{src}"},
|
|
|
|
|
{"application/zip", "application/x-tar", "application/x-7z-compressed"},
|
|
|
|
|
additionalCMakeDirs};
|
|
|
|
|
tools << Tool{{"cmake"},
|
|
|
|
|
{"-E", "tar", "xvzf", "%{src}"},
|
|
|
|
|
{"application/x-compressed-tar"},
|
|
|
|
|
additionalCMakeDirs};
|
|
|
|
|
tools << Tool{{"cmake"},
|
|
|
|
|
{"-E", "tar", "xvJf", "%{src}"},
|
|
|
|
|
{"application/x-xz-compressed-tar"},
|
|
|
|
|
additionalCMakeDirs};
|
|
|
|
|
tools << Tool{{"cmake"},
|
|
|
|
|
{"-E", "tar", "xvjf", "%{src}"},
|
|
|
|
|
{"application/x-bzip-compressed-tar"},
|
|
|
|
|
additionalCMakeDirs};
|
|
|
|
|
}
|
|
|
|
|
return tools;
|
|
|
|
|
}
|
2020-05-27 16:32:31 +02:00
|
|
|
|
|
|
|
|
static QVector<Tool> toolsForMimeType(const Utils::MimeType &mimeType)
|
2020-05-27 14:08:49 +02:00
|
|
|
{
|
2020-06-12 09:07:19 +02:00
|
|
|
return Utils::filtered(sTools(), [mimeType](const Tool &tool) {
|
2020-05-27 16:32:31 +02:00
|
|
|
return Utils::anyOf(tool.supportedMimeTypes,
|
|
|
|
|
[mimeType](const QString &mt) { return mimeType.inherits(mt); });
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-05-27 14:08:49 +02:00
|
|
|
|
2020-05-27 16:32:31 +02:00
|
|
|
static QVector<Tool> toolsForFilePath(const Utils::FilePath &fp)
|
|
|
|
|
{
|
|
|
|
|
return toolsForMimeType(Utils::mimeTypeForFile(fp.toString()));
|
|
|
|
|
}
|
2020-05-27 14:08:49 +02:00
|
|
|
|
2020-05-27 16:32:31 +02:00
|
|
|
static Utils::optional<Tool> resolveTool(const Tool &tool)
|
|
|
|
|
{
|
2020-06-12 09:07:19 +02:00
|
|
|
const QString executable
|
|
|
|
|
= Utils::Environment::systemEnvironment()
|
|
|
|
|
.searchInPath(Utils::HostOsInfo::withExecutableSuffix(tool.executable),
|
|
|
|
|
Utils::transform(tool.additionalSearchDirs, &Utils::FilePath::fromString))
|
|
|
|
|
.toString();
|
2020-05-27 16:32:31 +02:00
|
|
|
Tool resolvedTool = tool;
|
|
|
|
|
resolvedTool.executable = executable;
|
|
|
|
|
return executable.isEmpty() ? Utils::nullopt : Utils::make_optional(resolvedTool);
|
|
|
|
|
}
|
2020-05-27 14:08:49 +02:00
|
|
|
|
2020-05-27 16:32:31 +02:00
|
|
|
Utils::optional<Tool> unzipTool(const Utils::FilePath &src, const Utils::FilePath &dest)
|
|
|
|
|
{
|
|
|
|
|
const QVector<Tool> tools = toolsForFilePath(src);
|
|
|
|
|
for (const Tool &tool : tools) {
|
|
|
|
|
const Utils::optional<Tool> resolvedTool = resolveTool(tool);
|
|
|
|
|
if (resolvedTool) {
|
|
|
|
|
Tool result = *resolvedTool;
|
|
|
|
|
const QString srcStr = src.toString();
|
|
|
|
|
const QString destStr = dest.toString();
|
|
|
|
|
result.arguments
|
|
|
|
|
= Utils::transform(result.arguments, [srcStr, destStr](const QString &a) {
|
|
|
|
|
QString val = a;
|
|
|
|
|
return val.replace("%{src}", srcStr).replace("%{dest}", destStr);
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-27 14:08:49 +02:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
namespace Utils {
|
|
|
|
|
|
|
|
|
|
bool Archive::supportsFile(const FilePath &filePath, QString *reason)
|
|
|
|
|
{
|
2020-05-27 16:32:31 +02:00
|
|
|
const QVector<Tool> tools = toolsForFilePath(filePath);
|
|
|
|
|
if (tools.isEmpty()) {
|
2020-05-27 14:08:49 +02:00
|
|
|
if (reason)
|
|
|
|
|
*reason = tr("File format not supported.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-05-27 16:32:31 +02:00
|
|
|
if (!anyOf(tools, [](const Tool &t) { return resolveTool(t); })) {
|
2020-05-27 14:08:49 +02:00
|
|
|
if (reason)
|
2020-05-27 16:32:31 +02:00
|
|
|
*reason = tr("Could not find any unarchiving executable in PATH (%1).")
|
|
|
|
|
.arg(transform<QStringList>(tools, &Tool::executable).join(", "));
|
2020-05-27 14:08:49 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Archive::unarchive(const FilePath &src, const FilePath &dest, QWidget *parent)
|
|
|
|
|
{
|
2020-06-08 09:35:25 +02:00
|
|
|
Archive *archive = unarchive(src, dest);
|
|
|
|
|
QTC_ASSERT(archive, return false);
|
|
|
|
|
|
2020-05-27 14:08:49 +02:00
|
|
|
CheckableMessageBox box(parent);
|
|
|
|
|
box.setIcon(QMessageBox::Information);
|
2020-05-27 16:32:31 +02:00
|
|
|
box.setWindowTitle(tr("Unarchiving File"));
|
2020-05-27 14:08:49 +02:00
|
|
|
box.setText(tr("Unzipping \"%1\" to \"%2\".").arg(src.toUserOutput(), dest.toUserOutput()));
|
|
|
|
|
box.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
|
|
|
|
box.button(QDialogButtonBox::Ok)->setEnabled(false);
|
|
|
|
|
box.setCheckBoxVisible(false);
|
2020-06-08 09:35:25 +02:00
|
|
|
QObject::connect(archive, &Archive::outputReceived, &box, [&box](const QString &output) {
|
|
|
|
|
box.setDetailedText(box.detailedText() + output);
|
2020-05-27 14:08:49 +02:00
|
|
|
});
|
2020-06-08 09:35:25 +02:00
|
|
|
bool success = false;
|
|
|
|
|
QObject::connect(archive, &Archive::finished, [&box, &success](bool ret) {
|
|
|
|
|
box.button(QDialogButtonBox::Ok)->setEnabled(true);
|
|
|
|
|
box.button(QDialogButtonBox::Cancel)->setEnabled(false);
|
|
|
|
|
success = ret;
|
2020-05-27 14:08:49 +02:00
|
|
|
});
|
2020-06-08 09:35:25 +02:00
|
|
|
QObject::connect(&box, &QMessageBox::rejected, archive, &Archive::cancel);
|
2020-05-27 14:08:49 +02:00
|
|
|
box.exec();
|
2020-06-08 09:35:25 +02:00
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Archive *Archive::unarchive(const FilePath &src, const FilePath &dest)
|
|
|
|
|
{
|
|
|
|
|
const Utils::optional<Tool> tool = unzipTool(src, dest);
|
|
|
|
|
QTC_ASSERT(tool, return nullptr);
|
|
|
|
|
|
|
|
|
|
auto archive = new Archive;
|
|
|
|
|
|
|
|
|
|
const QString workingDirectory = dest.toFileInfo().absoluteFilePath();
|
|
|
|
|
QDir(workingDirectory).mkpath(".");
|
|
|
|
|
|
|
|
|
|
archive->m_process = new QProcess;
|
|
|
|
|
archive->m_process->setProcessChannelMode(QProcess::MergedChannels);
|
|
|
|
|
QObject::connect(
|
|
|
|
|
archive->m_process,
|
|
|
|
|
&QProcess::readyReadStandardOutput,
|
|
|
|
|
archive,
|
|
|
|
|
[archive]() {
|
2020-06-23 08:34:35 +02:00
|
|
|
if (!archive->m_process)
|
|
|
|
|
return;
|
2020-06-08 09:35:25 +02:00
|
|
|
archive->outputReceived(QString::fromUtf8(archive->m_process->readAllStandardOutput()));
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
QObject::connect(
|
|
|
|
|
archive->m_process,
|
|
|
|
|
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
|
|
|
|
archive,
|
|
|
|
|
[archive](int, QProcess::ExitStatus) {
|
2020-06-23 08:34:35 +02:00
|
|
|
if (!archive->m_process)
|
|
|
|
|
return;
|
2020-06-08 09:35:25 +02:00
|
|
|
archive->finished(archive->m_process->exitStatus() == QProcess::NormalExit
|
|
|
|
|
&& archive->m_process->exitCode() == 0);
|
|
|
|
|
archive->m_process->deleteLater();
|
|
|
|
|
archive->m_process = nullptr;
|
|
|
|
|
archive->deleteLater();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
QObject::connect(
|
|
|
|
|
archive->m_process,
|
|
|
|
|
&QProcess::errorOccurred,
|
|
|
|
|
archive,
|
|
|
|
|
[archive](QProcess::ProcessError) {
|
2020-06-23 08:34:35 +02:00
|
|
|
if (!archive->m_process)
|
|
|
|
|
return;
|
2020-06-08 09:35:25 +02:00
|
|
|
archive->outputReceived(tr("Command failed."));
|
|
|
|
|
archive->finished(false);
|
|
|
|
|
archive->m_process->deleteLater();
|
|
|
|
|
archive->m_process = nullptr;
|
|
|
|
|
archive->deleteLater();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
QTimer::singleShot(0, archive, [archive, tool, workingDirectory] {
|
|
|
|
|
archive->outputReceived(
|
|
|
|
|
tr("Running %1\nin \"%2\".\n\n", "Running <cmd> in <workingdirectory>")
|
|
|
|
|
.arg(CommandLine(tool->executable, tool->arguments).toUserOutput(),
|
|
|
|
|
workingDirectory));
|
|
|
|
|
});
|
|
|
|
|
archive->m_process->setProgram(tool->executable);
|
|
|
|
|
archive->m_process->setArguments(tool->arguments);
|
|
|
|
|
archive->m_process->setWorkingDirectory(workingDirectory);
|
|
|
|
|
archive->m_process->start(QProcess::ReadOnly);
|
|
|
|
|
return archive;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Archive::cancel()
|
|
|
|
|
{
|
|
|
|
|
if (!m_process)
|
|
|
|
|
return;
|
|
|
|
|
SynchronousProcess::stopProcess(*m_process);
|
2020-05-27 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Utils
|