Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/cli7zplugin/cli7zplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ bool Cli7zPlugin::readListLine(const QString &line)
return false;
}

const QRegularExpression rxVersionLine(QStringLiteral("^p7zip Version ([\\d\\.]+) .*$"));
const QRegularExpression rxVersionLine(QStringLiteral("^(?:p7zip Version|7-Zip) ([\\d\\.]+).*$"));
QRegularExpressionMatch matchVersion;

switch (m_parseState) {
Expand Down
3 changes: 2 additions & 1 deletion 3rdparty/clipzipplugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set(LIB_NAME clipzipplugin)
project(${LIB_NAME})

find_package(PkgConfig REQUIRED)
find_package(Qt${QT_DESIRED_VERSION} REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_DESIRED_VERSION} REQUIRED COMPONENTS Widgets Core5Compat)
find_package(KF${KF_VERSION_MAJOR}Codecs REQUIRED)

include(FindPkgConfig)
Expand All @@ -22,6 +22,7 @@ add_library(${LIB_NAME} SHARED ${c_files} ${json_files} ${h_files})

target_link_libraries(${LIB_NAME}
Qt${QT_DESIRED_VERSION}::Widgets
Qt${QT_DESIRED_VERSION}::Core5Compat
KF${KF_VERSION_MAJOR}::Codecs
compressor-interface
)
Expand Down
215 changes: 201 additions & 14 deletions 3rdparty/clipzipplugin/clipzipplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,71 @@

#include "clipzipplugin.h"
#include "datamanager.h"
#include "common.h"

#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QStandardPaths>
#include <QCoreApplication>
#include <QDirIterator>
#include <cmath>

#include <linux/limits.h>
#include <signal.h>

#include <QTemporaryFile>
#include <QTextCodec>


namespace {

constexpr auto kUiEncAes128 = "AES128";
constexpr auto kUiEncAes192 = "AES192";
constexpr auto kUiEncAes256 = "AES256";

constexpr auto kCliEncAes128 = "aes128";
constexpr auto kCliEncAes192 = "aes192";
constexpr auto kCliEncAes256 = "aes256";

// 与 LibzipPlugin::passwordUnicode(str, 0) 对 .zip 的逻辑一致,保证与 libzip 产包互解
static QByteArray passwordBytesLikeLibzipForZip(const QString &strPassword, const QString &archiveName)
{
if (!archiveName.endsWith(QLatin1String(".zip"), Qt::CaseInsensitive)) {
return strPassword.toUtf8();
}
const int nCount = strPassword.count();
bool hasHan = false;
for (int i = 0; i < nCount; ++i) {
const ushort uni = strPassword.at(i).unicode();
if (uni >= 0x4E00 && uni <= 0x9FA5) {
hasHan = true;
break;
}
}
if (hasHan) {
QTextCodec *utf8 = QTextCodec::codecForName("UTF-8");
QTextCodec *dst = QTextCodec::codecForName("UTF-8");
if (!utf8 || !dst) {
return strPassword.toUtf8();
}
const QString strUnicode = utf8->toUnicode(strPassword.toUtf8().constData());
return dst->fromUnicode(strUnicode);
}
return strPassword.toUtf8();
}

static QString encryptionCliArgFromUi(const QString &ui)
{
if (ui == QLatin1String(kUiEncAes128)) return QString::fromLatin1(kCliEncAes128);
if (ui == QLatin1String(kUiEncAes192)) return QString::fromLatin1(kCliEncAes192);
if (ui == QLatin1String(kUiEncAes256)) return QString::fromLatin1(kCliEncAes256);
return QString::fromLatin1(kCliEncAes256);
}

} // namespace

// pzip
// pzip 安装路径
static const QString PZIP_INSTALL_PATH = QStringLiteral("/usr/lib/deepin-compressor/pzip");
static const QString PUNZIP_INSTALL_PATH = QStringLiteral("/usr/lib/deepin-compressor/punzip");
Expand Down Expand Up @@ -43,13 +98,31 @@ CliPzipPlugin::CliPzipPlugin(QObject *parent, const QVariantList &args)
m_ePlugintype = PT_Libzip; // 复用 Libzip 类型,因为都是 ZIP 格式
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, [=]() {
QFileInfo info(m_strArchiveName);
if (m_qTotalSize > 0) {
emit signalprogress(static_cast<double>(info.size()) / m_qTotalSize * 100);
}
emitProgressIfArchiveGrew();
});
}

void CliPzipPlugin::emitProgressIfArchiveGrew()
{
if (m_qTotalSize <= 0) {
return;
}

QFileInfo info(m_progressArchiveName.isEmpty() ? m_strArchiveName : m_progressArchiveName);
const qint64 curSize = info.size();
if (curSize < 0) {
return;
}

// 避免重复发送相同进度:会导致 UI 速度=0,从而“剩余时间”消失
if (curSize == m_lastProgressArchiveSize) {
return;
}
m_lastProgressArchiveSize = curSize;

emit signalprogress(static_cast<double>(curSize) / m_qTotalSize * 100);
}

