mirror of
https://gitee.com/spark-store-project/spark-store
synced 2025-10-04 09:32:21 +08:00
399 lines
15 KiB
C++
399 lines
15 KiB
C++
#include "aptssupdater.h"
|
||
#include <QProcess>
|
||
#include <QTextStream>
|
||
#include <QRegularExpression>
|
||
#include <QFile>
|
||
#include <qdebug.h>
|
||
|
||
aptssUpdater::aptssUpdater(QWidget *parent)
|
||
: QWidget(parent)
|
||
{
|
||
packageName = getUpdateablePackages();
|
||
}
|
||
|
||
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/sparkstore.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()) {
|
||
qWarning() << "Process failed to finish.";
|
||
return packageDetails;
|
||
}
|
||
|
||
QString output = process.readAllStandardOutput();
|
||
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
|
||
|
||
// 创建临时文件
|
||
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;
|
||
QProcess process;
|
||
|
||
// 获取可更新包名列表
|
||
QStringList updateablePackages;
|
||
for (const QString &pkgInfo : packageName) {
|
||
updateablePackages << pkgInfo.section(":", 0, 0).trimmed();
|
||
}
|
||
|
||
foreach (const QString &packageName, updateablePackages) {
|
||
// 构建新命令(包含包名参数)
|
||
QString command = QString("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/sparkstore.list\" "
|
||
"-o Dir::Etc::sourceparts=\"/dev/null\"").arg(packageName);
|
||
|
||
process.start("bash", QStringList() << "-c" << command);
|
||
if (!process.waitForFinished()) {
|
||
qWarning() << "获取包信息失败:" << packageName;
|
||
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;
|
||
QProcess dpkgProcess;
|
||
|
||
// 获取当前系统语言环境
|
||
QString lang = QLocale().name().replace("_", "-");
|
||
|
||
// 遍历所有可更新包(复用已有的临时文件数据)
|
||
QStringList packages = packageName;
|
||
|
||
foreach (const QString &package, packages) {
|
||
QString packageName = package.split(":")[0];
|
||
QString finalName = packageName; // 默认使用包名
|
||
|
||
// 获取包文件列表
|
||
dpkgProcess.start("dpkg", QStringList() << "-L" << packageName);
|
||
dpkgProcess.waitForFinished();
|
||
QStringList files = QString(dpkgProcess.readAllStandardOutput()).split('\n', Qt::SkipEmptyParts);
|
||
|
||
// 先检查常规应用目录
|
||
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;
|
||
QProcess dpkgProcess;
|
||
|
||
// 遍历所有可更新包
|
||
QStringList packages = packageName;
|
||
|
||
foreach (const QString &package, packages) {
|
||
QString packageName = package.split(":")[0];
|
||
QString iconPath = ":/resources/default_icon.svg"; // 默认图标
|
||
|
||
// 获取包文件列表
|
||
dpkgProcess.start("dpkg", QStringList() << "-L" << packageName);
|
||
dpkgProcess.waitForFinished();
|
||
QStringList files = QString(dpkgProcess.readAllStandardOutput()).split('\n', Qt::SkipEmptyParts);
|
||
|
||
// 查找.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;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
// 已经是绝对路径
|
||
if (QFile::exists(iconName)) {
|
||
iconPath = iconName;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
file.close();
|
||
}
|
||
}
|
||
|
||
// 如果.desktop中没有找到图标,尝试直接查找包中的图标文件
|
||
if (iconPath == ":/resources/default_icon.svg") {
|
||
QStringList iconFiles = files.filter(QRegularExpression("/(usr/share/pixmaps|usr/share/icons|opt/apps/.*/entries/icons)/.*\\.(png|svg)$"));
|
||
if (!iconFiles.isEmpty()) {
|
||
iconPath = iconFiles.first();
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
|