Files
spark-store/spark-update-tool/src/aptssupdater.cpp
shenmo f395d654ea fix(aptss): 添加aptss命令存在性检查并支持apm更新
在运行aptss相关操作前检查命令是否存在,避免错误执行
同时添加对apm命令的支持,当aptss不存在时尝试使用apm更新
2026-04-05 10:26:09 +08:00

664 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "aptssupdater.h"
#include <QProcess>
#include <QTextStream>
#include <QRegularExpression>
#include <QFile>
#include <QDir>
#include <qdebug.h>
aptssUpdater::aptssUpdater(QWidget *parent)
: QWidget(parent)
{
packageName = getUpdateablePackages();
apmPackageName = getApmUpdateablePackages();
}
QStringList aptssUpdater::getUpdateablePackages()
{
QStringList packageDetails;
// 检查aptss命令是否存在
QProcess checkProcess;
checkProcess.start("which", QStringList() << "aptss");
if (!checkProcess.waitForFinished(5000) || checkProcess.exitCode() != 0) {
qDebug() << "aptss命令不存在跳过Spark更新检查";
return packageDetails;
}
QProcess process;
QString command = R"(env LANGUAGE=en_US /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf list --upgradable -o Dir::Etc::sourcelist="/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list" -o Dir::Etc::sourceparts="/dev/null" -o APT::Get::List-Cleanup="0" | awk 'NR>1')";
process.start("bash", QStringList() << "-c" << command);
if (!process.waitForFinished(30000)) { // 30秒超时
qWarning() << "Process failed to finish within 30 seconds.";
process.kill();
return packageDetails;
}
QString output = process.readAllStandardOutput();
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
#else
QStringList lines = output.split('\n', QString::SkipEmptyParts);
#endif
// 创建临时文件
QTemporaryFile tempFile;
tempFile.setAutoRemove(false);
if (tempFile.open()) {
QTextStream stream(&tempFile);
for (const QString &line : lines) {
QRegularExpression regex(R"(([\w\-\+\.]+)/\S+\s+([^\s]+)\s+\S+\s+\[upgradable from: ([^\]]+)\])");
QRegularExpressionMatch match = regex.match(line);
if (match.hasMatch()) {
QString name = match.captured(1);
QString newVersion = match.captured(2);
QString oldVersion = match.captured(3);
// 检查版本是否相同,相同则跳过
if (newVersion == oldVersion) {
qDebug() << "跳过版本相同的包:" << name << "(" << oldVersion << "" << newVersion << ")";
continue;
}
// 写入内存列表
packageDetails << QString("%1: %2 → %3").arg(name, oldVersion, newVersion);
// 写入临时文件(原始数据)
stream << name << "|" << oldVersion << "|" << newVersion << "\n";
}
}
tempFile.close();
m_tempFilePath = tempFile.fileName();
qDebug()<< "临时文件路径:" << m_tempFilePath;
} else {
qWarning() << "无法创建临时文件";
}
return packageDetails;
}
QStringList aptssUpdater::getPackageSizes()
{
QStringList packageDetails;
// 获取可更新包名列表
QStringList updateablePackages;
for (const QString &pkgInfo : packageName) {
updateablePackages << pkgInfo.section(":", 0, 0).trimmed();
}
foreach (const QString &packageName, updateablePackages) {
QProcess process; // 在循环内部创建新的QProcess实例
// 构建新命令(包含包名参数)
QString command = QString("/usr/bin/apt download %1 --print-uris -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf "
"-o Dir::Etc::sourcelist=\"/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list\" "
"-o Dir::Etc::sourceparts=\"/dev/null\"").arg(packageName);
process.start("bash", QStringList() << "-c" << command);
if (!process.waitForFinished(30000)) { // 30秒超时
qWarning() << "获取包信息失败:" << packageName << "(超时)";
process.kill();
continue;
}
QString output = process.readAllStandardOutput();
// 使用正则匹配所有信息
// 调整正则表达式匹配分组
QRegularExpression regex(R"('([^']+)'\s+(\S+)\s+(\d+)\s+SHA512:([^\s]+))"); // 分组1:URL 分组2:文件名 分组3:大小 分组4:SHA512
QRegularExpressionMatch match = regex.match(output);
if (match.hasMatch()) {
QString url = match.captured(1);
QString fileName = match.captured(2);
QString size = match.captured(3);
QString sha512 = match.captured(4);
// 调整字段顺序:包名 | 大小 | URL | SHA512
packageDetails << QString("%1: %2|%3|%4").arg(packageName, size, url, sha512);
}
}
qDebug() << "完整包信息:" << packageDetails;
return packageDetails;
}
QStringList aptssUpdater::getDesktopAppNames()
{
QStringList appNames;
// 获取当前系统语言环境
QString lang = QLocale().name().replace("_", "-");
// 遍历所有可更新包(复用已有的临时文件数据)
QStringList packages = packageName;
foreach (const QString &package, packages) {
QProcess dpkgProcess; // 在循环内部创建新的QProcess实例
QString packageName = package.split(":")[0];
QString finalName = packageName; // 默认使用包名
// 获取包文件列表
dpkgProcess.start("dpkg", QStringList() << "-L" << packageName);
if (!dpkgProcess.waitForFinished(30000)) { // 30秒超时
qWarning() << "获取包文件列表失败:" << packageName << "(超时)";
dpkgProcess.kill();
continue;
}
// 修复:添加这行代码来获取进程输出
QString output = dpkgProcess.readAllStandardOutput();
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList files = output.split('\n', Qt::SkipEmptyParts);
#else
QStringList files = output.split('\n', QString::SkipEmptyParts);
#endif
// 先检查常规应用目录
QStringList regularDesktopFiles = files.filter("/usr/share/applications/");
QString regularAppName;
if (!regularDesktopFiles.isEmpty()) {
checkDesktopFiles(regularDesktopFiles, regularAppName, lang, packageName);
}
// 如果常规目录没有找到,再检查特殊目录
if (regularAppName.isEmpty()) {
QStringList specialDesktopFiles = files.filter(QRegularExpression(QString("/opt/apps/%1/entries/applications").arg(packageName)));
QString specialAppName;
if (!specialDesktopFiles.isEmpty()) {
checkDesktopFiles(specialDesktopFiles, specialAppName, lang, packageName);
if (!specialAppName.isEmpty()) {
finalName = specialAppName;
}
}
} else {
finalName = regularAppName;
}
// 输出格式为[软件名|包名]
appNames << QString("[%1|%2]").arg(finalName, packageName);
}
qDebug()<< "应用名称列表:" << appNames;
return appNames;
}
bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QString &appName, const QString &lang, const QString &packageName)
{
QString lastValidName;
QRegularExpression noDisplayRe("^NoDisplay=(true|True)");
QRegularExpression nameRe("^Name\\[?" + lang + "?\\]?=(.*)");
QRegularExpression nameOrigRe("^Name=(.*)");
foreach (const QString &filePath, desktopFiles) {
if (!filePath.endsWith(".desktop")) continue;
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) continue;
bool skip = false;
QString currentName;
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
// 检查NoDisplay属性
if (line.startsWith("NoDisplay=")) {
if (noDisplayRe.match(line).hasMatch()) {
skip = true;
break;
}
}
// 优先匹配本地化名称
if (currentName.isEmpty()) {
QRegularExpressionMatch match = nameRe.match(line);
if (match.hasMatch()) {
currentName = match.captured(1);
continue;
}
// 匹配原始名称
match = nameOrigRe.match(line);
if (match.hasMatch()) {
currentName = match.captured(1);
}
}
}
if (!skip && !currentName.isEmpty()) {
lastValidName = currentName;
}
}
// 处理最终的有效名称
if (!lastValidName.isEmpty()) {
appName = lastValidName; // 直接赋值而不是使用<<
return true;
}
// 回退到包名
appName = packageName;
return false;
}
QStringList aptssUpdater::getPackageIcons()
{
QStringList packageIcons;
// 遍历所有可更新包
QStringList packages = packageName;
foreach (const QString &package, packages) {
QProcess dpkgProcess; // 在循环内部创建新的QProcess实例
QString packageName = package.split(":")[0];
QString iconPath = ":/resources/default_icon.png"; // 默认图标
// 获取包文件列表
dpkgProcess.start("dpkg", QStringList() << "-L" << packageName);
if (!dpkgProcess.waitForFinished(30000)) { // 30秒超时
qWarning() << "获取包文件列表失败:" << packageName << "(超时)";
dpkgProcess.kill();
packageIcons << QString("%1: %2").arg(packageName, iconPath);
continue;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList files = QString(dpkgProcess.readAllStandardOutput()).split('\n', Qt::SkipEmptyParts);
#else
QStringList files = QString(dpkgProcess.readAllStandardOutput()).split('\n', QString::SkipEmptyParts);
#endif
// 查找.desktop文件
QStringList desktopFiles = files.filter(QRegularExpression("/(usr/share|opt/apps)/.*\\.desktop$"));
// 从.desktop文件中提取图标
foreach (const QString &desktopFile, desktopFiles) {
QFile file(desktopFile);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line.startsWith("Icon=")) {
QString iconName = line.mid(5).trimmed();
// 处理相对图标名如Icon=vscode
if (!iconName.contains('/')) {
// 查找标准图标路径
QStringList iconPaths = {
QString("/usr/share/pixmaps/%1.png").arg(iconName),
QString("/usr/share/icons/hicolor/48x48/apps/%1.png").arg(iconName),
QString("/usr/share/icons/hicolor/scalable/apps/%1.svg").arg(iconName),
QString("/opt/apps/%1/entries/icons/hicolor/48x48/apps/%2.png").arg(packageName, iconName)
};
foreach (const QString &path, iconPaths) {
if (QFile::exists(path)) {
iconPath = path;
qDebug() << "找到图标文件:" << path;
break;
}
}
} else {
// 已经是绝对路径
if (QFile::exists(iconName)) {
iconPath = iconName;
qDebug() << "使用绝对路径图标文件:" << iconName;
}
}
break;
}
}
file.close();
}
}
// 如果.desktop中没有找到图标尝试直接查找包中的图标文件
if (iconPath == ":/resources/default_icon.png") {
qDebug() << "未在.desktop文件中找到图标尝试直接查找包中的图标文件";
QStringList iconFiles = files.filter(QRegularExpression("/(usr/share/pixmaps|usr/share/icons|opt/apps/.*/entries/icons)/.*\\.(png|svg)$"));
if (!iconFiles.isEmpty()) {
iconPath = iconFiles.first();
qDebug() << "从包中找到图标文件:" << iconPath;
} else {
qDebug() << "未在包中找到图标文件,使用默认图标";
}
}
qDebug() << "包名:" << packageName << "图标路径:" << iconPath;
packageIcons << QString("%1: %2").arg(packageName, iconPath);
}
return packageIcons;
}
QJsonArray aptssUpdater::getUpdateInfoAsJson()
{
QJsonArray jsonArray;
// 获取所有需要的信息
QStringList sizes = getPackageSizes();
QStringList names = getDesktopAppNames();
QStringList icons = getPackageIcons();
// 创建包名到各种信息的映射
QHash<QString, QHash<QString, QString>> packageInfo;
// 解析包版本信息
for (const QString &pkg : packageName) {
QStringList parts = pkg.split(": ");
if (parts.size() >= 2) {
QString packageName = parts[0];
QStringList versions = parts[1].split("");
if (versions.size() == 2) {
packageInfo[packageName]["current_version"] = versions[0];
packageInfo[packageName]["new_version"] = versions[1];
}
}
}
// 解析包详细信息(新增部分)
for (const QString &sizeInfo : sizes) {
QStringList parts = sizeInfo.split(": ");
if (parts.size() == 2) {
QString packageName = parts[0];
QStringList details = parts[1].split("|");
if (details.size() == 3) { // 现在包含大小|URL|SHA512
packageInfo[packageName]["size"] = details[0];
packageInfo[packageName]["url"] = details[1];
packageInfo[packageName]["sha512"] = details[2];
}
}
}
// 解析应用名称信息
for (const QString &nameInfo : names) {
if (nameInfo.startsWith("[") && nameInfo.endsWith("]")) {
QString content = nameInfo.mid(1, nameInfo.length() - 2);
QStringList parts = content.split("|");
if (parts.size() == 2) {
QString displayName = parts[0];
QString packageName = parts[1];
packageInfo[packageName]["display_name"] = displayName;
}
}
}
// 解析图标信息
for (const QString &iconInfo : icons) {
QStringList parts = iconInfo.split(": ");
if (parts.size() == 2) {
QString packageName = parts[0];
packageInfo[packageName]["icon"] = parts[1].trimmed();
}
}
// 构建JSON数组
for (const QString &packageName : packageInfo.keys()) {
QJsonObject jsonObj;
jsonObj["package"] = packageName;
// 使用显示名称(如果有),否则使用包名
if (packageInfo[packageName].contains("display_name")) {
jsonObj["name"] = packageInfo[packageName]["display_name"];
} else {
jsonObj["name"] = packageName;
}
jsonObj["current_version"] = packageInfo[packageName]["current_version"];
jsonObj["new_version"] = packageInfo[packageName]["new_version"];
jsonObj["icon"] = packageInfo[packageName]["icon"];
jsonObj["ignored"] = false; // 默认不忽略
// 如果有大小信息也加入
if (packageInfo[packageName].contains("size")) {
jsonObj["size"] = packageInfo[packageName]["size"];
}
// 在构建JSON对象时添加新字段在jsonObj中添加
if (packageInfo[packageName].contains("url")) {
jsonObj["download_url"] = packageInfo[packageName]["url"];
qDebug() << "生成的下载 URL:" << packageInfo[packageName]["url"]; // 检查生成的 URL
} else {
qWarning() << "未找到下载 URL包名:" << packageName;
jsonObj["download_url"] = ""; // 设置为空字符串以避免崩溃
}
jsonObj["sha512"] = packageInfo[packageName]["sha512"];
jsonArray.append(jsonObj);
}
qDebug()<<jsonArray;
return jsonArray;
}
QStringList aptssUpdater::getApmUpdateablePackages()
{
QStringList packageDetails;
// 检查apm命令是否存在
QProcess checkProcess;
checkProcess.start("which", QStringList() << "apm");
if (!checkProcess.waitForFinished(5000) || checkProcess.exitCode() != 0) {
qDebug() << "apm命令不存在跳过APM更新检查";
return packageDetails;
}
QProcess process;
QString command = R"(env LANGUAGE=en_US /usr/bin/apm list --upgradable | awk 'NR>1')";
process.start("bash", QStringList() << "-c" << command);
if (!process.waitForFinished(30000)) { // 30秒超时
qWarning() << "APM process failed to finish within 30 seconds.";
process.kill();
return packageDetails;
}
QString output = process.readAllStandardOutput();
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
#else
QStringList lines = output.split('\n', QString::SkipEmptyParts);
#endif
for (const QString &line : lines) {
QRegularExpression regex(R"(([\w\-\+\.]+)/\S+\s+([^\s]+)\s+\S+\s+\[upgradable from: ([^\]]+)\])");
QRegularExpressionMatch match = regex.match(line);
if (match.hasMatch()) {
QString name = match.captured(1);
QString newVersion = match.captured(2);
QString oldVersion = match.captured(3);
// 检查版本是否相同,相同则跳过
if (newVersion == oldVersion) {
qDebug() << "跳过版本相同的APM包:" << name << "(" << oldVersion << "" << newVersion << ")";
continue;
}
// 写入内存列表
packageDetails << QString("%1: %2 → %3").arg(name, oldVersion, newVersion);
}
}
return packageDetails;
}
QJsonArray aptssUpdater::getApmUpdateInfoAsJson()
{
QJsonArray jsonArray;
// 解析APM包版本信息
QHash<QString, QHash<QString, QString>> packageInfo;
for (const QString &pkg : apmPackageName) {
QStringList parts = pkg.split(": ");
if (parts.size() >= 2) {
QString packageName = parts[0];
QStringList versions = parts[1].split("");
if (versions.size() == 2) {
packageInfo[packageName]["current_version"] = versions[0];
packageInfo[packageName]["new_version"] = versions[1];
packageInfo[packageName]["source"] = "apm";
}
}
}
// 构建JSON数组
for (const QString &packageName : packageInfo.keys()) {
QJsonObject jsonObj;
jsonObj["package"] = packageName;
// 从APM桌面文件中解析应用名称和图标
QString displayName = packageName; // 默认使用包名
QString iconPath = ":/resources/default_icon.png"; // 默认图标
// APM应用的desktop文件路径
QString apmDesktopPath = QString("/var/lib/apm/apm/files/ace-env/var/lib/apm/%1/entries/applications").arg(packageName);
QDir desktopDir(apmDesktopPath);
if (desktopDir.exists()) {
// 查找desktop文件
QStringList desktopFiles = desktopDir.entryList(QStringList() << "*.desktop", QDir::Files);
if (!desktopFiles.isEmpty()) {
QString desktopFile = desktopDir.absoluteFilePath(desktopFiles.first());
QFile file(desktopFile);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line.startsWith("Name=")) {
displayName = line.mid(5).trimmed();
} else if (line.startsWith("Icon=")) {
QString iconName = line.mid(5).trimmed();
// 处理图标路径
if (!iconName.contains('/')) {
// 查找APM包中的图标
QString apmIconPath = QString("/var/lib/apm/apm/files/ace-env/var/lib/apm/%1/entries/icons/hicolor/48x48/apps/%2.png").arg(packageName, iconName);
if (QFile::exists(apmIconPath)) {
iconPath = apmIconPath;
}
} else {
// 已经是绝对路径
if (QFile::exists(iconName)) {
iconPath = iconName;
}
}
}
}
file.close();
}
}
}
// 获取APM包大小和下载信息
QString size = "0";
QString url = "";
QString sha512 = "";
QProcess process;
QString command = QString("amber-pm-debug /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf download %1 --print-uris").arg(packageName);
process.start("bash", QStringList() << "-c" << command);
if (process.waitForFinished(30000)) { // 30秒超时
QString output = process.readAllStandardOutput();
// 解析输出格式:'URL' 文件名 大小 SHA512:哈希值
QRegularExpression regex(R"('([^']+)'\s+\S+\s+(\d+)\s+SHA512:([^\s]+))");
QRegularExpressionMatch match = regex.match(output);
if (match.hasMatch()) {
url = match.captured(1);
size = match.captured(2);
sha512 = match.captured(3);
}
}
jsonObj["name"] = displayName;
jsonObj["current_version"] = packageInfo[packageName]["current_version"];
jsonObj["new_version"] = packageInfo[packageName]["new_version"];
jsonObj["icon"] = iconPath;
jsonObj["ignored"] = false; // 默认不忽略
jsonObj["source"] = "apm";
jsonObj["size"] = size;
jsonObj["download_url"] = url;
jsonObj["sha512"] = sha512;
jsonArray.append(jsonObj);
}
qDebug()<<"APM更新信息:"<<jsonArray;
return jsonArray;
}
QJsonArray aptssUpdater::mergeUpdateInfo()
{
QJsonArray aptssInfo = getUpdateInfoAsJson();
QJsonArray apmInfo = getApmUpdateInfoAsJson();
// 创建包名到更新信息的映射
QHash<QString, QJsonObject> aptssMap;
for (const QJsonValue &value : aptssInfo) {
QJsonObject obj = value.toObject();
QString packageName = obj["package"].toString();
obj["source"] = "aptss";
aptssMap[packageName] = obj;
}
QHash<QString, QJsonObject> apmMap;
for (const QJsonValue &value : apmInfo) {
QJsonObject obj = value.toObject();
QString packageName = obj["package"].toString();
obj["source"] = "apm";
apmMap[packageName] = obj;
}
QJsonArray mergedArray;
// 处理只在aptss中存在的包
for (const QString &packageName : aptssMap.keys()) {
if (!apmMap.contains(packageName)) {
mergedArray.append(aptssMap[packageName]);
}
}
// 处理只在apm中存在的包
for (const QString &packageName : apmMap.keys()) {
if (!aptssMap.contains(packageName)) {
mergedArray.append(apmMap[packageName]);
}
}
// 处理在两者中都存在的包
for (const QString &packageName : aptssMap.keys()) {
if (apmMap.contains(packageName)) {
QJsonObject aptssObj = aptssMap[packageName];
QJsonObject apmObj = apmMap[packageName];
// 比较版本
QString aptssVersion = aptssObj["new_version"].toString();
QString apmVersion = apmObj["new_version"].toString();
// 这里简化处理,实际应该使用版本比较函数
if (apmVersion > aptssVersion) {
// APM版本更高使用APM版本
mergedArray.append(apmObj);
} else {
// APTSS版本更高或相同不展示该包
qDebug() << "APTSS版本更高不展示APM包:" << packageName;
}
}
}
qDebug()<<"合并后的更新信息:"<<mergedArray;
return mergedArray;
}