This commit is contained in:
2026-04-04 17:28:58 +08:00
parent b836392ffb
commit 80291aef7c
90 changed files with 8178 additions and 0 deletions

View File

@@ -0,0 +1,654 @@
#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;
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;
}