CliPzipPlugin::~CliPzipPlugin()
{
deleteProcess();
Expand Down Expand Up @@ -172,6 +245,11 @@ PluginFinishType CliPzipPlugin::addFiles(const QList<FileEntry> &files, const Co
m_qTotalSize = options.qTotalSize;
m_stdOutData.clear();
m_isProcessKilled = false;
m_tempArchiveDir.reset();
m_tempArchiveName.clear();
m_progressArchiveName.clear();
m_lastProgressArchiveSize = -1;
m_lastUiBytesProgress = -1.0;

QString pzipPath = getPzipPath();
if (pzipPath.isEmpty()) {
Expand All @@ -186,10 +264,74 @@ PluginFinishType CliPzipPlugin::addFiles(const QList<FileEntry> &files, const Co
m_process->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::Text);

QStringList arguments;


m_passwordFile.reset();

// 静默模式
arguments << "-q";

arguments << "--ui-events";

// MTP 挂载目录不一定支持 seek/rename 等操作,先在临时目录生成,再 mv 回目标路径
QString outArchiveName = m_strArchiveName;
if (IsMtpFileOrDirectory(m_strArchiveName)) {
m_tempArchiveDir = std::make_unique<QTemporaryDir>();
m_tempArchiveDir->setAutoRemove(true);
m_tempArchiveName = m_tempArchiveDir->path() + QDir::separator() + QFileInfo(m_strArchiveName).fileName();
outArchiveName = m_tempArchiveName;
qInfo() << "[PZIP_ROUTE]" << "clipzip: mtp target detected, staging archive at:" << outArchiveName
<< "final:" << m_strArchiveName;
}
m_progressArchiveName = outArchiveName;

// pzip 静默模式不会输出“当前文件名”,为了避免进度页一直显示“计算中”,先上报一个可展示的文件名
if (!files.isEmpty()) {
const FileEntry &f0 = files.first();
const QString display = !f0.strAlias.isEmpty() ? f0.strAlias
: (!f0.strFileName.isEmpty() ? f0.strFileName : f0.strFullPath);
emit signalCurFileName(display);
}

// 兜底:部分场景上层未计算总大小(qTotalSize=0),会导致进度条永远不刷新
if (m_qTotalSize <= 0) {
qint64 sum = 0;
for (const FileEntry &f : files) {
QFileInfo fi(f.strFullPath);
if (!fi.exists()) continue;
if (fi.isFile()) {
sum += fi.size();
continue;
}
if (fi.isDir()) {
QDirIterator it(fi.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
sum += it.fileInfo().size();
}
}
}
m_qTotalSize = sum;
qInfo() << "[PZIP_ROUTE]" << "clipzip: fallback total size computed:" << m_qTotalSize;
}

if (options.bEncryption && !options.strPassword.isEmpty()) {
m_passwordFile = std::make_unique<QTemporaryFile>();
if (!m_passwordFile->open()) {
qWarning() << "Failed to create temporary file for password";
m_eErrorType = ET_PluginError;
return PFT_Error;
}
const QByteArray pwBytes = passwordBytesLikeLibzipForZip(options.strPassword, m_strArchiveName);
if (m_passwordFile->write(pwBytes) != pwBytes.size()) {
qWarning() << "Failed to write password bytes";
m_eErrorType = ET_PluginError;
return PFT_Error;
}
m_passwordFile->flush();
arguments << "--password-file" << m_passwordFile->fileName();
arguments << "-e" << encryptionCliArgFromUi(options.strEncryptionMethod);
}

// 压缩等级:0=Store(不压缩),1-9=deflate 压缩级别
int level = options.iCompressionLevel;
if (level < 0 || level > 9) {
Expand All @@ -203,7 +345,7 @@ PluginFinishType CliPzipPlugin::addFiles(const QList<FileEntry> &files, const Co
}

// 输出文件
arguments << m_strArchiveName;
arguments << outArchiveName;

// 添加所有源文件/目录
for (const FileEntry &file : files) {
Expand Down Expand Up @@ -242,6 +384,11 @@ PluginFinishType CliPzipPlugin::addFiles(const QList<FileEntry> &files, const Co
m_processId = m_process->processId();
getChildProcessId(m_processId, QStringList() << "pzip", m_childProcessId);
m_timer->start(500); // 每500ms更新一次进度

// 让 UI 立即脱离“计算中”(速度/剩余时间会在首次 setProgress 后更新)
if (m_qTotalSize > 0) {
emit signalprogress(1);
}
}

return PFT_Nomral;
Expand Down Expand Up @@ -327,6 +474,36 @@ bool CliPzipPlugin::doKill()

bool CliPzipPlugin::handleLine(const QString &line)
{
static const QLatin1String kUiPrefix("[PZIP_UI] entry ");
if (line.startsWith(kUiPrefix)) {
const QString nameInArchive = line.mid(kUiPrefix.size());
if (!nameInArchive.isEmpty()) {
emit signalCurFileName(nameInArchive);
}
emitProgressIfArchiveGrew();
return true;
}

static const QLatin1String kUiBytesPrefix("[PZIP_UI] inbytes ");
if (line.startsWith(kUiBytesPrefix)) {
if (m_qTotalSize > 0) {
bool ok = false;
const qint64 inBytes = line.mid(kUiBytesPrefix.size()).trimmed().toLongLong(&ok);
if (ok && inBytes >= 0) {
double p = static_cast<double>(inBytes) / static_cast<double>(m_qTotalSize) * 100.0;
if (p > 99.9) p = 99.9; // 结束由 processFinished 统一补 100
if (p < 0.0) p = 0.0;

// 只要有实际变化就推一把,让 UI 能稳定算速度/剩余时间
if (m_lastUiBytesProgress < 0.0 || std::abs(p - m_lastUiBytesProgress) >= 0.05) {
m_lastUiBytesProgress = p;
emit signalprogress(p);
}
}
}
return true;
}

if (line.contains(QLatin1String("No space left on device"))) {
m_eErrorType = ET_InsufficientDiskSpace;
return false;
Expand All @@ -337,13 +514,7 @@ bool CliPzipPlugin::handleLine(const QString &line)
// 不一定是致命错误,继续处理
}

// 更新进度
if (m_qTotalSize > 0) {
QFileInfo info(m_strArchiveName);
emit signalprogress(static_cast<double>(info.size()) / m_qTotalSize * 100);
}

emit signalCurFileName(line);
emitProgressIfArchiveGrew();
return true;
}

Expand Down Expand Up @@ -374,6 +545,7 @@ void CliPzipPlugin::killProcess(bool emitFinished)

void CliPzipPlugin::deleteProcess()
{
m_passwordFile.reset();
if (m_process) {
readStdout(true);
m_process->blockSignals(true);
Expand Down Expand Up @@ -467,7 +639,22 @@ void CliPzipPlugin::processFinished(int exitCode)
PluginFinishType eFinishType;

if (0 == exitCode && exitStatus == QProcess::NormalExit) {
eFinishType = PFT_Nomral;
bool ok = true;

// 若走了 MTP staging,成功后将临时文件移动到最终目标路径
if (!m_tempArchiveName.isEmpty() && m_tempArchiveDir) {
QProcess mover;
const QStringList args = {m_tempArchiveName, m_strArchiveName};
int ret = mover.execute(QStringLiteral("mv"), args);
ok = (ret == 0) && (mover.exitStatus() == QProcess::NormalExit) && (mover.exitCode() == 0);
qInfo() << "[PZIP_ROUTE]" << "clipzip: mv staged archive to final, ok=" << ok
<< "from" << m_tempArchiveName << "to" << m_strArchiveName;
if (!ok) {
m_eErrorType = ET_FileWriteError;
}
}

eFinishType = ok ? PFT_Nomral : PFT_Error;
} else {
eFinishType = PFT_Error;
}
Expand Down
12 changes: 12 additions & 0 deletions 3rdparty/clipzipplugin/clipzipplugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

#include <QProcess>
#include <QTimer>
#include <memory>
#include <QTemporaryFile>
#include <QTemporaryDir>
#include <QtGlobal> // for QT_VERSION_CHECK

class CliPzipPluginFactory : public KPluginFactory
Expand Down Expand Up @@ -88,6 +91,8 @@ class CliPzipPlugin : public ReadWriteArchiveInterface
*/
void getChildProcessId(qint64 processId, const QStringList &listKey, QVector<qint64> &childprocessid);

void emitProgressIfArchiveGrew();

private slots:
void readStdout(bool handleAll = false);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
Expand All @@ -104,6 +109,13 @@ private slots:
QVector<qint64> m_childProcessId;
qint64 m_qTotalSize = 0;
QTimer *m_timer = nullptr;
qint64 m_lastProgressArchiveSize = -1;
double m_lastUiBytesProgress = -1.0;

std::unique_ptr<QTemporaryFile> m_passwordFile;
std::unique_ptr<QTemporaryDir> m_tempArchiveDir;
QString m_tempArchiveName;
QString m_progressArchiveName;

// 解压相关
QString m_extractDestPath;
Expand Down
10 changes: 9 additions & 1 deletion src/pzip/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ find_package(PkgConfig REQUIRED)
find_package(ZLIB REQUIRED)
find_package(Threads REQUIRED)

find_package(OpenSSL REQUIRED)

include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${ZLIB_INCLUDE_DIRS})

Expand All @@ -26,11 +28,17 @@ set(PZIP_SOURCES
src/fast_deflate.cpp
src/utils.cpp
src/worker_pool.cpp
src/crypto/aes_encryptor.cpp
src/crypto/winzip_aes_extra.cpp
)

add_library(pzip_core_lib STATIC ${PZIP_SOURCES})
target_include_directories(pzip_core_lib PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(pzip_core_lib ${ZLIB_LIBRARIES} Threads::Threads)
target_link_libraries(pzip_core_lib
${ZLIB_LIBRARIES}
Threads::Threads
OpenSSL::Crypto
)

target_compile_features(pzip_core_lib PUBLIC cxx_std_17)

Expand Down
Loading
Loading